@xfey/tutti 0.1.119 → 0.1.120

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 (110) hide show
  1. package/dist/approvals/decision-command.d.ts +2 -2
  2. package/dist/collaboration-state/clarification-snapshot.d.ts +9 -9
  3. package/dist/collaboration-state/task-compile-context.d.ts +1 -1
  4. package/dist/control-plane/workflows/openai.d.ts +3 -4
  5. package/dist/control-plane/workflows/openai.js +3 -2
  6. package/dist/providers/openai/app-server/invocation-coordinator.d.ts +1 -0
  7. package/dist/providers/openai/app-server/invocation-coordinator.js +4 -1
  8. package/dist/providers/openai/app-server/invocation-deadline.d.ts +3 -0
  9. package/dist/providers/openai/app-server/invocation-deadline.js +15 -0
  10. package/dist/providers/openai/app-server/read-only-procedure.d.ts +4 -2
  11. package/dist/providers/openai/app-server/read-only-procedure.js +16 -0
  12. package/dist/providers/openai/app-server/workspace-write-run.d.ts +4 -2
  13. package/dist/providers/openai/app-server/workspace-write-run.js +16 -0
  14. package/dist/providers/openai/chat-assistant.d.ts +3 -4
  15. package/dist/providers/openai/chat-assistant.js +2 -1
  16. package/dist/providers/openai/execution-access.d.ts +17 -0
  17. package/dist/providers/openai/execution-access.js +13 -0
  18. package/dist/providers/openai/sdk-procedure-runner.d.ts +2 -2
  19. package/dist/providers/openai/sdk-procedure-runner.js +17 -0
  20. package/dist/run-pipeline/artifact-applicability.d.ts +3 -4
  21. package/dist/run-pipeline/artifact-applicability.js +2 -1
  22. package/dist/run-pipeline/openai.d.ts +3 -4
  23. package/dist/run-pipeline/openai.js +3 -2
  24. package/dist/run-pipeline/task-run-invocation.js +6 -12
  25. package/dist/server-shell/cli/cli.js +3 -0
  26. package/dist/server-shell/cli/host-lifecycle.js +1 -0
  27. package/dist/server-shell/cli/host-server-runtime.d.ts +1 -0
  28. package/dist/server-shell/cli/host-server-runtime.js +83 -34
  29. package/dist/server-shell/cli/launch-command.js +1 -1
  30. package/dist/server-shell/cli/launch.d.ts +7 -3
  31. package/dist/server-shell/cli/launch.js +23 -4
  32. package/dist/server-shell/cli/machine-local.d.ts +9 -0
  33. package/dist/server-shell/cli/machine-local.js +60 -6
  34. package/dist/server-shell/cli/project-resolver.d.ts +2 -2
  35. package/dist/server-shell/cli/project-resolver.js +6 -3
  36. package/dist/server-shell/cli/relay-registration.js +38 -1
  37. package/dist/server-shell/http/routes/local-control.d.ts +1 -1
  38. package/dist/server-shell/http/routes/local-control.js +2 -46
  39. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +47 -137
  40. package/dist/server-shell/http/routes/project-api/openapi.d.ts +47 -137
  41. package/dist/server-shell/http/routes/project-api/schemas.d.ts +25 -136
  42. package/dist/server-shell/http/routes/project-api/schemas.js +4 -58
  43. package/dist/server-shell/http/routes/project-api/types.d.ts +4 -0
  44. package/dist/server-shell/local-console/managed-project-routes.d.ts +9 -0
  45. package/dist/server-shell/local-console/managed-project-routes.js +120 -0
  46. package/dist/server-shell/local-console/managed-project-service.d.ts +151 -0
  47. package/dist/server-shell/local-console/managed-project-service.js +163 -0
  48. package/dist/server-shell/local-console/operation-coordinator.d.ts +1 -1
  49. package/dist/server-shell/local-console/package-update-hosts.js +2 -1
  50. package/dist/server-shell/local-console/project-service.d.ts +5 -1
  51. package/dist/server-shell/local-console/project-service.js +63 -14
  52. package/dist/server-shell/local-console/server.js +22 -2
  53. package/dist/server-shell/managed-access/execution-binding.d.ts +9 -0
  54. package/dist/server-shell/managed-access/execution-binding.js +22 -0
  55. package/dist/server-shell/managed-access/existing-project.d.ts +15 -0
  56. package/dist/server-shell/managed-access/existing-project.js +41 -0
  57. package/dist/server-shell/managed-access/model-control-client.d.ts +66 -0
  58. package/dist/server-shell/managed-access/model-control-client.js +114 -0
  59. package/dist/server-shell/managed-access/private-store.d.ts +19 -0
  60. package/dist/server-shell/managed-access/private-store.js +275 -0
  61. package/dist/server-shell/managed-access/project-provider.d.ts +18 -0
  62. package/dist/server-shell/managed-access/project-provider.js +30 -0
  63. package/dist/server-shell/managed-access/records.d.ts +108 -0
  64. package/dist/server-shell/managed-access/records.js +73 -0
  65. package/dist/server-shell/managed-access/runners.d.ts +11 -0
  66. package/dist/server-shell/managed-access/runners.js +103 -0
  67. package/dist/server-shell/managed-access/runtime-records.d.ts +110 -0
  68. package/dist/server-shell/managed-access/runtime-records.js +65 -0
  69. package/dist/server-shell/managed-access/runtime.d.ts +48 -0
  70. package/dist/server-shell/managed-access/runtime.js +301 -0
  71. package/dist/server-shell/managed-access/state.d.ts +54 -0
  72. package/dist/server-shell/managed-access/state.js +287 -0
  73. package/dist/server-shell/managed-access/transport.d.ts +48 -0
  74. package/dist/server-shell/managed-access/transport.js +38 -0
  75. package/dist/server-shell/managed-access/workspace.d.ts +10 -0
  76. package/dist/server-shell/managed-access/workspace.js +69 -0
  77. package/dist/server-shell/smoke/e2e-client.d.ts +8 -3
  78. package/dist/server-shell/smoke/e2e-client.js +11 -11
  79. package/node_modules/@tutti/relay-client/dist/host-control.d.ts +4 -1
  80. package/node_modules/@tutti/relay-client/dist/host-control.js +26 -3
  81. package/node_modules/@tutti/relay-client/dist/index.d.ts +1 -0
  82. package/node_modules/@tutti/relay-client/dist/index.js +1 -0
  83. package/node_modules/@tutti/relay-client/dist/project-authority.d.ts +75 -0
  84. package/node_modules/@tutti/relay-client/dist/project-authority.js +140 -0
  85. package/node_modules/@tutti/shared/dist/ids/index.d.ts +7 -0
  86. package/node_modules/@tutti/shared/dist/ids/index.js +7 -0
  87. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -0
  88. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -0
  89. package/node_modules/@tutti/shared/dist/schemas/api/managed-account.d.ts +35 -0
  90. package/node_modules/@tutti/shared/dist/schemas/api/managed-account.js +41 -0
  91. package/node_modules/@tutti/shared/dist/schemas/api/provider-config.d.ts +8 -4
  92. package/node_modules/@tutti/shared/dist/schemas/api/provider-config.js +6 -0
  93. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +4 -0
  94. package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.d.ts +28 -0
  95. package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.js +42 -0
  96. package/node_modules/@tutti/shared/dist/schemas/internal/index.d.ts +5 -0
  97. package/node_modules/@tutti/shared/dist/schemas/internal/index.js +5 -0
  98. package/node_modules/@tutti/shared/dist/schemas/internal/managed-account.d.ts +12 -0
  99. package/node_modules/@tutti/shared/dist/schemas/internal/managed-account.js +13 -0
  100. package/node_modules/@tutti/shared/dist/schemas/internal/managed-models.d.ts +17 -0
  101. package/node_modules/@tutti/shared/dist/schemas/internal/managed-models.js +17 -0
  102. package/node_modules/@tutti/shared/dist/schemas/internal/managed-runtime.d.ts +116 -0
  103. package/node_modules/@tutti/shared/dist/schemas/internal/managed-runtime.js +87 -0
  104. package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.d.ts +120 -0
  105. package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.js +110 -0
  106. package/package.json +1 -1
  107. package/web/assets/{homepage-motion-scene-B6lCCHBq.js → homepage-motion-scene-DyMFsYmT.js} +1 -1
  108. package/web/assets/index-DSlecDXC.js +70 -0
  109. package/web/index.html +1 -1
  110. package/web/assets/index-DepsYMLl.js +0 -70
@@ -0,0 +1,54 @@
1
+ import { type HostDelegationProjection, type PrepareProjectAuthority, type ManagedProjectBootstrap, type NativeCreationProof } from "@tutti/shared/schemas/internal";
2
+ import { type CreationJournal, type Installation } from "./records.js";
3
+ export declare function managedRelayOrigin(input: string): string;
4
+ export declare function findNativeManagedProject(options: {
5
+ tuttiHome: string;
6
+ relayUrl: string;
7
+ projectId: string;
8
+ }): {
9
+ state: NativeManagedState;
10
+ journal: CreationJournal;
11
+ } | null;
12
+ export declare class NativeManagedState {
13
+ readonly origin: string;
14
+ private readonly files;
15
+ constructor(options: {
16
+ tuttiHome: string;
17
+ relayUrl: string;
18
+ });
19
+ installation(now?: Date): Installation;
20
+ createDraft(options: {
21
+ workspaceRoot: string;
22
+ displayName: string;
23
+ description?: string;
24
+ surface: "standalone" | "feishu";
25
+ tier: "light" | "high" | "max";
26
+ requestRef?: string;
27
+ projectId?: string;
28
+ existing?: {
29
+ relayProjectRef: string;
30
+ hostSecret: string;
31
+ previousRequestRef?: string;
32
+ };
33
+ now?: Date;
34
+ }): CreationJournal;
35
+ read(requestRef: string): CreationJournal;
36
+ attachProject(requestRef: string, now?: Date): CreationJournal;
37
+ project(projectId: string): CreationJournal;
38
+ preparationBody(requestRef: string): PrepareProjectAuthority;
39
+ proof(requestRef: string): NativeCreationProof;
40
+ prepared(requestRef: string, receipt: {
41
+ creation_ref: string;
42
+ project_id: string;
43
+ installation_ref: string;
44
+ surface: string;
45
+ tier: string;
46
+ expires_at: string;
47
+ }, now?: Date): CreationJournal;
48
+ validation(requestRef: string, outcome: "validated" | "validation_unknown" | "validation_failed"): CreationJournal;
49
+ bootstrap(requestRef: string, now?: Date): ManagedProjectBootstrap;
50
+ registered(requestRef: string, authority: HostDelegationProjection): CreationJournal;
51
+ private update;
52
+ private journalName;
53
+ }
54
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1,287 @@
1
+ import { createHash, randomBytes } from "node:crypto";
2
+ import { existsSync, realpathSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { Value } from "@sinclair/typebox/value";
5
+ import { createIdempotencyKey, createPrefixedId, createProjectId, ID_PREFIXES, isPrefixedId, } from "@tutti/shared/ids";
6
+ import { HostDelegationProjectionSchema, PROJECT_TRIAL_POLICY, } from "@tutti/shared/schemas/internal";
7
+ import { ensureManagedPrivateRoot, isWithin, ManagedLocalStateError, ManagedPrivateStore, } from "./private-store.js";
8
+ import { parseInstallation, parseJournal, NativeProjectPointerSchema, } from "./records.js";
9
+ const DRAFT_TTL_MS = 10 * 60_000;
10
+ const secret = () => randomBytes(32).toString("base64url");
11
+ const hash = (value) => `sha256:${createHash("sha256").update(value).digest("hex")}`;
12
+ export function managedRelayOrigin(input) {
13
+ try {
14
+ const url = new URL(input);
15
+ if (url.username ||
16
+ url.password ||
17
+ url.search ||
18
+ url.hash ||
19
+ url.pathname !== "/" ||
20
+ !(url.protocol === "https:" ||
21
+ (url.protocol === "http:" && ["127.0.0.1", "[::1]"].includes(url.hostname))))
22
+ throw new Error();
23
+ return url.origin;
24
+ }
25
+ catch {
26
+ throw new ManagedLocalStateError("managed_relay_origin_invalid");
27
+ }
28
+ }
29
+ function originDirectory(tuttiHome, relayUrl) {
30
+ return join(tuttiHome, "credentials", "managed", createHash("sha256").update(managedRelayOrigin(relayUrl)).digest("hex"));
31
+ }
32
+ // BYOK reads do not create a managed installation, journal or mutex.
33
+ export function findNativeManagedProject(options) {
34
+ if (!isPrefixedId(options.projectId, ID_PREFIXES.project))
35
+ throw new ManagedLocalStateError("managed_project_id_invalid");
36
+ let origin;
37
+ try {
38
+ origin = new URL(options.relayUrl).origin;
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ if (!existsSync(join(originDirectory(options.tuttiHome, origin), `project-${options.projectId}.json`)))
44
+ return null;
45
+ const state = new NativeManagedState(options);
46
+ return { state, journal: state.project(options.projectId) };
47
+ }
48
+ export class NativeManagedState {
49
+ origin;
50
+ files;
51
+ constructor(options) {
52
+ this.origin = managedRelayOrigin(options.relayUrl);
53
+ this.files = new ManagedPrivateStore(ensureManagedPrivateRoot(options.tuttiHome, createHash("sha256").update(this.origin).digest("hex")));
54
+ }
55
+ installation(now = new Date()) {
56
+ return this.files.transaction(() => {
57
+ const saved = this.files.read("installation.json");
58
+ if (saved !== null)
59
+ return parseInstallation(saved, this.origin);
60
+ const value = {
61
+ version: 1,
62
+ relay_origin: this.origin,
63
+ installation_ref: createPrefixedId(ID_PREFIXES.hostInstallation),
64
+ secret: secret(),
65
+ created_at: now.toISOString(),
66
+ };
67
+ this.files.write("installation.json", value, true);
68
+ return parseInstallation(this.files.read("installation.json"), this.origin);
69
+ });
70
+ }
71
+ createDraft(options) {
72
+ const now = options.now ?? new Date();
73
+ let workspace;
74
+ try {
75
+ workspace = realpathSync(options.workspaceRoot);
76
+ }
77
+ catch {
78
+ throw new ManagedLocalStateError("managed_workspace_unavailable");
79
+ }
80
+ if (isWithin(workspace, this.files.root) || isWithin(this.files.root, workspace))
81
+ throw new ManagedLocalStateError("managed_state_in_workspace");
82
+ const installation = this.installation(now);
83
+ const requestRef = options.requestRef ?? createIdempotencyKey();
84
+ const name = this.journalName(requestRef);
85
+ return this.files.transaction(() => {
86
+ const saved = this.files.read(name);
87
+ if (saved !== null) {
88
+ const record = parseJournal(saved, this.origin, requestRef);
89
+ if (record.workspace_root !== workspace ||
90
+ record.display_name !== options.displayName ||
91
+ record.description !== options.description ||
92
+ record.surface !== options.surface ||
93
+ record.tier !== options.tier ||
94
+ (options.projectId !== undefined && record.project_id !== options.projectId) ||
95
+ record.existing_relay_project_ref !== options.existing?.relayProjectRef ||
96
+ record.previous_request_ref !== options.existing?.previousRequestRef ||
97
+ (options.existing !== undefined &&
98
+ record.host_registration_secret !== options.existing.hostSecret) ||
99
+ record.installation_ref !== installation.installation_ref)
100
+ throw new ManagedLocalStateError("managed_creation_mismatch");
101
+ return record;
102
+ }
103
+ const record = parseJournal({
104
+ version: 1,
105
+ request_ref: requestRef,
106
+ project_id: options.projectId ?? createProjectId(),
107
+ relay_origin: this.origin,
108
+ installation_ref: installation.installation_ref,
109
+ workspace_root: workspace,
110
+ display_name: options.displayName,
111
+ ...(options.description === undefined ? {} : { description: options.description }),
112
+ surface: options.surface,
113
+ tier: options.tier,
114
+ ...(options.existing === undefined
115
+ ? {}
116
+ : {
117
+ existing_relay_project_ref: options.existing.relayProjectRef,
118
+ ...(options.existing.previousRequestRef === undefined
119
+ ? {}
120
+ : { previous_request_ref: options.existing.previousRequestRef }),
121
+ }),
122
+ handoff_token: secret(),
123
+ host_registration_secret: options.existing?.hostSecret ?? secret(),
124
+ created_at: now.toISOString(),
125
+ expires_at: new Date(now.getTime() + DRAFT_TTL_MS).toISOString(),
126
+ state: "awaiting_authorization",
127
+ }, this.origin, requestRef);
128
+ this.files.write(name, record, true);
129
+ return record;
130
+ });
131
+ }
132
+ read(requestRef) {
133
+ return this.files.transaction(() => parseJournal(this.files.read(this.journalName(requestRef)), this.origin, requestRef));
134
+ }
135
+ attachProject(requestRef, now = new Date()) {
136
+ this.bootstrap(requestRef, now);
137
+ return this.files.transaction(() => {
138
+ const row = parseJournal(this.files.read(this.journalName(requestRef)), this.origin, requestRef);
139
+ const name = `project-${row.project_id}.json`;
140
+ const old = this.files.read(name);
141
+ if (old !== null &&
142
+ (!Value.Check(NativeProjectPointerSchema, old) ||
143
+ old.project_id !== row.project_id ||
144
+ (old.request_ref !== requestRef && old.request_ref !== row.previous_request_ref)))
145
+ throw new ManagedLocalStateError("managed_project_binding_conflict");
146
+ if (old === null ||
147
+ (Value.Check(NativeProjectPointerSchema, old) && old.request_ref !== requestRef))
148
+ this.files.write(name, { version: 1, project_id: row.project_id, request_ref: requestRef }, old === null);
149
+ return row;
150
+ });
151
+ }
152
+ project(projectId) {
153
+ if (!isPrefixedId(projectId, ID_PREFIXES.project))
154
+ throw new ManagedLocalStateError("managed_project_id_invalid");
155
+ return this.files.transaction(() => {
156
+ const pointer = this.files.read(`project-${projectId}.json`);
157
+ if (!Value.Check(NativeProjectPointerSchema, pointer) || pointer.project_id !== projectId)
158
+ throw new ManagedLocalStateError("managed_project_binding_invalid");
159
+ const row = parseJournal(this.files.read(this.journalName(pointer.request_ref)), this.origin, pointer.request_ref);
160
+ if (row.project_id !== projectId || (row.state !== "validated" && row.state !== "registered"))
161
+ throw new ManagedLocalStateError("managed_project_binding_invalid");
162
+ return row;
163
+ });
164
+ }
165
+ preparationBody(requestRef) {
166
+ const row = this.read(requestRef), installation = this.installation();
167
+ if (row.installation_ref !== installation.installation_ref)
168
+ throw new ManagedLocalStateError("managed_installation_changed");
169
+ return {
170
+ request_ref: row.request_ref,
171
+ project_id: row.project_id,
172
+ surface: row.surface,
173
+ tier: row.tier,
174
+ ...(row.existing_relay_project_ref === undefined
175
+ ? {}
176
+ : { existing_relay_project_ref: row.existing_relay_project_ref }),
177
+ installation_ref: row.installation_ref,
178
+ installation_secret_hash: hash(installation.secret),
179
+ handoff_token_hash: hash(row.handoff_token),
180
+ host_registration_secret_hash: hash(row.host_registration_secret),
181
+ trial_consent: PROJECT_TRIAL_POLICY,
182
+ expires_before: row.expires_at,
183
+ };
184
+ }
185
+ proof(requestRef) {
186
+ const row = this.read(requestRef), installation = this.installation();
187
+ if (row.installation_ref !== installation.installation_ref)
188
+ throw new ManagedLocalStateError("managed_installation_changed");
189
+ return {
190
+ request_ref: row.request_ref,
191
+ project_id: row.project_id,
192
+ installation_ref: row.installation_ref,
193
+ installation_secret: installation.secret,
194
+ handoff_token: row.handoff_token,
195
+ host_registration_secret: row.host_registration_secret,
196
+ };
197
+ }
198
+ // Only native, Relay-verified responses may call these transitions. Browser input is not evidence.
199
+ prepared(requestRef, receipt, now = new Date()) {
200
+ return this.update(requestRef, (row) => {
201
+ if (row.project_id !== receipt.project_id ||
202
+ row.installation_ref !== receipt.installation_ref ||
203
+ row.surface !== receipt.surface ||
204
+ row.tier !== receipt.tier ||
205
+ !Number.isFinite(Date.parse(receipt.expires_at)) ||
206
+ Date.parse(receipt.expires_at) > Date.parse(row.expires_at))
207
+ throw new ManagedLocalStateError("managed_preparation_mismatch");
208
+ if (row.state !== "awaiting_authorization") {
209
+ if (row.creation_ref !== receipt.creation_ref)
210
+ throw new ManagedLocalStateError("managed_preparation_mismatch");
211
+ return row;
212
+ }
213
+ if (receipt.expires_at <= now.toISOString())
214
+ throw new ManagedLocalStateError("managed_preparation_expired");
215
+ return {
216
+ ...row,
217
+ state: "prepared",
218
+ creation_ref: receipt.creation_ref,
219
+ expires_at: receipt.expires_at,
220
+ };
221
+ });
222
+ }
223
+ validation(requestRef, outcome) {
224
+ return this.update(requestRef, (row) => {
225
+ if (row.state === "registered" || row.state === "validated")
226
+ return row;
227
+ if (row.state === "validation_failed") {
228
+ if (outcome !== row.state)
229
+ throw new ManagedLocalStateError("managed_validation_conflict");
230
+ return row;
231
+ }
232
+ if (row.state !== "prepared" && row.state !== "validation_unknown")
233
+ throw new ManagedLocalStateError("managed_creation_not_prepared");
234
+ return { ...row, state: outcome };
235
+ });
236
+ }
237
+ bootstrap(requestRef, now = new Date()) {
238
+ const row = this.read(requestRef), installation = this.installation();
239
+ if ((row.state !== "validated" && row.state !== "registered") ||
240
+ (row.state !== "registered" && row.expires_at <= now.toISOString()))
241
+ throw new ManagedLocalStateError("managed_creation_not_validated");
242
+ if (row.installation_ref !== installation.installation_ref)
243
+ throw new ManagedLocalStateError("managed_installation_changed");
244
+ return {
245
+ kind: "managed_project_authority_v2",
246
+ creation_ref: row.creation_ref,
247
+ installation_ref: installation.installation_ref,
248
+ installation_secret: installation.secret,
249
+ handoff_token: row.handoff_token,
250
+ };
251
+ }
252
+ registered(requestRef, authority) {
253
+ return this.update(requestRef, (row) => {
254
+ if (!Value.Check(HostDelegationProjectionSchema, authority) ||
255
+ authority.installation_ref !== row.installation_ref ||
256
+ authority.tier !== row.tier ||
257
+ (row.existing_relay_project_ref !== undefined &&
258
+ authority.relay_project_ref !== row.existing_relay_project_ref))
259
+ throw new ManagedLocalStateError("managed_registration_mismatch");
260
+ if (row.state === "registered") {
261
+ if (row.authority.delegation_ref !== authority.delegation_ref ||
262
+ row.authority.grant_ref !== authority.grant_ref ||
263
+ row.authority.relay_project_ref !== authority.relay_project_ref)
264
+ throw new ManagedLocalStateError("managed_registration_mismatch");
265
+ return row;
266
+ }
267
+ if (row.state !== "validated")
268
+ throw new ManagedLocalStateError("managed_creation_not_validated");
269
+ return { ...row, state: "registered", authority };
270
+ });
271
+ }
272
+ update(requestRef, change) {
273
+ return this.files.transaction(() => {
274
+ const name = this.journalName(requestRef);
275
+ const row = parseJournal(this.files.read(name), this.origin, requestRef);
276
+ const updated = parseJournal(change(row), this.origin, requestRef);
277
+ this.files.write(name, updated);
278
+ return updated;
279
+ });
280
+ }
281
+ journalName(requestRef) {
282
+ if (!isPrefixedId(requestRef, ID_PREFIXES.idempotency))
283
+ throw new ManagedLocalStateError("managed_request_ref_invalid");
284
+ return `${requestRef}.json`;
285
+ }
286
+ }
287
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1,48 @@
1
+ import type { NativeManagedState } from "./state.js";
2
+ export declare function reconcileNativePreparation(options: {
3
+ state: NativeManagedState;
4
+ requestRef: string;
5
+ fetchImpl?: typeof fetch;
6
+ now?: Date;
7
+ signal?: AbortSignal;
8
+ }): Promise<{
9
+ authority?: {
10
+ state: "active";
11
+ delegation_ref: string;
12
+ grant_ref: string;
13
+ installation_ref: string;
14
+ relay_project_ref: string;
15
+ tier: "light" | "high" | "max";
16
+ expires_at: null;
17
+ funding_kind: "trial";
18
+ model_access: "runtime_authority_required" | "not_enabled";
19
+ };
20
+ account_ref: string;
21
+ request_ref: string;
22
+ creation: {
23
+ relay_project_ref?: string;
24
+ state: "revoked" | "expired" | "prepared" | "activated";
25
+ installation_ref: string;
26
+ surface: "standalone" | "feishu";
27
+ tier: "light" | "high" | "max";
28
+ expires_at: string;
29
+ project_id: string;
30
+ creation_ref: string;
31
+ };
32
+ }>;
33
+ export declare function validateNativePreparation(options: {
34
+ state: NativeManagedState;
35
+ requestRef: string;
36
+ fetchImpl?: typeof fetch;
37
+ signal?: AbortSignal;
38
+ }): Promise<{
39
+ state: "failed" | "unknown" | "started" | "succeeded";
40
+ installation_ref: string;
41
+ tier: "light" | "high" | "max";
42
+ reason_code: "validation_started" | "validation_succeeded" | "validation_route_unavailable" | "validation_http_rejected" | "validation_response_invalid" | "validation_transport_unknown" | "validation_interrupted";
43
+ project_id: string;
44
+ creation_ref: string;
45
+ route_ref: string;
46
+ http_status: number | null;
47
+ }>;
48
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1,38 @@
1
+ import { readNativeCreationAuthority, validateNativeCreationAuthority } from "@tutti/relay-client";
2
+ import { ManagedLocalStateError } from "./private-store.js";
3
+ // The browser never supplies a trusted receipt or account identity to the native journal.
4
+ export async function reconcileNativePreparation(options) {
5
+ const result = await readNativeCreationAuthority({
6
+ relayUrl: options.state.origin,
7
+ body: options.state.proof(options.requestRef),
8
+ ...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
9
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
10
+ });
11
+ if (result.creation.state === "expired" || result.creation.state === "revoked")
12
+ throw new ManagedLocalStateError(`managed_preparation_${result.creation.state}`);
13
+ options.state.prepared(options.requestRef, result.creation, options.now ?? new Date());
14
+ if (result.authority !== undefined)
15
+ options.state.registered(options.requestRef, result.authority);
16
+ return result;
17
+ }
18
+ export async function validateNativePreparation(options) {
19
+ const result = await validateNativeCreationAuthority({
20
+ relayUrl: options.state.origin,
21
+ body: options.state.proof(options.requestRef),
22
+ ...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
23
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
24
+ });
25
+ const row = options.state.read(options.requestRef);
26
+ if (!("creation_ref" in row) ||
27
+ row.creation_ref !== result.creation_ref ||
28
+ row.tier !== result.tier)
29
+ throw new ManagedLocalStateError("managed_validation_mismatch");
30
+ if (result.state === "succeeded")
31
+ options.state.validation(options.requestRef, "validated");
32
+ else if (result.state === "unknown" || result.state === "started")
33
+ options.state.validation(options.requestRef, "validation_unknown");
34
+ else
35
+ options.state.validation(options.requestRef, "validation_failed");
36
+ return result;
37
+ }
38
+ //# sourceMappingURL=transport.js.map
@@ -0,0 +1,10 @@
1
+ import type { NativeManagedState } from "./state.js";
2
+ export declare function acquireManagedWorkspaceLease(tuttiHome: string, workspaceRoot: string): () => void;
3
+ export declare function materializeManagedWorkspace(options: {
4
+ state: NativeManagedState;
5
+ requestRef: string;
6
+ tuttiHome: string;
7
+ environment: NodeJS.ProcessEnv;
8
+ now?: () => Date;
9
+ }): Promise<import("../cli/launch.js").LaunchPreparationResult>;
10
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1,69 @@
1
+ import { createHash } from "node:crypto";
2
+ import { realpathSync } from "node:fs";
3
+ import { prepareLaunchProject, resolveLaunchLocalContext } from "../cli/launch.js";
4
+ import { readExistingManagedTarget } from "./existing-project.js";
5
+ import { ensureManagedPrivateRoot, ManagedLocalStateError, ManagedPrivateStore, } from "./private-store.js";
6
+ // A workspace can be selected by different drafts and Relay origins. Serialize by
7
+ // canonical folder in a machine-wide namespace, not by project or creation ID.
8
+ export function acquireManagedWorkspaceLease(tuttiHome, workspaceRoot) {
9
+ const files = new ManagedPrivateStore(ensureManagedPrivateRoot(tuttiHome, createHash("sha256").update("tutti-managed-workspace-locks-v1").digest("hex")));
10
+ return files.acquireExecutionLease(`workspace-${createHash("sha256").update(realpathSync(workspaceRoot)).digest("hex")}`);
11
+ }
12
+ // Native-only materialization after verified validation. Does not start a Host or
13
+ // write a placeholder BYOK credential. Resuming the same draft reuses its identity.
14
+ export async function materializeManagedWorkspace(options) {
15
+ const now = options.now ?? (() => new Date());
16
+ options.state.bootstrap(options.requestRef, now());
17
+ const row = options.state.read(options.requestRef);
18
+ const unlock = acquireManagedWorkspaceLease(options.tuttiHome, row.workspace_root);
19
+ try {
20
+ let workspacePath = row.workspace_root;
21
+ if (row.existing_relay_project_ref !== undefined) {
22
+ const target = readExistingManagedTarget({
23
+ tuttiHome: options.tuttiHome,
24
+ relayOrigin: options.state.origin,
25
+ projectId: row.project_id,
26
+ workspaceRoot: row.workspace_root,
27
+ surface: row.surface,
28
+ requireStopped: row.state !== "registered",
29
+ });
30
+ if (target.relayProjectRef !== row.existing_relay_project_ref ||
31
+ target.hostSecret !== row.host_registration_secret)
32
+ throw new ManagedLocalStateError("managed_existing_project_mismatch");
33
+ workspacePath = target.workspacePath;
34
+ }
35
+ const environment = {
36
+ ...options.environment,
37
+ TUTTI_HOME: options.tuttiHome,
38
+ TUTTI_RELAY_URL: options.state.origin,
39
+ };
40
+ const local = resolveLaunchLocalContext({
41
+ workspacePath,
42
+ env: environment,
43
+ });
44
+ if (realpathSync(local.workspace_root) !== row.workspace_root ||
45
+ (local.existing_identity.kind === "present" &&
46
+ local.existing_identity.project_id !== row.project_id))
47
+ throw new ManagedLocalStateError("managed_workspace_identity_changed");
48
+ // Persist the recovery pointer before Git/binding mutation. A crash never leaves
49
+ // an apparently-BYOK project where the managed creation reservation was lost.
50
+ options.state.attachProject(options.requestRef, now());
51
+ return await prepareLaunchProject({
52
+ workspacePath,
53
+ env: environment,
54
+ yes: true,
55
+ projectDisplayName: row.display_name,
56
+ ...(row.description === undefined ? {} : { projectDescription: row.description }),
57
+ projectSurface: row.surface,
58
+ reservedIdentity: {
59
+ projectId: row.project_id,
60
+ hostRegistrationSecret: row.host_registration_secret,
61
+ },
62
+ now,
63
+ });
64
+ }
65
+ finally {
66
+ unlock();
67
+ }
68
+ }
69
+ //# sourceMappingURL=workspace.js.map
@@ -1,5 +1,10 @@
1
- import { type HostProjectApiClient, type LoginPayload, type LoginResponse, type OpenProjectResponse, type RelayApiClient, type TuttiApiFetch, type AcceptJoinResponse, type JoinEntryProjection, type TuttiApiResponse } from "@tutti/api-client";
1
+ import { type HostProjectApiClient, type EmailLoginChallengeProjection, type LoginResponse, type OpenProjectResponse, type RelayApiClient, type TuttiApiFetch, type AcceptJoinResponse, type JoinEntryProjection, type TuttiApiResponse } from "@tutti/api-client";
2
2
  import type { RelayProjectRef } from "@tutti/shared/ids";
3
+ export type SmokeEmailLoginInput = {
4
+ email: string;
5
+ display_name?: string;
6
+ readCode: (challenge: EmailLoginChallengeProjection) => Promise<string>;
7
+ };
3
8
  export type E2ESmokeLogSink = (line: string) => void;
4
9
  export type E2ESmokeClientOptions = {
5
10
  relayBaseUrl: string;
@@ -20,7 +25,7 @@ export type E2ESmokeSession = {
20
25
  };
21
26
  export type LoginJoinAndOpenWorkspaceInput = {
22
27
  joinToken: string;
23
- login?: Partial<LoginPayload>;
28
+ login: SmokeEmailLoginInput;
24
29
  };
25
30
  export type LoginJoinAndOpenWorkspaceResult = {
26
31
  joinEntry: JoinEntryProjection;
@@ -53,7 +58,7 @@ export declare class E2ESmokeCookieJar {
53
58
  private storeSetCookieHeader;
54
59
  }
55
60
  export declare function createE2ESmokeSession(options: E2ESmokeClientOptions): E2ESmokeSession;
56
- export declare function loginRelaySmokeAccount(session: E2ESmokeSession, input?: Partial<LoginPayload>): Promise<LoginResponse>;
61
+ export declare function loginRelaySmokeAccount(session: E2ESmokeSession, input: SmokeEmailLoginInput): Promise<LoginResponse>;
57
62
  export declare function loginJoinAndOpenWorkspace(session: E2ESmokeSession, input: LoginJoinAndOpenWorkspaceInput): Promise<LoginJoinAndOpenWorkspaceResult>;
58
63
  export declare function pollE2ESmoke<TValue>(options: E2ESmokePollOptions<TValue>): Promise<TValue>;
59
64
  //# sourceMappingURL=e2e-client.d.ts.map
@@ -1,10 +1,6 @@
1
1
  import { setTimeout as sleep } from "node:timers/promises";
2
2
  import { createHostProjectApiClientFromRelay, createRelayApiClientFromRelay, } from "@tutti/api-client";
3
3
  import { serializeRuntimeLogLine } from "@tutti/shared/utils";
4
- const DEFAULT_SMOKE_LOGIN_PAYLOAD = {
5
- email: "tutti-smoke@example.invalid",
6
- display_name: "Tutti Smoke",
7
- };
8
4
  const DEFAULT_POLL_TIMEOUT_MS = 30_000;
9
5
  const DEFAULT_POLL_INTERVAL_MS = 500;
10
6
  export class E2ESmokeFlowError extends Error {
@@ -160,12 +156,6 @@ function emitSmokeLog(session, stage, fields = {}) {
160
156
  now: session.now,
161
157
  }));
162
158
  }
163
- function loginPayload(input) {
164
- return {
165
- ...DEFAULT_SMOKE_LOGIN_PAYLOAD,
166
- ...(input ?? {}),
167
- };
168
- }
169
159
  function readRelayProjectRefFromJoin(joinEntry) {
170
160
  if (joinEntry.relay_project !== undefined) {
171
161
  return joinEntry.relay_project.relay_project_ref;
@@ -216,7 +206,17 @@ export function createE2ESmokeSession(options) {
216
206
  };
217
207
  }
218
208
  export async function loginRelaySmokeAccount(session, input) {
219
- const response = await session.relay.login(loginPayload(input), { now: session.now });
209
+ const start = await session.relay.startEmailLogin({ email: input.email }, { now: session.now });
210
+ if (start.result === undefined)
211
+ throw new Error("Email login challenge was not returned");
212
+ const code = await input.readCode(start.result.challenge);
213
+ const response = await session.relay.verifyEmailLogin({
214
+ challenge_ref: start.result.challenge.challenge_ref,
215
+ code,
216
+ }, { now: session.now });
217
+ if (response.result?.account.profile_complete === false && input.display_name !== undefined) {
218
+ await session.relay.completeAccountProfile({ display_name: input.display_name }, { now: session.now });
219
+ }
220
220
  emitSmokeLog(session, "relay_login", {
221
221
  disposition: response.disposition.kind,
222
222
  account_ref: response.result?.account.account_ref,
@@ -1,7 +1,7 @@
1
1
  import { type HostConnectionRef, type IdempotencyKey, type ProjectId, type RelayProjectRef, type RelayUploadRef } from "@tutti/shared/ids";
2
2
  import type { ProjectSurfaceProfile } from "@tutti/shared/domain";
3
3
  import type { RelayUploadPurpose, Sha256Digest } from "@tutti/shared/schemas/api";
4
- import { type FeishuSetupProjection, type PlatformOutboundHandoffResponse, type PlatformOutboundIntent } from "@tutti/shared/schemas/internal";
4
+ import { type HostDelegationProjection, type ManagedProjectBootstrap, type FeishuSetupProjection, type PlatformOutboundHandoffResponse, type PlatformOutboundIntent } from "@tutti/shared/schemas/internal";
5
5
  export type RelayHostControlFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
6
6
  export type RelayProjectProjection = {
7
7
  relay_project_ref: RelayProjectRef;
@@ -25,6 +25,7 @@ export type JoinTokenProjection = {
25
25
  visibility: "visible_once" | "not_recoverable";
26
26
  };
27
27
  export type RegisterRelayHostConnectionPayload = {
28
+ managed_bootstrap?: ManagedProjectBootstrap;
28
29
  project_identity: {
29
30
  source: "git_config";
30
31
  project_id: ProjectId;
@@ -64,6 +65,7 @@ export type RegisterRelayHostConnectionPayload = {
64
65
  };
65
66
  };
66
67
  export type RegisterRelayHostConnectionResult = {
68
+ managed_authority?: HostDelegationProjection;
67
69
  relay_project: RelayProjectProjection;
68
70
  host_connection: HostConnectionProjection;
69
71
  join_token?: JoinTokenProjection;
@@ -152,6 +154,7 @@ export type DeliverRelayPlatformOutboundOptions = {
152
154
  signal?: AbortSignal;
153
155
  };
154
156
  export type RegisterRelayHostConnectionOptions = {
157
+ managedBootstrap?: ManagedProjectBootstrap;
155
158
  relayUrl: string;
156
159
  projectId: ProjectId;
157
160
  hostRegistrationSecret: string;