@xfey/tutti 0.1.42 → 0.1.44
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 +39 -0
- package/dist/artifacts/candidate-validator.js +512 -0
- package/dist/artifacts/index.d.ts +3 -1
- package/dist/artifacts/index.js +3 -1
- package/dist/artifacts/manifest-contract.d.ts +30 -0
- package/dist/artifacts/manifest-contract.js +214 -0
- package/dist/artifacts/manifest.d.ts +3 -12
- package/dist/artifacts/manifest.js +29 -201
- package/dist/artifacts/preview-runtime.d.ts +29 -10
- package/dist/artifacts/preview-runtime.js +400 -155
- package/dist/artifacts/process-boundary.d.ts +33 -0
- package/dist/artifacts/process-boundary.js +134 -0
- package/dist/control-plane/types.d.ts +7 -2
- package/dist/prompt-templates/index.d.ts +1 -1
- package/dist/prompt-templates/index.js +1 -0
- package/dist/provider-usage/index.js +1 -0
- package/dist/run-pipeline/artifact-applicability.d.ts +49 -0
- package/dist/run-pipeline/artifact-applicability.js +281 -0
- package/dist/run-pipeline/openai.d.ts +6 -0
- package/dist/run-pipeline/openai.js +72 -2
- package/dist/run-pipeline/promotion-reconcile.d.ts +9 -1
- package/dist/run-pipeline/promotion-reconcile.js +65 -11
- package/dist/server-shell/cli/launch.d.ts +2 -0
- package/dist/server-shell/cli/launch.js +7 -1
- package/dist/server-shell/http/host-tunnel.js +1 -1
- package/dist/server-shell/http/routes/project-api/artifacts-routes.js +63 -14
- package/dist/server-shell/http/routes/project-api/types.d.ts +2 -0
- package/dist/server-shell/local-console/project-service.d.ts +5 -0
- package/dist/server-shell/local-console/project-service.js +25 -0
- package/dist/server-shell/local-console/server.js +10 -0
- package/dist/workspace-ops/index.d.ts +1 -1
- package/dist/workspace-ops/index.js +1 -1
- package/dist/workspace-ops/run-workspaces.d.ts +1 -0
- package/dist/workspace-ops/run-workspaces.js +27 -0
- package/node_modules/@tutti/shared/dist/schemas/api/artifacts.d.ts +28 -8
- package/node_modules/@tutti/shared/dist/schemas/api/artifacts.js +34 -9
- 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 +5 -1
- package/node_modules/@tutti/shared/dist/utils/redaction/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/utils/redaction/index.js +14 -0
- package/package.json +1 -1
- package/prompts/README.md +1 -0
- package/prompts/artifacts/README.md +7 -0
- package/prompts/artifacts/applicability.md +41 -0
- package/prompts/prompt-flow-map.md +29 -26
- package/prompts/runs/README.md +3 -3
- package/prompts/runs/task-continuation.md +36 -4
- package/prompts/runs/task-retry.md +42 -6
- package/prompts/runs/task-run.md +36 -4
- package/web/assets/index-B7I1QwT_.js +69 -0
- package/web/assets/index-F-ouhuJ5.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-Sqw_t67u.css +0 -1
- package/web/assets/index-cYUjFgtc.js +0 -29
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import type { CandidateArtifactValidationFailure } from "../artifacts/index.js";
|
|
1
2
|
import type { RunPipelineInput, RunPipelineResult } from "../control-plane/index.js";
|
|
2
3
|
import { type ResultCommit, type RunWorkspaceHandle } from "../workspace-ops/index.js";
|
|
3
4
|
import type { OpenAiRunPipelineRunnerOptions } from "./openai.js";
|
|
4
5
|
import type { CompletedNoRepoChangesTaskRunOutput, CompletedTaskRunOutput } from "./task-run-output.js";
|
|
6
|
+
export type PromotionReconcileOutcome = {
|
|
7
|
+
result: RunPipelineResult;
|
|
8
|
+
artifact_gate_failure?: {
|
|
9
|
+
correctable: boolean;
|
|
10
|
+
failure: CandidateArtifactValidationFailure;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
5
13
|
export declare function runPromotionReconcile(input: {
|
|
6
14
|
options: OpenAiRunPipelineRunnerOptions;
|
|
7
15
|
pipelineInput: RunPipelineInput;
|
|
@@ -9,5 +17,5 @@ export declare function runPromotionReconcile(input: {
|
|
|
9
17
|
taskOutput: CompletedTaskRunOutput | CompletedNoRepoChangesTaskRunOutput;
|
|
10
18
|
sourceCommit: ResultCommit;
|
|
11
19
|
changedPaths: string[];
|
|
12
|
-
}): Promise<
|
|
20
|
+
}): Promise<PromotionReconcileOutcome>;
|
|
13
21
|
//# sourceMappingURL=promotion-reconcile.d.ts.map
|
|
@@ -2,7 +2,14 @@ import { dirname, join } from "node:path";
|
|
|
2
2
|
import { createNodeNpmCheckPlan, executeCheckPlan } from "../checks/index.js";
|
|
3
3
|
import { applyRunResultToMergeWorkspace, cleanupMergeWorkspace, collectMergeDiff, createMergeCandidateCommit, prepareMergeWorkspace, promoteMergeCandidate, WorkspaceOpsError, } from "../workspace-ops/index.js";
|
|
4
4
|
import { candidateChangedAfterChecks } from "./candidate-diff.js";
|
|
5
|
+
import { runArtifactCandidateGate } from "./artifact-applicability.js";
|
|
5
6
|
import { checkSkipFields, checkStatus, checksSummary, docsStatusForChangedPaths, failedBeforeChecks, failedFinishedResult, promotionNotPromoted, successfulRunSummary, } from "./result-projections.js";
|
|
7
|
+
function reconcileOutcome(result, artifactGateFailure) {
|
|
8
|
+
return {
|
|
9
|
+
result,
|
|
10
|
+
...(artifactGateFailure === undefined ? {} : { artifact_gate_failure: artifactGateFailure }),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
6
13
|
function withPromotionReconcileEvent(result, event) {
|
|
7
14
|
return {
|
|
8
15
|
...result,
|
|
@@ -40,13 +47,60 @@ export async function runPromotionReconcile(input) {
|
|
|
40
47
|
changedPaths = diff.changed_paths;
|
|
41
48
|
if (changedPaths.length === 0) {
|
|
42
49
|
const summary = "Promotion reconcile produced no candidate changes.";
|
|
43
|
-
return withPromotionReconcileEvent(failedBeforeChecks({
|
|
50
|
+
return reconcileOutcome(withPromotionReconcileEvent(failedBeforeChecks({
|
|
44
51
|
summary,
|
|
45
52
|
errorCode: "promotion_reconcile_no_candidate_changes",
|
|
46
53
|
retryable: false,
|
|
47
54
|
changedPaths,
|
|
48
55
|
promotion: promotionNotPromoted("promotion_reconcile_no_candidate_changes", "No reconciled candidate changes were available for promotion."),
|
|
56
|
+
}), failedPromotionReconcileEvent({ summary, changedPaths })));
|
|
57
|
+
}
|
|
58
|
+
input.pipelineInput.on_stage?.({
|
|
59
|
+
stage: "artifact_candidate_validate",
|
|
60
|
+
summary: "validating the reconciled candidate Artifact preview.",
|
|
61
|
+
});
|
|
62
|
+
const artifactGate = await runArtifactCandidateGate({
|
|
63
|
+
repoRoot: handle.repo_root,
|
|
64
|
+
task: {
|
|
65
|
+
title: input.pipelineInput.task.title,
|
|
66
|
+
goal: input.pipelineInput.task_detail.detail.contract.goal,
|
|
67
|
+
scope: input.pipelineInput.task_detail.detail.contract.scope,
|
|
68
|
+
},
|
|
69
|
+
diff,
|
|
70
|
+
config: input.options.config,
|
|
71
|
+
apiKey: input.options.apiKey,
|
|
72
|
+
...(input.options.promptsRoot === undefined
|
|
73
|
+
? {}
|
|
74
|
+
: { promptsRoot: input.options.promptsRoot }),
|
|
75
|
+
...(input.options.baseEnv === undefined ? {} : { baseEnv: input.options.baseEnv }),
|
|
76
|
+
...(input.options.artifactApplicabilityClient === undefined
|
|
77
|
+
? {}
|
|
78
|
+
: { client: input.options.artifactApplicabilityClient }),
|
|
79
|
+
...(input.options.artifactApplicabilityJudge === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: { judge: input.options.artifactApplicabilityJudge }),
|
|
82
|
+
...(input.options.validateArtifactCandidate === undefined
|
|
83
|
+
? {}
|
|
84
|
+
: { validateCandidate: input.options.validateArtifactCandidate }),
|
|
85
|
+
...(input.options.recordUsage === undefined
|
|
86
|
+
? {}
|
|
87
|
+
: { recordUsage: input.options.recordUsage }),
|
|
88
|
+
activityRef: input.pipelineInput.activity_ref,
|
|
89
|
+
taskId: input.pipelineInput.task.id,
|
|
90
|
+
});
|
|
91
|
+
if (artifactGate.status === "failed") {
|
|
92
|
+
const summary = artifactGate.failure.summary;
|
|
93
|
+
const result = withPromotionReconcileEvent(failedBeforeChecks({
|
|
94
|
+
summary,
|
|
95
|
+
errorCode: artifactGate.failure.reason_code,
|
|
96
|
+
retryable: false,
|
|
97
|
+
changedPaths,
|
|
98
|
+
promotion: promotionNotPromoted(artifactGate.failure.reason_code, "Reconciled Artifact candidate validation failed, so promotion failed."),
|
|
49
99
|
}), failedPromotionReconcileEvent({ summary, changedPaths }));
|
|
100
|
+
return reconcileOutcome(result, {
|
|
101
|
+
correctable: artifactGate.correctable,
|
|
102
|
+
failure: artifactGate.failure,
|
|
103
|
+
});
|
|
50
104
|
}
|
|
51
105
|
let docs = docsStatusForChangedPaths(changedPaths);
|
|
52
106
|
const checkPlan = createNodeNpmCheckPlan({ workspaceRoot: handle.repo_root });
|
|
@@ -59,20 +113,20 @@ export async function runPromotionReconcile(input) {
|
|
|
59
113
|
});
|
|
60
114
|
if (checks.status === "failed") {
|
|
61
115
|
const summary = checksSummary(checks);
|
|
62
|
-
return withPromotionReconcileEvent(failedFinishedResult({
|
|
116
|
+
return reconcileOutcome(withPromotionReconcileEvent(failedFinishedResult({
|
|
63
117
|
summary,
|
|
64
118
|
checks,
|
|
65
119
|
docs,
|
|
66
120
|
changedPaths,
|
|
67
121
|
promotion: promotionNotPromoted("promotion_reconcile_checks_failed", "Reconciled candidate checks failed, so promotion failed."),
|
|
68
|
-
}), failedPromotionReconcileEvent({ summary, changedPaths }));
|
|
122
|
+
}), failedPromotionReconcileEvent({ summary, changedPaths })));
|
|
69
123
|
}
|
|
70
124
|
const postChecksDiff = collectMergeDiff(handle);
|
|
71
125
|
changedPaths = postChecksDiff.changed_paths;
|
|
72
126
|
docs = docsStatusForChangedPaths(changedPaths);
|
|
73
127
|
if (candidateChangedAfterChecks(diff, postChecksDiff)) {
|
|
74
128
|
const summary = "Checks modified the reconciled candidate workspace; promotion failed.";
|
|
75
|
-
return withPromotionReconcileEvent(failedFinishedResult({
|
|
129
|
+
return reconcileOutcome(withPromotionReconcileEvent(failedFinishedResult({
|
|
76
130
|
summary,
|
|
77
131
|
checks: {
|
|
78
132
|
status: "failed",
|
|
@@ -81,11 +135,11 @@ export async function runPromotionReconcile(input) {
|
|
|
81
135
|
docs,
|
|
82
136
|
changedPaths,
|
|
83
137
|
promotion: promotionNotPromoted("promotion_reconcile_checks_modified_candidate", "Checks changed tracked reconciled candidate files, so promotion failed."),
|
|
84
|
-
}), failedPromotionReconcileEvent({ summary, changedPaths }));
|
|
138
|
+
}), failedPromotionReconcileEvent({ summary, changedPaths })));
|
|
85
139
|
}
|
|
86
140
|
const commit = createMergeCandidateCommit(handle, `Reconcile Tutti task: ${input.pipelineInput.task.title}`);
|
|
87
141
|
const promotion = promoteMergeCandidate(handle, commit);
|
|
88
|
-
return withPromotionReconcileEvent({
|
|
142
|
+
return reconcileOutcome(withPromotionReconcileEvent({
|
|
89
143
|
result_kind: "finished",
|
|
90
144
|
outcome: "completed",
|
|
91
145
|
summary: successfulRunSummary({
|
|
@@ -111,26 +165,26 @@ export async function runPromotionReconcile(input) {
|
|
|
111
165
|
summary: "Promotion reconciled and promoted.",
|
|
112
166
|
commit_oid: promotion.promoted_commit_oid,
|
|
113
167
|
meta: promotionReconcileMeta(changedPaths),
|
|
114
|
-
});
|
|
168
|
+
}));
|
|
115
169
|
}
|
|
116
170
|
catch (error) {
|
|
117
171
|
if (error instanceof WorkspaceOpsError) {
|
|
118
|
-
return withPromotionReconcileEvent(failedBeforeChecks({
|
|
172
|
+
return reconcileOutcome(withPromotionReconcileEvent(failedBeforeChecks({
|
|
119
173
|
summary: error.message,
|
|
120
174
|
errorCode: `workspace_ops_${error.code}`,
|
|
121
175
|
retryable: error.code === "conflict",
|
|
122
176
|
changedPaths,
|
|
123
177
|
promotion: promotionNotPromoted("promotion_reconcile_failed", error.message),
|
|
124
|
-
}), failedPromotionReconcileEvent({ summary: error.message, changedPaths }));
|
|
178
|
+
}), failedPromotionReconcileEvent({ summary: error.message, changedPaths })));
|
|
125
179
|
}
|
|
126
180
|
const summary = error instanceof Error ? error.message : "Promotion reconcile failed";
|
|
127
|
-
return withPromotionReconcileEvent(failedBeforeChecks({
|
|
181
|
+
return reconcileOutcome(withPromotionReconcileEvent(failedBeforeChecks({
|
|
128
182
|
summary,
|
|
129
183
|
errorCode: "promotion_reconcile_failed",
|
|
130
184
|
retryable: false,
|
|
131
185
|
changedPaths,
|
|
132
186
|
promotion: promotionNotPromoted("promotion_reconcile_failed", summary),
|
|
133
|
-
}), failedPromotionReconcileEvent({ summary, changedPaths }));
|
|
187
|
+
}), failedPromotionReconcileEvent({ summary, changedPaths })));
|
|
134
188
|
}
|
|
135
189
|
finally {
|
|
136
190
|
if (handle !== undefined) {
|
|
@@ -11,6 +11,8 @@ export type LaunchPreparationOptions = {
|
|
|
11
11
|
now?: () => Date;
|
|
12
12
|
confirm?: LaunchConfirmationHandler;
|
|
13
13
|
allowWorkspaceRootTakeover?: boolean;
|
|
14
|
+
projectDisplayName?: string;
|
|
15
|
+
projectDescription?: string;
|
|
14
16
|
};
|
|
15
17
|
export type LaunchProviderStatus = OpenAiProviderConfigProjection["status"];
|
|
16
18
|
export type LaunchPreparationResult = {
|
|
@@ -4,7 +4,7 @@ import { readOpenAiProviderConfigProjection, resolveTuttiHome, } from "../../pro
|
|
|
4
4
|
import { LaunchError } from "./errors.js";
|
|
5
5
|
import { assertSafeProjectRoot, ensureGitBootstrap, } from "./git-bootstrap.js";
|
|
6
6
|
import { ensureMachineProjectBinding } from "./machine-local.js";
|
|
7
|
-
import { readProjectIdentity, readProjectDisplayName, writeProjectIdentity, } from "./project-identity.js";
|
|
7
|
+
import { readProjectIdentity, readProjectDisplayName, writeProjectDescription, writeProjectDisplayName, writeProjectIdentity, } from "./project-identity.js";
|
|
8
8
|
const DEFAULT_RELAY_URL = "https://tutti.now";
|
|
9
9
|
const DEFAULT_HOST_ENDPOINT_HOST = "127.0.0.1";
|
|
10
10
|
const AUTO_HOST_ENDPOINT_PORT = 0;
|
|
@@ -64,6 +64,12 @@ export async function prepareLaunchProject(options) {
|
|
|
64
64
|
gitOptions.confirm = options.confirm;
|
|
65
65
|
}
|
|
66
66
|
const git = await ensureGitBootstrap(gitOptions);
|
|
67
|
+
if (projectIdentityCreated && options.projectDisplayName !== undefined) {
|
|
68
|
+
writeProjectDisplayName(workspaceRoot, options.projectDisplayName);
|
|
69
|
+
}
|
|
70
|
+
if (projectIdentityCreated && options.projectDescription !== undefined) {
|
|
71
|
+
writeProjectDescription(workspaceRoot, options.projectDescription);
|
|
72
|
+
}
|
|
67
73
|
const bindingOptions = {
|
|
68
74
|
tuttiHome,
|
|
69
75
|
projectId,
|
|
@@ -1,13 +1,46 @@
|
|
|
1
1
|
import { HeartbeatArtifactPreviewBodySchema, HeartbeatArtifactPreviewResponseSchema, GetArtifactsResponseSchema, StartArtifactPreviewBodySchema, StartArtifactPreviewResponseSchema, StopArtifactPreviewBodySchema, StopArtifactPreviewResponseSchema, } from "@tutti/shared/schemas/api";
|
|
2
|
-
import { ARTIFACT_PREVIEW_CSP, ArtifactPreviewManager, readArtifactProjection, readArtifactStaticPreview, } from "../../../../artifacts/index.js";
|
|
2
|
+
import { ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_CSP, ArtifactPreviewManager, isArtifactPreviewHttpMethod, readArtifactProjection, readArtifactStaticPreview, } from "../../../../artifacts/index.js";
|
|
3
|
+
import { WorkspaceOpsError } from "../../../../workspace-ops/index.js";
|
|
3
4
|
import { executeIdempotentCommand } from "../../../command-idempotency/index.js";
|
|
4
5
|
import { HOST_PROJECT_API_BASE_PATH } from "./constants.js";
|
|
5
6
|
import { requireHostProjectSession, requireProjectStore, requireWorkspaceRoot, } from "./session-requirements.js";
|
|
6
7
|
function artifactInvalidates() {
|
|
7
8
|
return [{ kind: "artifacts" }];
|
|
8
9
|
}
|
|
10
|
+
const ARTIFACT_PREVIEW_ALLOWED_METHODS = "GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS";
|
|
11
|
+
function previewRequestHeaders(request) {
|
|
12
|
+
const headers = {};
|
|
13
|
+
for (const [name, value] of Object.entries(request.headers)) {
|
|
14
|
+
if (typeof value === "string") {
|
|
15
|
+
headers[name] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return headers;
|
|
19
|
+
}
|
|
20
|
+
function previewRequestBody(body) {
|
|
21
|
+
if (body === undefined || body === null) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
if (Buffer.isBuffer(body)) {
|
|
25
|
+
return body;
|
|
26
|
+
}
|
|
27
|
+
if (body instanceof Uint8Array) {
|
|
28
|
+
return Buffer.from(body);
|
|
29
|
+
}
|
|
30
|
+
if (typeof body === "string") {
|
|
31
|
+
return Buffer.from(body, "utf8");
|
|
32
|
+
}
|
|
33
|
+
return Buffer.from(JSON.stringify(body), "utf8");
|
|
34
|
+
}
|
|
35
|
+
function requireCurrentArtifactManifestMarker(request, manifestBlobOid) {
|
|
36
|
+
const marker = new URL(request.url, "http://artifact.invalid").searchParams.get("v");
|
|
37
|
+
if (marker !== null && marker !== manifestBlobOid) {
|
|
38
|
+
throw new WorkspaceOpsError("not_found", "Artifact preview manifest is stale");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
9
41
|
export function registerArtifactsRoutes(app, { options, workspaceEvents, }) {
|
|
10
42
|
const artifactPreviewManager = new ArtifactPreviewManager({
|
|
43
|
+
...options.artifactPreview,
|
|
11
44
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
12
45
|
onStatusChange: () => {
|
|
13
46
|
workspaceEvents.publish({
|
|
@@ -20,9 +53,8 @@ export function registerArtifactsRoutes(app, { options, workspaceEvents, }) {
|
|
|
20
53
|
});
|
|
21
54
|
},
|
|
22
55
|
});
|
|
23
|
-
app.addHook("onClose", (
|
|
24
|
-
artifactPreviewManager.stopAll();
|
|
25
|
-
done();
|
|
56
|
+
app.addHook("onClose", async () => {
|
|
57
|
+
await artifactPreviewManager.stopAll();
|
|
26
58
|
});
|
|
27
59
|
app.get(`${HOST_PROJECT_API_BASE_PATH}/artifacts`, {
|
|
28
60
|
schema: {
|
|
@@ -36,27 +68,37 @@ export function registerArtifactsRoutes(app, { options, workspaceEvents, }) {
|
|
|
36
68
|
workspaceRoot: requireWorkspaceRoot(options),
|
|
37
69
|
});
|
|
38
70
|
});
|
|
39
|
-
app.
|
|
71
|
+
app.all("/artifacts/preview/*", {
|
|
72
|
+
bodyLimit: ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES,
|
|
73
|
+
}, async (request, reply) => {
|
|
40
74
|
requireHostProjectSession(request, options);
|
|
75
|
+
const method = request.method.toUpperCase();
|
|
76
|
+
if (!isArtifactPreviewHttpMethod(method)) {
|
|
77
|
+
void reply
|
|
78
|
+
.code(405)
|
|
79
|
+
.header("allow", ARTIFACT_PREVIEW_ALLOWED_METHODS)
|
|
80
|
+
.send("Artifact preview method is not allowed");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
41
83
|
const workspaceRoot = requireWorkspaceRoot(options);
|
|
42
84
|
const projection = readArtifactProjection({
|
|
43
85
|
workspaceRoot,
|
|
44
86
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
45
87
|
});
|
|
46
|
-
if (projection.artifact.status === "declared"
|
|
88
|
+
if (projection.artifact.status === "declared") {
|
|
89
|
+
requireCurrentArtifactManifestMarker(request, projection.artifact.manifest_blob_oid);
|
|
90
|
+
}
|
|
91
|
+
if (projection.artifact.status === "declared" &&
|
|
92
|
+
projection.artifact.artifact.kind === "server") {
|
|
47
93
|
const queryIndex = request.url.indexOf("?");
|
|
94
|
+
const body = previewRequestBody(request.body);
|
|
48
95
|
const preview = await artifactPreviewManager.fetchServerPreview({
|
|
49
96
|
workspaceRoot,
|
|
50
97
|
path: request.params["*"],
|
|
98
|
+
method,
|
|
51
99
|
...(queryIndex < 0 ? {} : { query: request.url.slice(queryIndex + 1) }),
|
|
52
|
-
headers:
|
|
53
|
-
|
|
54
|
-
? { accept: request.headers.accept }
|
|
55
|
-
: {}),
|
|
56
|
-
...(typeof request.headers["accept-language"] === "string"
|
|
57
|
-
? { "accept-language": request.headers["accept-language"] }
|
|
58
|
-
: {}),
|
|
59
|
-
},
|
|
100
|
+
headers: previewRequestHeaders(request),
|
|
101
|
+
...(body === undefined ? {} : { body }),
|
|
60
102
|
});
|
|
61
103
|
for (const [key, value] of Object.entries(preview.headers)) {
|
|
62
104
|
void reply.header(key, value);
|
|
@@ -69,6 +111,13 @@ export function registerArtifactsRoutes(app, { options, workspaceEvents, }) {
|
|
|
69
111
|
.send(preview.content);
|
|
70
112
|
return;
|
|
71
113
|
}
|
|
114
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
115
|
+
void reply
|
|
116
|
+
.code(405)
|
|
117
|
+
.header("allow", "GET, HEAD")
|
|
118
|
+
.send("Static Artifact previews only support GET and HEAD");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
72
121
|
const preview = readArtifactStaticPreview({
|
|
73
122
|
workspaceRoot,
|
|
74
123
|
path: request.params["*"],
|
|
@@ -1,6 +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
5
|
import type { HostProjectStore } from "../../../../store/index.js";
|
|
5
6
|
import type { ProjectId, RelayProjectRef } from "@tutti/shared/ids";
|
|
6
7
|
import type { AccountAvatarProjection } from "@tutti/shared/domain";
|
|
@@ -72,6 +73,7 @@ export type HostProjectApiCapabilities = {
|
|
|
72
73
|
clarification: boolean;
|
|
73
74
|
};
|
|
74
75
|
export type HostProjectApiRouteOptions = {
|
|
76
|
+
artifactPreview?: Omit<ArtifactPreviewManagerOptions, "now" | "onStatusChange">;
|
|
75
77
|
project: {
|
|
76
78
|
project_id: ProjectId;
|
|
77
79
|
display_name: string;
|
|
@@ -23,6 +23,10 @@ export type LocalConsoleProviderInput = {
|
|
|
23
23
|
api_key: string;
|
|
24
24
|
model: string;
|
|
25
25
|
};
|
|
26
|
+
export type LocalConsoleProjectMetadataInput = {
|
|
27
|
+
display_name: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
};
|
|
26
30
|
export type LocalConsoleLaunchResult = {
|
|
27
31
|
project: LocalConsoleProject;
|
|
28
32
|
project_created: boolean;
|
|
@@ -59,6 +63,7 @@ export declare class LocalConsoleProjectService {
|
|
|
59
63
|
}>;
|
|
60
64
|
launchProject(input: {
|
|
61
65
|
workspacePath: string;
|
|
66
|
+
project?: LocalConsoleProjectMetadataInput;
|
|
62
67
|
provider?: LocalConsoleProviderInput;
|
|
63
68
|
}, context: LocalConsoleInvocationContext): Promise<LocalConsoleLaunchResult>;
|
|
64
69
|
openProject(projectId: string, context: LocalConsoleInvocationContext): Promise<LocalConsoleOpenResult>;
|
|
@@ -50,6 +50,20 @@ function validateProvider(input) {
|
|
|
50
50
|
}
|
|
51
51
|
return provider;
|
|
52
52
|
}
|
|
53
|
+
function validateProjectMetadata(input) {
|
|
54
|
+
const displayName = input.display_name.trim();
|
|
55
|
+
const description = input.description?.trim();
|
|
56
|
+
if (displayName === "" || displayName.length > 80) {
|
|
57
|
+
throw new LocalConsoleProjectError("project_display_name_invalid", "Project name must be between 1 and 80 characters.");
|
|
58
|
+
}
|
|
59
|
+
if (description !== undefined && description.length > 160) {
|
|
60
|
+
throw new LocalConsoleProjectError("project_description_invalid", "Project description cannot exceed 160 characters.");
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
display_name: displayName,
|
|
64
|
+
...(description === undefined || description === "" ? {} : { description }),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
53
67
|
function projectFromRuntimeRow(row, providerStatus, providerModel, metadata) {
|
|
54
68
|
const currentVersion = readCliVersion();
|
|
55
69
|
const openUrl = projectOpenUrl(metadata.relayUrl, row.relay_project_ref);
|
|
@@ -220,6 +234,9 @@ export class LocalConsoleProjectService {
|
|
|
220
234
|
}
|
|
221
235
|
const launch = this.#runMutation(async () => await this.#launchProject({
|
|
222
236
|
workspacePath,
|
|
237
|
+
...(input.project === undefined
|
|
238
|
+
? {}
|
|
239
|
+
: { project: validateProjectMetadata(input.project) }),
|
|
223
240
|
...(input.provider === undefined ? {} : { provider: validateProvider(input.provider) }),
|
|
224
241
|
}, context)).finally(() => {
|
|
225
242
|
this.#launches.delete(workspacePath);
|
|
@@ -234,6 +251,14 @@ export class LocalConsoleProjectService {
|
|
|
234
251
|
preparation = await prepareLaunchProject({
|
|
235
252
|
workspacePath: input.workspacePath,
|
|
236
253
|
yes: true,
|
|
254
|
+
...(input.project === undefined
|
|
255
|
+
? {}
|
|
256
|
+
: {
|
|
257
|
+
projectDisplayName: input.project.display_name,
|
|
258
|
+
...(input.project.description === undefined
|
|
259
|
+
? {}
|
|
260
|
+
: { projectDescription: input.project.description }),
|
|
261
|
+
}),
|
|
237
262
|
...operation,
|
|
238
263
|
});
|
|
239
264
|
}
|
|
@@ -256,6 +256,15 @@ export function createLocalConsoleServer(options) {
|
|
|
256
256
|
additionalProperties: false,
|
|
257
257
|
properties: {
|
|
258
258
|
workspace_path: { type: "string", minLength: 1, maxLength: 4096 },
|
|
259
|
+
project: {
|
|
260
|
+
type: "object",
|
|
261
|
+
required: ["display_name"],
|
|
262
|
+
additionalProperties: false,
|
|
263
|
+
properties: {
|
|
264
|
+
display_name: { type: "string", minLength: 1, maxLength: 80 },
|
|
265
|
+
description: { type: "string", maxLength: 160 },
|
|
266
|
+
},
|
|
267
|
+
},
|
|
259
268
|
provider: {
|
|
260
269
|
type: "object",
|
|
261
270
|
required: ["base_url", "api_key", "model"],
|
|
@@ -273,6 +282,7 @@ export function createLocalConsoleServer(options) {
|
|
|
273
282
|
const session = requireSession(request, true);
|
|
274
283
|
return await projects.launchProject({
|
|
275
284
|
workspacePath: request.body.workspace_path,
|
|
285
|
+
...(request.body.project === undefined ? {} : { project: request.body.project }),
|
|
276
286
|
...(request.body.provider === undefined ? {} : { provider: request.body.provider }),
|
|
277
287
|
}, session.context);
|
|
278
288
|
});
|
|
@@ -4,7 +4,7 @@ export { initializeProjectDocs, readProjectDocsInitializationStatus, syncProject
|
|
|
4
4
|
export { uploadReferenceFile } from "./reference-files.js";
|
|
5
5
|
export { referenceFileCategoryForPath, referenceFileSourceForPath, referenceMediaTypeForPath, referenceTuttiGroupForPath, } from "./reference-metadata.js";
|
|
6
6
|
export { readReferenceSummaryIndex, readReferenceSummaryTargets, referenceSummaryProjectionForEntry, updateReferenceSummaries, } from "./reference-summaries.js";
|
|
7
|
-
export { applyRunResultToMergeWorkspace, cleanupMergeWorkspace, cleanupRunWorkspace, cleanupStaleRunWorkspaces, collectMergeDiff, collectRunDiff, createMergeCandidateCommit, createResultCommit, prepareMergeWorkspace, prepareRunWorkspace, promoteMergeCandidate, promoteRunResult, } from "./run-workspaces.js";
|
|
7
|
+
export { applyRunResultToMergeWorkspace, cleanupMergeWorkspace, cleanupRunWorkspace, cleanupStaleRunWorkspaces, collectMergeDiff, collectRunDiff, createMergeCandidateCommit, createResultCommit, fingerprintWorkspaceCandidate, prepareMergeWorkspace, prepareRunWorkspace, promoteMergeCandidate, promoteRunResult, } from "./run-workspaces.js";
|
|
8
8
|
export { readViewerAsset, readViewerFile, readViewerSnapshot, readViewerTree } from "./viewer.js";
|
|
9
9
|
export type { WorkspaceOpsErrorCode } from "./errors.js";
|
|
10
10
|
export type { CleanupRunWorkspaceResult, CleanupStaleRunWorkspacesResult, InitializeProjectDocsOptions, InitializeProjectDocsResult, MergeWorkspaceHandle, PrepareMergeWorkspaceOptions, PrepareRunWorkspaceOptions, ProjectDocsInitializationContext, ProjectDocsInitializationDocument, ProjectDocsInitializationStatus, PromotionResult, ReadViewerAssetOptions, ReadViewerFileOptions, ReadViewerTreeOptions, ReferenceFileCategory, ReferenceSummaryProjection, ReferenceSummaryRecord, ReferenceSummaryStatus, ReferenceSummaryTarget, ResultCommit, RunDiffSummary, RunWorkspaceHandle, SyncProjectDocsResult, SyncProjectDocsUpdate, UpdateReferenceSummariesOptions, UpdateReferenceSummariesResult, UploadReferenceFileOptions, UploadReferenceFileResult, ViewerAssetResult, ViewerAssetVariant, ViewerEntryKind, ViewerFileContent, ViewerFileResult, ViewerRepoSnapshot, ViewerTreeEntry, ViewerTreeResult, WorkspaceViewerOptions, } from "./types.js";
|
|
@@ -4,6 +4,6 @@ export { initializeProjectDocs, readProjectDocsInitializationStatus, syncProject
|
|
|
4
4
|
export { uploadReferenceFile } from "./reference-files.js";
|
|
5
5
|
export { referenceFileCategoryForPath, referenceFileSourceForPath, referenceMediaTypeForPath, referenceTuttiGroupForPath, } from "./reference-metadata.js";
|
|
6
6
|
export { readReferenceSummaryIndex, readReferenceSummaryTargets, referenceSummaryProjectionForEntry, updateReferenceSummaries, } from "./reference-summaries.js";
|
|
7
|
-
export { applyRunResultToMergeWorkspace, cleanupMergeWorkspace, cleanupRunWorkspace, cleanupStaleRunWorkspaces, collectMergeDiff, collectRunDiff, createMergeCandidateCommit, createResultCommit, prepareMergeWorkspace, prepareRunWorkspace, promoteMergeCandidate, promoteRunResult, } from "./run-workspaces.js";
|
|
7
|
+
export { applyRunResultToMergeWorkspace, cleanupMergeWorkspace, cleanupRunWorkspace, cleanupStaleRunWorkspaces, collectMergeDiff, collectRunDiff, createMergeCandidateCommit, createResultCommit, fingerprintWorkspaceCandidate, prepareMergeWorkspace, prepareRunWorkspace, promoteMergeCandidate, promoteRunResult, } from "./run-workspaces.js";
|
|
8
8
|
export { readViewerAsset, readViewerFile, readViewerSnapshot, readViewerTree } from "./viewer.js";
|
|
9
9
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CleanupRunWorkspaceResult, CleanupStaleRunWorkspacesResult, MergeWorkspaceHandle, PrepareMergeWorkspaceOptions, PrepareRunWorkspaceOptions, PromotionResult, ResultCommit, RunDiffSummary, RunWorkspaceHandle } from "./types.js";
|
|
2
|
+
export declare function fingerprintWorkspaceCandidate(repoRoot: string): string;
|
|
2
3
|
export declare function prepareRunWorkspace(options: PrepareRunWorkspaceOptions): RunWorkspaceHandle;
|
|
3
4
|
export declare function collectRunDiff(handle: RunWorkspaceHandle): RunDiffSummary;
|
|
4
5
|
export declare function createResultCommit(handle: RunWorkspaceHandle, summary: string): ResultCommit;
|
|
@@ -1,10 +1,37 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { classifyViewerPath } from "@tutti/shared/utils";
|
|
4
5
|
import { WorkspaceOpsError } from "./errors.js";
|
|
5
6
|
import { runGit, runGitRaw, runGitText, stagedChangesExist, summarizeGitFailure, tryGitText, } from "./git.js";
|
|
6
7
|
import { requireMainlineWriteReady } from "./mainline.js";
|
|
7
8
|
const MAX_RUN_DIFF_SUMMARY_BYTES = 32 * 1024;
|
|
9
|
+
export function fingerprintWorkspaceCandidate(repoRoot) {
|
|
10
|
+
const status = runGitRaw(["status", "--porcelain=v1", "-z"], repoRoot);
|
|
11
|
+
if (status.status !== 0) {
|
|
12
|
+
throw new WorkspaceOpsError("git_failed", summarizeGitFailure(["status"], status));
|
|
13
|
+
}
|
|
14
|
+
const diff = runGitRaw(["diff", "--binary", "HEAD", "--"], repoRoot);
|
|
15
|
+
if (diff.status !== 0) {
|
|
16
|
+
throw new WorkspaceOpsError("git_failed", summarizeGitFailure(["diff", "HEAD"], diff));
|
|
17
|
+
}
|
|
18
|
+
const untracked = runGitRaw(["ls-files", "--others", "--exclude-standard", "-z"], repoRoot);
|
|
19
|
+
if (untracked.status !== 0) {
|
|
20
|
+
throw new WorkspaceOpsError("git_failed", summarizeGitFailure(["ls-files", "--others"], untracked));
|
|
21
|
+
}
|
|
22
|
+
const hash = createHash("sha256").update(status.stdout).update(diff.stdout);
|
|
23
|
+
for (const path of untracked.stdout.toString("utf8").split("\0")) {
|
|
24
|
+
if (path.length === 0) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const object = runGitRaw(["hash-object", "--no-filters", "--", path], repoRoot);
|
|
28
|
+
if (object.status !== 0) {
|
|
29
|
+
throw new WorkspaceOpsError("git_failed", summarizeGitFailure(["hash-object"], object));
|
|
30
|
+
}
|
|
31
|
+
hash.update(path).update("\0").update(object.stdout);
|
|
32
|
+
}
|
|
33
|
+
return hash.digest("hex");
|
|
34
|
+
}
|
|
8
35
|
function runWorkspaceTempBranch(taskId, activityRef) {
|
|
9
36
|
return `tutti/run/${taskId}/${activityRef}`;
|
|
10
37
|
}
|
|
@@ -6,15 +6,14 @@ export declare const ArtifactStaticManifestSchema: import("@sinclair/typebox").T
|
|
|
6
6
|
kind: import("@sinclair/typebox").TLiteral<"static">;
|
|
7
7
|
root: import("@sinclair/typebox").TString;
|
|
8
8
|
entry: import("@sinclair/typebox").TString;
|
|
9
|
-
network: import("@sinclair/typebox").
|
|
9
|
+
network: import("@sinclair/typebox").TBoolean;
|
|
10
10
|
}>;
|
|
11
11
|
export declare const ArtifactServerManifestSchema: import("@sinclair/typebox").TObject<{
|
|
12
12
|
title: import("@sinclair/typebox").TString;
|
|
13
13
|
kind: import("@sinclair/typebox").TLiteral<"server">;
|
|
14
14
|
cwd: import("@sinclair/typebox").TString;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
network: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
15
|
+
entry: import("@sinclair/typebox").TString;
|
|
16
|
+
network: import("@sinclair/typebox").TBoolean;
|
|
18
17
|
}>;
|
|
19
18
|
export declare const ArtifactManifestSchema: import("@sinclair/typebox").TObject<{
|
|
20
19
|
version: import("@sinclair/typebox").TLiteral<1>;
|
|
@@ -23,14 +22,13 @@ export declare const ArtifactManifestSchema: import("@sinclair/typebox").TObject
|
|
|
23
22
|
kind: import("@sinclair/typebox").TLiteral<"static">;
|
|
24
23
|
root: import("@sinclair/typebox").TString;
|
|
25
24
|
entry: import("@sinclair/typebox").TString;
|
|
26
|
-
network: import("@sinclair/typebox").
|
|
25
|
+
network: import("@sinclair/typebox").TBoolean;
|
|
27
26
|
}>, import("@sinclair/typebox").TObject<{
|
|
28
27
|
title: import("@sinclair/typebox").TString;
|
|
29
28
|
kind: import("@sinclair/typebox").TLiteral<"server">;
|
|
30
29
|
cwd: import("@sinclair/typebox").TString;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
network: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TBoolean>;
|
|
30
|
+
entry: import("@sinclair/typebox").TString;
|
|
31
|
+
network: import("@sinclair/typebox").TBoolean;
|
|
34
32
|
}>]>;
|
|
35
33
|
}>;
|
|
36
34
|
export declare const ArtifactPreviewStatusSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"ready">, import("@sinclair/typebox").TLiteral<"starting">, import("@sinclair/typebox").TLiteral<"stopped">, import("@sinclair/typebox").TLiteral<"failed">]>;
|
|
@@ -94,6 +92,28 @@ export declare const GetArtifactsResponseSchema: import("@sinclair/typebox").TOb
|
|
|
94
92
|
}>;
|
|
95
93
|
}>]>;
|
|
96
94
|
}>;
|
|
95
|
+
export declare const IssueArtifactPreviewSessionPayloadSchema: import("@sinclair/typebox").TObject<{}>;
|
|
96
|
+
export declare const IssueArtifactPreviewSessionDispositionSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
97
|
+
kind: import("@sinclair/typebox").TLiteral<"issued">;
|
|
98
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
99
|
+
kind: import("@sinclair/typebox").TLiteral<"not_declared">;
|
|
100
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
101
|
+
kind: import("@sinclair/typebox").TLiteral<"invalid_manifest">;
|
|
102
|
+
message: import("@sinclair/typebox").TString;
|
|
103
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
104
|
+
kind: import("@sinclair/typebox").TLiteral<"preview_not_ready">;
|
|
105
|
+
status: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"starting">, import("@sinclair/typebox").TLiteral<"stopped">, import("@sinclair/typebox").TLiteral<"failed">]>;
|
|
106
|
+
message: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|
|
107
|
+
}>, import("@sinclair/typebox").TObject<{
|
|
108
|
+
kind: import("@sinclair/typebox").TLiteral<"interrupted">;
|
|
109
|
+
retry_with_new_command_id: import("@sinclair/typebox").TLiteral<true>;
|
|
110
|
+
}>]>;
|
|
111
|
+
export declare const IssueArtifactPreviewSessionResultSchema: import("@sinclair/typebox").TObject<{
|
|
112
|
+
bootstrap_url: import("@sinclair/typebox").TString;
|
|
113
|
+
expires_at: import("@sinclair/typebox").TString;
|
|
114
|
+
}>;
|
|
115
|
+
export declare const IssueArtifactPreviewSessionBodySchema: import("@sinclair/typebox").TSchema;
|
|
116
|
+
export declare const IssueArtifactPreviewSessionResponseSchema: import("@sinclair/typebox").TSchema;
|
|
97
117
|
export declare const ArtifactPreviewCommandPayloadSchema: import("@sinclair/typebox").TObject<{}>;
|
|
98
118
|
export declare const StartArtifactPreviewDispositionSchema: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TObject<{
|
|
99
119
|
kind: import("@sinclair/typebox").TLiteral<"started">;
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { IsoDateTimeStringSchema } from "../domain/index.js";
|
|
2
3
|
import { InterruptedCommandDispositionSchema, NonEmptyStringSchema, commandEnvelopeSchema, commandResponseWithResultSchema, } from "./primitives.js";
|
|
3
|
-
import { ViewerPathSchema } from "./viewer-reference.js";
|
|
4
4
|
export const ArtifactManifestPathSchema = Type.Literal("tutti.artifact.json");
|
|
5
5
|
export const ArtifactKindSchema = Type.Union([Type.Literal("static"), Type.Literal("server")]);
|
|
6
|
-
export const ArtifactTitleSchema = Type.String({ minLength: 1, maxLength: 120 });
|
|
6
|
+
export const ArtifactTitleSchema = Type.String({ minLength: 1, maxLength: 120, pattern: "\\S" });
|
|
7
|
+
const ArtifactRepoPathSchema = Type.String({ minLength: 1 });
|
|
7
8
|
export const ArtifactStaticManifestSchema = Type.Object({
|
|
8
9
|
title: ArtifactTitleSchema,
|
|
9
10
|
kind: Type.Literal("static"),
|
|
10
|
-
root:
|
|
11
|
-
entry:
|
|
12
|
-
network: Type.
|
|
11
|
+
root: ArtifactRepoPathSchema,
|
|
12
|
+
entry: ArtifactRepoPathSchema,
|
|
13
|
+
network: Type.Boolean(),
|
|
13
14
|
}, { additionalProperties: false });
|
|
14
15
|
export const ArtifactServerManifestSchema = Type.Object({
|
|
15
16
|
title: ArtifactTitleSchema,
|
|
16
17
|
kind: Type.Literal("server"),
|
|
17
|
-
cwd:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
network: Type.Optional(Type.Boolean()),
|
|
18
|
+
cwd: ArtifactRepoPathSchema,
|
|
19
|
+
entry: Type.String({ minLength: 1, maxLength: 256, pattern: "^/" }),
|
|
20
|
+
network: Type.Boolean(),
|
|
21
21
|
}, { additionalProperties: false });
|
|
22
22
|
export const ArtifactManifestSchema = Type.Object({
|
|
23
23
|
version: Type.Literal(1),
|
|
@@ -62,6 +62,31 @@ export const ArtifactProjectionSchema = Type.Union([
|
|
|
62
62
|
export const GetArtifactsResponseSchema = Type.Object({
|
|
63
63
|
artifact: ArtifactProjectionSchema,
|
|
64
64
|
}, { additionalProperties: false });
|
|
65
|
+
export const IssueArtifactPreviewSessionPayloadSchema = Type.Object({}, { additionalProperties: false });
|
|
66
|
+
export const IssueArtifactPreviewSessionDispositionSchema = Type.Union([
|
|
67
|
+
Type.Object({ kind: Type.Literal("issued") }, { additionalProperties: false }),
|
|
68
|
+
Type.Object({ kind: Type.Literal("not_declared") }, { additionalProperties: false }),
|
|
69
|
+
Type.Object({
|
|
70
|
+
kind: Type.Literal("invalid_manifest"),
|
|
71
|
+
message: NonEmptyStringSchema,
|
|
72
|
+
}, { additionalProperties: false }),
|
|
73
|
+
Type.Object({
|
|
74
|
+
kind: Type.Literal("preview_not_ready"),
|
|
75
|
+
status: Type.Union([
|
|
76
|
+
Type.Literal("starting"),
|
|
77
|
+
Type.Literal("stopped"),
|
|
78
|
+
Type.Literal("failed"),
|
|
79
|
+
]),
|
|
80
|
+
message: Type.Optional(NonEmptyStringSchema),
|
|
81
|
+
}, { additionalProperties: false }),
|
|
82
|
+
InterruptedCommandDispositionSchema,
|
|
83
|
+
]);
|
|
84
|
+
export const IssueArtifactPreviewSessionResultSchema = Type.Object({
|
|
85
|
+
bootstrap_url: Type.String({ minLength: 1 }),
|
|
86
|
+
expires_at: IsoDateTimeStringSchema,
|
|
87
|
+
}, { additionalProperties: false });
|
|
88
|
+
export const IssueArtifactPreviewSessionBodySchema = commandEnvelopeSchema(IssueArtifactPreviewSessionPayloadSchema);
|
|
89
|
+
export const IssueArtifactPreviewSessionResponseSchema = commandResponseWithResultSchema(IssueArtifactPreviewSessionDispositionSchema, IssueArtifactPreviewSessionResultSchema);
|
|
65
90
|
export const ArtifactPreviewCommandPayloadSchema = Type.Object({}, { additionalProperties: false });
|
|
66
91
|
export const StartArtifactPreviewDispositionSchema = Type.Union([
|
|
67
92
|
Type.Object({ kind: Type.Literal("started") }, { additionalProperties: false }),
|