@xfey/tutti 0.1.53 → 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.
Files changed (32) hide show
  1. package/dist/artifacts/index.d.ts +3 -3
  2. package/dist/artifacts/index.js +3 -3
  3. package/dist/artifacts/live-manifest.d.ts +23 -0
  4. package/dist/artifacts/live-manifest.js +115 -0
  5. package/dist/artifacts/preview-runtime.d.ts +34 -36
  6. package/dist/artifacts/preview-runtime.js +215 -273
  7. package/dist/artifacts/static-preview.d.ts +2 -1
  8. package/dist/artifacts/static-preview.js +19 -21
  9. package/dist/control-plane/index.d.ts +1 -0
  10. package/dist/control-plane/index.js +13 -2
  11. package/dist/control-plane/types.d.ts +4 -0
  12. package/dist/server-shell/cli/host-server-runtime.js +20 -0
  13. package/dist/server-shell/http/routes/project-api/artifacts-routes.js +43 -110
  14. package/dist/server-shell/http/routes/project-api/openapi-artifacts-routes.d.ts +5 -8
  15. package/dist/server-shell/http/routes/project-api/openapi-artifacts-routes.js +5 -33
  16. package/dist/server-shell/http/routes/project-api/openapi.d.ts +17 -8
  17. package/dist/server-shell/http/routes/project-api/types.d.ts +3 -2
  18. package/node_modules/@tutti/shared/dist/schemas/api/artifacts.d.ts +7 -88
  19. package/node_modules/@tutti/shared/dist/schemas/api/artifacts.js +3 -48
  20. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
  21. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
  22. package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +1 -10
  23. package/package.json +1 -1
  24. package/web/assets/{homepage-motion-scene-F4ibPvI9.js → homepage-motion-scene-C96Na8TV.js} +1 -1
  25. package/web/assets/{index-B1mBtL9x.js → index-CM41hrQd.js} +3 -3
  26. package/web/assets/index-nWy1QP97.css +1 -0
  27. package/web/assets/tutti_avatar-_0Yc-cMJ.png +0 -0
  28. package/web/index.html +2 -2
  29. package/dist/artifacts/manifest.d.ts +0 -41
  30. package/dist/artifacts/manifest.js +0 -169
  31. package/web/assets/index-OFlcQ3eS.css +0 -1
  32. package/web/assets/tutti_avatar-BBhaZGi3.png +0 -0
@@ -1,6 +1,6 @@
1
- export { ARTIFACT_MANIFEST_PATH, ARTIFACT_PREVIEW_BASE_PATH, buildArtifactPreviewUrl, readArtifactDeclaration, readArtifactProjection, type ArtifactDeclaration, type DeclaredArtifactRuntime, type ReadArtifactDeclarationOptions, } from "./manifest.js";
1
+ export { readLiveArtifactDeclaration, type LiveArtifactDeclaration, } from "./live-manifest.js";
2
2
  export { validateCandidateArtifact, type ArtifactValidationDiagnostic, type ArtifactValidationDiagnosticSource, type ArtifactValidationReasonCode, type CandidateArtifactValidationFailure, type CandidateArtifactValidationResult, type ValidateCandidateArtifactOptions, } from "./candidate-validator.js";
3
- export { parseArtifactManifestContract, type ArtifactContractError, type ArtifactContractResult, type ValidatedArtifactContract, type ValidatedArtifactRuntime, } from "./manifest-contract.js";
4
- export { ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_RESPONSE_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_DEFAULT_BASE_PATH, ArtifactPreviewManager, isArtifactPreviewHttpMethod, type ArtifactPreviewCommandResult, type ArtifactPreviewHeartbeatResult, type ArtifactPreviewHttpMethod, type ArtifactPreviewManagerOptions, type ArtifactPreviewProcessPlan, type ArtifactPreviewStartResult, type ArtifactPreviewStopResult, type ArtifactServerPreviewResponse, } from "./preview-runtime.js";
3
+ export { ARTIFACT_MANIFEST_PATH, parseArtifactManifestContract, type ArtifactContractError, type ArtifactContractResult, type ValidatedArtifactContract, type ValidatedArtifactRuntime, } from "./manifest-contract.js";
4
+ export { ARTIFACT_PREVIEW_BASE_PATH, ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_RESPONSE_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_DEFAULT_BASE_PATH, ArtifactPreviewManager, isArtifactPreviewHttpMethod, type ArtifactPreviewLeaseOptions, type ArtifactPreviewHttpMethod, type ArtifactPreviewManagerOptions, type ArtifactPreviewProcessPlan, type ArtifactServerPreviewResponse, } from "./preview-runtime.js";
5
5
  export { ARTIFACT_PREVIEW_CSP, readArtifactStaticPreview, rewriteArtifactPreviewResourceUrls, type ArtifactStaticPreviewFile, type ReadArtifactStaticPreviewOptions, } from "./static-preview.js";
6
6
  //# sourceMappingURL=index.d.ts.map
@@ -1,6 +1,6 @@
1
- export { ARTIFACT_MANIFEST_PATH, ARTIFACT_PREVIEW_BASE_PATH, buildArtifactPreviewUrl, readArtifactDeclaration, readArtifactProjection, } from "./manifest.js";
1
+ export { readLiveArtifactDeclaration, } from "./live-manifest.js";
2
2
  export { validateCandidateArtifact, } from "./candidate-validator.js";
3
- export { parseArtifactManifestContract, } from "./manifest-contract.js";
4
- export { ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_RESPONSE_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_DEFAULT_BASE_PATH, ArtifactPreviewManager, isArtifactPreviewHttpMethod, } from "./preview-runtime.js";
3
+ export { ARTIFACT_MANIFEST_PATH, parseArtifactManifestContract, } from "./manifest-contract.js";
4
+ export { ARTIFACT_PREVIEW_BASE_PATH, ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_RESPONSE_BODY_LIMIT_BYTES, ARTIFACT_PREVIEW_DEFAULT_BASE_PATH, ArtifactPreviewManager, isArtifactPreviewHttpMethod, } from "./preview-runtime.js";
5
5
  export { ARTIFACT_PREVIEW_CSP, readArtifactStaticPreview, rewriteArtifactPreviewResourceUrls, } from "./static-preview.js";
6
6
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,23 @@
1
+ import type { ArtifactManifest, ArtifactProjection } from "@tutti/shared/schemas/api";
2
+ import { type ValidatedArtifactRuntime } from "./manifest-contract.js";
3
+ type ArtifactInvalidProjection = Extract<ArtifactProjection, {
4
+ status: "invalid";
5
+ }>;
6
+ export type LiveArtifactDeclaration = {
7
+ status: "not_declared";
8
+ projection: Extract<ArtifactProjection, {
9
+ status: "not_declared";
10
+ }>;
11
+ } | {
12
+ status: "invalid";
13
+ projection: ArtifactInvalidProjection;
14
+ } | {
15
+ status: "declared";
16
+ manifest: ArtifactManifest;
17
+ runtime: ValidatedArtifactRuntime;
18
+ };
19
+ export declare function readLiveArtifactDeclaration(options: {
20
+ workspaceRoot: string;
21
+ }): LiveArtifactDeclaration;
22
+ export {};
23
+ //# sourceMappingURL=live-manifest.d.ts.map
@@ -0,0 +1,115 @@
1
+ import { lstatSync, readFileSync, realpathSync } from "node:fs";
2
+ import { isAbsolute, join, relative, resolve } from "node:path";
3
+ import { ARTIFACT_MANIFEST_PATH, MAX_ARTIFACT_MANIFEST_BYTES, parseArtifactManifestContract, } from "./manifest-contract.js";
4
+ function truncateMessage(message) {
5
+ const normalized = message.replace(/\s+/gu, " ").trim();
6
+ return normalized.length <= 220 ? normalized : `${normalized.slice(0, 217)}...`;
7
+ }
8
+ function invalidArtifact(code, message) {
9
+ return {
10
+ status: "invalid",
11
+ projection: {
12
+ status: "invalid",
13
+ error: {
14
+ code,
15
+ message: truncateMessage(message),
16
+ },
17
+ },
18
+ };
19
+ }
20
+ function isContainedPath(root, target) {
21
+ const relativePath = relative(root, target);
22
+ return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
23
+ }
24
+ function requireContainedPath(options) {
25
+ try {
26
+ const target = realpathSync.native(resolve(options.workspaceRealPath, options.repoPath));
27
+ const stat = lstatSync(target);
28
+ const expectedKind = options.kind === "file" ? stat.isFile() : stat.isDirectory();
29
+ if (!isContainedPath(options.workspaceRealPath, target) || !expectedKind) {
30
+ return invalidArtifact("invalid_path", `${options.label} is not a contained ${options.kind}`);
31
+ }
32
+ return null;
33
+ }
34
+ catch {
35
+ return invalidArtifact("not_found", `${options.label} does not exist`);
36
+ }
37
+ }
38
+ export function readLiveArtifactDeclaration(options) {
39
+ let workspaceRealPath;
40
+ try {
41
+ workspaceRealPath = realpathSync.native(options.workspaceRoot);
42
+ }
43
+ catch {
44
+ return invalidArtifact("workspace_unavailable", "Project workspace is unavailable");
45
+ }
46
+ const manifestPath = join(workspaceRealPath, ARTIFACT_MANIFEST_PATH);
47
+ let manifestStat;
48
+ try {
49
+ manifestStat = lstatSync(manifestPath);
50
+ }
51
+ catch (error) {
52
+ if (error.code === "ENOENT") {
53
+ return {
54
+ status: "not_declared",
55
+ projection: { status: "not_declared" },
56
+ };
57
+ }
58
+ return invalidArtifact("manifest_unavailable", "Artifact manifest cannot be read");
59
+ }
60
+ if (!manifestStat.isFile()) {
61
+ return invalidArtifact("invalid_manifest", "Artifact manifest path is not a regular file");
62
+ }
63
+ if (manifestStat.size > MAX_ARTIFACT_MANIFEST_BYTES) {
64
+ return invalidArtifact("too_large", "Artifact manifest is too large");
65
+ }
66
+ let manifestContent;
67
+ try {
68
+ manifestContent = readFileSync(manifestPath);
69
+ }
70
+ catch {
71
+ return invalidArtifact("manifest_unavailable", "Artifact manifest cannot be read");
72
+ }
73
+ const parsed = parseArtifactManifestContract(manifestContent);
74
+ if (!parsed.ok) {
75
+ return invalidArtifact(parsed.error.code, parsed.error.message);
76
+ }
77
+ const runtime = parsed.declaration.runtime;
78
+ if (runtime.kind === "static") {
79
+ const rootError = requireContainedPath({
80
+ workspaceRealPath,
81
+ repoPath: runtime.root_path,
82
+ kind: "directory",
83
+ label: "Static artifact root",
84
+ });
85
+ if (rootError !== null) {
86
+ return rootError;
87
+ }
88
+ const entryError = requireContainedPath({
89
+ workspaceRealPath,
90
+ repoPath: runtime.entry_repo_path,
91
+ kind: "file",
92
+ label: "Static artifact entry",
93
+ });
94
+ if (entryError !== null) {
95
+ return entryError;
96
+ }
97
+ }
98
+ else {
99
+ const cwdError = requireContainedPath({
100
+ workspaceRealPath,
101
+ repoPath: runtime.cwd_path,
102
+ kind: "directory",
103
+ label: "Server artifact cwd",
104
+ });
105
+ if (cwdError !== null) {
106
+ return cwdError;
107
+ }
108
+ }
109
+ return {
110
+ status: "declared",
111
+ manifest: parsed.declaration.manifest,
112
+ runtime,
113
+ };
114
+ }
115
+ //# sourceMappingURL=live-manifest.js.map
@@ -1,21 +1,7 @@
1
1
  import type { ChildProcessWithoutNullStreams } from "node:child_process";
2
- import type { ArtifactProjection, GetArtifactsResponse, HeartbeatArtifactPreviewDisposition, StartArtifactPreviewDisposition, StopArtifactPreviewDisposition } from "@tutti/shared/schemas/api";
2
+ import type { GetArtifactsResponse } from "@tutti/shared/schemas/api";
3
3
  import { type ArtifactPreviewProcessPlan, type ArtifactProcessRuntime } from "./process-boundary.js";
4
- export type ArtifactPreviewCommandResult = {
5
- artifact: ArtifactProjection;
6
- };
7
- export type ArtifactPreviewStartResult = {
8
- disposition: StartArtifactPreviewDisposition;
9
- result: ArtifactPreviewCommandResult;
10
- };
11
- export type ArtifactPreviewHeartbeatResult = {
12
- disposition: HeartbeatArtifactPreviewDisposition;
13
- result: ArtifactPreviewCommandResult;
14
- };
15
- export type ArtifactPreviewStopResult = {
16
- disposition: StopArtifactPreviewDisposition;
17
- result: ArtifactPreviewCommandResult;
18
- };
4
+ import { type ArtifactStaticPreviewFile } from "./static-preview.js";
19
5
  export type ArtifactServerPreviewResponse = {
20
6
  status_code: number;
21
7
  headers: Record<string, string>;
@@ -23,7 +9,6 @@ export type ArtifactServerPreviewResponse = {
23
9
  };
24
10
  export type ArtifactPreviewHttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS";
25
11
  export type ArtifactPreviewManagerOptions = {
26
- now?: () => Date;
27
12
  baseEnv?: NodeJS.ProcessEnv;
28
13
  spawnProcess?: (plan: ArtifactPreviewProcessPlan) => ChildProcessWithoutNullStreams;
29
14
  terminateProcess?: (child: ChildProcessWithoutNullStreams) => Promise<void>;
@@ -43,11 +28,15 @@ export type ArtifactPreviewManagerOptions = {
43
28
  responseBodyLimitBytes?: number;
44
29
  idleStopTtlMs?: number;
45
30
  };
31
+ export type ArtifactPreviewLeaseOptions = {
32
+ viewerId: string;
33
+ workspaceRoot: string;
34
+ };
46
35
  export declare const ARTIFACT_PREVIEW_REQUEST_BODY_LIMIT_BYTES: number;
47
36
  export declare const ARTIFACT_PREVIEW_RESPONSE_BODY_LIMIT_BYTES: number;
37
+ export declare const ARTIFACT_PREVIEW_BASE_PATH: "/artifacts/preview";
48
38
  export declare function isArtifactPreviewHttpMethod(method: string): method is ArtifactPreviewHttpMethod;
49
39
  export declare class ArtifactPreviewManager {
50
- private readonly now;
51
40
  private readonly baseEnv;
52
41
  private readonly spawnProcess;
53
42
  private readonly terminateProcess;
@@ -67,25 +56,26 @@ export declare class ArtifactPreviewManager {
67
56
  private readonly responseBodyLimitBytes;
68
57
  private readonly idleStopTtlMs;
69
58
  private readonly pendingTasks;
59
+ private readonly leases;
60
+ private leaseSweepTimer;
70
61
  private state;
71
- private nextInstanceSequence;
62
+ private declaration;
63
+ private lastProjection;
64
+ private workspaceUpdating;
65
+ private nextGenerationSequence;
72
66
  constructor(options?: ArtifactPreviewManagerOptions);
73
- readProjection(options: {
74
- workspaceRoot: string;
67
+ readProjection(options?: {
68
+ workspaceRoot?: string;
75
69
  previewBasePath?: string;
76
70
  }): GetArtifactsResponse;
77
- start(options: {
71
+ initializeWorkspace(options: {
78
72
  workspaceRoot: string;
79
- previewBasePath?: string;
80
- }): ArtifactPreviewStartResult;
81
- heartbeat(options: {
73
+ }): void;
74
+ renewLease(options: ArtifactPreviewLeaseOptions): void;
75
+ beginWorkspaceUpdate(): void;
76
+ settleWorkspaceUpdate(options: {
82
77
  workspaceRoot: string;
83
- previewBasePath?: string;
84
- }): ArtifactPreviewHeartbeatResult;
85
- stop(options: {
86
- workspaceRoot: string;
87
- previewBasePath?: string;
88
- }): ArtifactPreviewStopResult;
78
+ }): void;
89
79
  fetchServerPreview(options: {
90
80
  workspaceRoot: string;
91
81
  path: string | undefined;
@@ -94,22 +84,30 @@ export declare class ArtifactPreviewManager {
94
84
  headers?: Record<string, string | undefined>;
95
85
  body?: Buffer;
96
86
  }): Promise<ArtifactServerPreviewResponse>;
97
- sweepIdle(): void;
87
+ readStaticPreview(options: {
88
+ workspaceRoot: string;
89
+ path: string | undefined;
90
+ query?: string;
91
+ }): ArtifactStaticPreviewFile;
98
92
  stopAll(): Promise<void>;
93
+ private currentProjection;
94
+ private ensurePreview;
95
+ private refreshDeclaration;
96
+ private applyDeclaration;
97
+ private createIdleState;
99
98
  private startProcess;
100
99
  private launchProcess;
101
100
  private attachProcessListeners;
102
101
  private waitUntilReady;
103
102
  private probeReadyEntry;
104
- private recordHeartbeat;
105
- private scheduleIdleSweep;
106
- private stopStaleRuntime;
103
+ private pruneExpiredLeases;
104
+ private scheduleLeaseSweep;
107
105
  private stopCurrentProcess;
108
106
  private failState;
109
107
  private disposeStateProcess;
110
108
  private cleanupStateRuntime;
111
109
  private trackTask;
112
- private nextInstanceId;
110
+ private nextGeneration;
113
111
  }
114
112
  export type { ArtifactPreviewProcessPlan } from "./process-boundary.js";
115
113
  export declare const ARTIFACT_PREVIEW_DEFAULT_BASE_PATH: "/artifacts/preview";