@xfey/tutti 0.1.60 → 0.1.62

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 (90) hide show
  1. package/README.md +2 -2
  2. package/dist/collaboration-state/clarification-records.d.ts +0 -9
  3. package/dist/collaboration-state/clarification-records.js +0 -57
  4. package/dist/collaboration-state/index.d.ts +6 -5
  5. package/dist/collaboration-state/index.js +5 -4
  6. package/dist/collaboration-state/messages.d.ts +4 -6
  7. package/dist/collaboration-state/messages.js +22 -29
  8. package/dist/collaboration-state/scratchpad-source-state.d.ts +4 -12
  9. package/dist/collaboration-state/scratchpad-source-state.js +24 -58
  10. package/dist/collaboration-state/scratchpad.d.ts +1 -2
  11. package/dist/collaboration-state/scratchpad.js +0 -59
  12. package/dist/collaboration-state/storage-types.d.ts +25 -5
  13. package/dist/collaboration-state/task-compile-context.d.ts +40 -0
  14. package/dist/collaboration-state/task-compile-context.js +273 -0
  15. package/dist/collaboration-state/types.d.ts +39 -9
  16. package/dist/control-plane/clarification-commands.d.ts +0 -1
  17. package/dist/control-plane/clarification-commands.js +0 -1
  18. package/dist/control-plane/index.js +16 -3
  19. package/dist/control-plane/project-context-bootstrap.d.ts +4 -4
  20. package/dist/control-plane/project-context-bootstrap.js +7 -12
  21. package/dist/control-plane/scratchpad-auto-refresh.js +12 -1
  22. package/dist/control-plane/scratchpad-refresh-start.js +77 -94
  23. package/dist/control-plane/scratchpad-refresh.js +3 -1
  24. package/dist/control-plane/scratchpad-source-messages.d.ts +15 -7
  25. package/dist/control-plane/scratchpad-source-messages.js +53 -69
  26. package/dist/control-plane/task-compile-clarification-context.d.ts +9 -0
  27. package/dist/control-plane/task-compile-clarification-context.js +70 -0
  28. package/dist/control-plane/task-compile-continuation.js +28 -56
  29. package/dist/control-plane/task-compile-output.js +50 -14
  30. package/dist/control-plane/task-compile-start.js +119 -55
  31. package/dist/control-plane/workflows/openai.d.ts +2 -1
  32. package/dist/control-plane/workflows/openai.js +13 -27
  33. package/dist/control-plane/workflows/types.d.ts +27 -17
  34. package/dist/server-shell/cli/args.d.ts +4 -0
  35. package/dist/server-shell/cli/args.js +11 -0
  36. package/dist/server-shell/cli/cli.js +35 -11
  37. package/dist/server-shell/cli/host-server-runtime.d.ts +2 -0
  38. package/dist/server-shell/cli/host-server-runtime.js +12 -5
  39. package/dist/server-shell/cli/local-control-client.d.ts +5 -1
  40. package/dist/server-shell/cli/local-control-client.js +19 -0
  41. package/dist/server-shell/desktop-integration/manager.d.ts +2 -0
  42. package/dist/server-shell/desktop-integration/manager.js +19 -31
  43. package/dist/server-shell/http/routes/local-control.d.ts +2 -0
  44. package/dist/server-shell/http/routes/local-control.js +24 -0
  45. package/dist/server-shell/local-console/invocation-context.js +10 -1
  46. package/dist/server-shell/local-console/operation-coordinator.d.ts +16 -0
  47. package/dist/server-shell/local-console/operation-coordinator.js +50 -0
  48. package/dist/server-shell/local-console/package-update-completion.d.ts +25 -0
  49. package/dist/server-shell/local-console/package-update-completion.js +91 -0
  50. package/dist/server-shell/local-console/package-update-hosts.d.ts +17 -0
  51. package/dist/server-shell/local-console/package-update-hosts.js +92 -0
  52. package/dist/server-shell/local-console/package-update-lock.d.ts +46 -0
  53. package/dist/server-shell/local-console/package-update-lock.js +223 -0
  54. package/dist/server-shell/local-console/package-update-log.d.ts +20 -0
  55. package/dist/server-shell/local-console/package-update-log.js +75 -0
  56. package/dist/server-shell/local-console/package-update-process.d.ts +22 -0
  57. package/dist/server-shell/local-console/package-update-process.js +252 -0
  58. package/dist/server-shell/local-console/package-update-record.d.ts +53 -0
  59. package/dist/server-shell/local-console/package-update-record.js +180 -0
  60. package/dist/server-shell/local-console/package-update-recovery.d.ts +34 -0
  61. package/dist/server-shell/local-console/package-update-recovery.js +140 -0
  62. package/dist/server-shell/local-console/package-update-service.d.ts +72 -0
  63. package/dist/server-shell/local-console/package-update-service.js +400 -0
  64. package/dist/server-shell/local-console/package-update-worker.d.ts +55 -0
  65. package/dist/server-shell/local-console/package-update-worker.js +297 -0
  66. package/dist/server-shell/local-console/project-service.d.ts +2 -0
  67. package/dist/server-shell/local-console/project-service.js +16 -12
  68. package/dist/server-shell/local-console/server.d.ts +9 -0
  69. package/dist/server-shell/local-console/server.js +92 -4
  70. package/dist/server-shell/package-installation/command.d.ts +36 -0
  71. package/dist/server-shell/package-installation/command.js +162 -0
  72. package/dist/server-shell/package-installation/discovery.d.ts +51 -0
  73. package/dist/server-shell/package-installation/discovery.js +137 -0
  74. package/dist/server-shell/package-installation/index.d.ts +5 -0
  75. package/dist/server-shell/package-installation/index.js +5 -0
  76. package/dist/server-shell/package-installation/installation.d.ts +34 -0
  77. package/dist/server-shell/package-installation/installation.js +123 -0
  78. package/dist/server-shell/package-installation/semver.d.ts +9 -0
  79. package/dist/server-shell/package-installation/semver.js +84 -0
  80. package/migrations/0014_scratchpad_task_compile_context.sql +147 -0
  81. package/migrations/README.md +2 -1
  82. package/package.json +1 -1
  83. package/prompts/procedures/README.md +3 -3
  84. package/prompts/procedures/scratchpad-refresh.md +17 -9
  85. package/prompts/procedures/task-compile.md +6 -3
  86. package/prompts/prompt-flow-map.md +21 -14
  87. package/web/assets/{homepage-motion-scene-CY7o4hnR.js → homepage-motion-scene-CRmrkkEH.js} +1 -1
  88. package/web/assets/{index-B08r3x8o.js → index-C9JrplHV.js} +14 -14
  89. package/web/assets/{index-DpabiRgp.css → index-IZKcmU_g.css} +1 -1
  90. package/web/index.html +2 -2
@@ -0,0 +1,400 @@
1
+ import { resolveTuttiHome } from "../../providers/openai/index.js";
2
+ import { inspectMachineProjects, } from "../cli/machine-project-inspector.js";
3
+ import { readHostLocalPackageUpdateReadiness } from "../cli/local-control-client.js";
4
+ import { readCliPackageIdentity, readCliVersion } from "../cli/version.js";
5
+ import { PackageUpdateDiscoveryService, resolveGlobalPackageInstallation, } from "../package-installation/index.js";
6
+ import { LocalConsoleOperationCoordinator, } from "./operation-coordinator.js";
7
+ import { inspectPackageUpdateLock, tryAcquirePackageUpdateLock, } from "./package-update-lock.js";
8
+ import { deletePackageUpdateRuntimeRecord, readPackageUpdateRuntimeRecord, } from "./package-update-record.js";
9
+ const HOST_REASON_PRIORITY = {
10
+ host_identity_unverified: 0,
11
+ host_update_unsupported: 1,
12
+ host_busy: 2,
13
+ };
14
+ export async function inspectPackageUpdateHosts(options) {
15
+ const restoreProjectIds = [];
16
+ const reasons = [];
17
+ await Promise.all(options.projects.map(async (project) => {
18
+ if (project.host.kind === "stopped" && project.endpoint.kind === "missing") {
19
+ return;
20
+ }
21
+ if (project.host.kind !== "running" ||
22
+ !project.host.identity_verified ||
23
+ project.endpoint.kind !== "valid") {
24
+ reasons.push("host_identity_unverified");
25
+ return;
26
+ }
27
+ let readiness;
28
+ try {
29
+ readiness = await options.readReadiness(project);
30
+ }
31
+ catch {
32
+ reasons.push("host_identity_unverified");
33
+ return;
34
+ }
35
+ if (readiness === "busy") {
36
+ reasons.push("host_busy");
37
+ return;
38
+ }
39
+ if (readiness === "unsupported") {
40
+ reasons.push("host_update_unsupported");
41
+ return;
42
+ }
43
+ restoreProjectIds.push(project.project_id);
44
+ }));
45
+ const reason = reasons.sort((left, right) => HOST_REASON_PRIORITY[left] - HOST_REASON_PRIORITY[right])[0];
46
+ return reason === undefined
47
+ ? { status: "ready", restore_project_ids: restoreProjectIds.sort() }
48
+ : { status: "blocked", reason_code: reason };
49
+ }
50
+ function blocked(options) {
51
+ return {
52
+ current_version: options.currentVersion,
53
+ ...(options.latestVersion === undefined ? {} : { latest_version: options.latestVersion }),
54
+ status: "blocked",
55
+ can_update: false,
56
+ reason_code: options.reasonCode,
57
+ };
58
+ }
59
+ function lockBlockedStatus(inspection, currentVersion) {
60
+ return blocked({
61
+ currentVersion,
62
+ reasonCode: inspection.status === "active" ? "update_in_progress" : "update_recovery_required",
63
+ });
64
+ }
65
+ export class LocalConsolePackageUpdateService {
66
+ #tuttiHome;
67
+ #currentVersion;
68
+ #operations;
69
+ #resolveInstallation;
70
+ #discover;
71
+ #inspectProjects;
72
+ #readReadiness;
73
+ #inspectLock;
74
+ #acquireLock;
75
+ #readRecord;
76
+ #clearFailedRecord;
77
+ constructor(options) {
78
+ this.#tuttiHome = options.tuttiHome;
79
+ this.#currentVersion = options.currentVersion ?? readCliVersion();
80
+ this.#operations = options.operations ?? new LocalConsoleOperationCoordinator();
81
+ const env = options.env ?? process.env;
82
+ let installation;
83
+ this.#resolveInstallation =
84
+ options.resolveInstallation ??
85
+ (() => {
86
+ installation ??= resolveGlobalPackageInstallation({
87
+ packageIdentity: readCliPackageIdentity(),
88
+ tuttiHome: this.#tuttiHome,
89
+ defaultTuttiHome: resolveTuttiHome(undefined),
90
+ env,
91
+ });
92
+ return installation;
93
+ });
94
+ const discovery = new PackageUpdateDiscoveryService();
95
+ this.#discover =
96
+ options.discover ?? ((resolution, input) => discovery.discover(resolution, input));
97
+ this.#inspectProjects =
98
+ options.inspectProjects ?? (() => inspectMachineProjects({ tuttiHome: this.#tuttiHome }));
99
+ this.#readReadiness =
100
+ options.readReadiness ??
101
+ (async (project) => {
102
+ if (project.endpoint.kind !== "valid") {
103
+ throw new Error("Host endpoint is unavailable.");
104
+ }
105
+ return await readHostLocalPackageUpdateReadiness({ endpoint: project.endpoint.record });
106
+ });
107
+ this.#inspectLock =
108
+ options.inspectLock ?? (() => inspectPackageUpdateLock({ tuttiHome: this.#tuttiHome }));
109
+ this.#acquireLock =
110
+ options.acquireLock ?? (() => tryAcquirePackageUpdateLock({ tuttiHome: this.#tuttiHome }));
111
+ this.#readRecord =
112
+ options.readRecord ?? (() => readPackageUpdateRuntimeRecord({ tuttiHome: this.#tuttiHome }));
113
+ this.#clearFailedRecord =
114
+ options.clearFailedRecord ??
115
+ ((record) => deletePackageUpdateRuntimeRecord({
116
+ tuttiHome: this.#tuttiHome,
117
+ expectedTargetVersion: record.target_version,
118
+ expectedUpdaterPid: record.updater_pid,
119
+ }));
120
+ }
121
+ async getStatus(options = {}) {
122
+ return (await this.#evaluate({ refresh: options.refresh === true, ownsUpdateLock: false }))
123
+ .update;
124
+ }
125
+ async beginUpdate() {
126
+ const lock = this.#acquireLock();
127
+ if (lock.status !== "acquired") {
128
+ return {
129
+ status: "blocked",
130
+ update: lockBlockedStatus(lock, this.#currentVersion),
131
+ };
132
+ }
133
+ const operationLease = this.#operations.tryBeginPackageUpdate();
134
+ if (operationLease === null) {
135
+ lock.lease.release();
136
+ return {
137
+ status: "blocked",
138
+ update: blocked({ currentVersion: this.#currentVersion, reasonCode: "console_busy" }),
139
+ };
140
+ }
141
+ try {
142
+ const evaluation = await this.#evaluate({ refresh: true, ownsUpdateLock: true });
143
+ if (evaluation.update.status !== "ready" ||
144
+ evaluation.installation === undefined ||
145
+ evaluation.target === undefined ||
146
+ evaluation.restore_project_ids === undefined) {
147
+ operationLease.release();
148
+ lock.lease.release();
149
+ return { status: "blocked", update: evaluation.update };
150
+ }
151
+ return {
152
+ status: "ready",
153
+ preparation: {
154
+ installation: evaluation.installation,
155
+ target: evaluation.target,
156
+ restore_project_ids: evaluation.restore_project_ids,
157
+ lease: this.#combinedLease(lock.lease, operationLease),
158
+ },
159
+ };
160
+ }
161
+ catch (error) {
162
+ operationLease.release();
163
+ lock.lease.release();
164
+ throw error;
165
+ }
166
+ }
167
+ #combinedLease(lock, operation) {
168
+ let released = false;
169
+ let handedOff = false;
170
+ return {
171
+ handoffTo: (pid) => {
172
+ if (released || handedOff) {
173
+ throw new Error("Package update preparation lease was already settled.");
174
+ }
175
+ lock.transferTo(pid);
176
+ handedOff = true;
177
+ },
178
+ release: () => {
179
+ if (released) {
180
+ return;
181
+ }
182
+ released = true;
183
+ if (!handedOff) {
184
+ operation.release();
185
+ lock.release();
186
+ }
187
+ },
188
+ cancel: () => {
189
+ if (released) {
190
+ return;
191
+ }
192
+ released = true;
193
+ operation.release();
194
+ lock.release();
195
+ },
196
+ };
197
+ }
198
+ async #evaluate(options) {
199
+ const runtime = options.skipRuntimeRecord
200
+ ? { status: "missing" }
201
+ : this.#readRecord();
202
+ if (runtime.status === "invalid") {
203
+ return {
204
+ update: blocked({
205
+ currentVersion: this.#currentVersion,
206
+ reasonCode: "update_recovery_required",
207
+ }),
208
+ };
209
+ }
210
+ if (runtime.status === "valid") {
211
+ const record = runtime.record;
212
+ if (record.source_version !== this.#currentVersion &&
213
+ record.target_version !== this.#currentVersion) {
214
+ return {
215
+ update: blocked({
216
+ currentVersion: this.#currentVersion,
217
+ reasonCode: "update_recovery_required",
218
+ }),
219
+ };
220
+ }
221
+ if (record.phase === "failed") {
222
+ if (options.refresh &&
223
+ this.#inspectLock().status === "unlocked" &&
224
+ this.#clearFailedRecord(record)) {
225
+ return await this.#evaluate({ ...options, refresh: true, skipRuntimeRecord: true });
226
+ }
227
+ const knownReasons = new Set([
228
+ "host_busy",
229
+ "host_identity_unverified",
230
+ "host_update_unsupported",
231
+ "npm_install_failed",
232
+ "post_install_validation_failed",
233
+ "completion_start_failed",
234
+ "completion_failed",
235
+ "worker_crashed",
236
+ ]);
237
+ return {
238
+ update: {
239
+ current_version: this.#currentVersion,
240
+ latest_version: record.target_version,
241
+ status: "failed",
242
+ can_update: false,
243
+ reason_code: record.failure_reason_code !== undefined &&
244
+ knownReasons.has(record.failure_reason_code)
245
+ ? record.failure_reason_code
246
+ : "update_recovery_required",
247
+ },
248
+ };
249
+ }
250
+ const lock = this.#inspectLock();
251
+ if (record.phase === "restarting" &&
252
+ record.target_version === this.#currentVersion &&
253
+ lock.status === "unlocked") {
254
+ return {
255
+ update: (record.restart_failure_count ?? 0) > 0
256
+ ? {
257
+ current_version: this.#currentVersion,
258
+ latest_version: record.target_version,
259
+ status: "failed",
260
+ can_update: false,
261
+ reason_code: "host_restore_failed",
262
+ }
263
+ : {
264
+ current_version: this.#currentVersion,
265
+ latest_version: record.target_version,
266
+ status: "up_to_date",
267
+ can_update: false,
268
+ },
269
+ };
270
+ }
271
+ if (lock.status !== "active") {
272
+ return {
273
+ update: blocked({
274
+ currentVersion: this.#currentVersion,
275
+ reasonCode: "update_recovery_required",
276
+ }),
277
+ };
278
+ }
279
+ return {
280
+ update: {
281
+ current_version: this.#currentVersion,
282
+ latest_version: record.target_version,
283
+ status: "updating",
284
+ can_update: false,
285
+ phase: record.phase === "stopping_hosts"
286
+ ? "stopping_hosts"
287
+ : record.phase === "installing"
288
+ ? "installing"
289
+ : record.phase === "restarting"
290
+ ? "restarting"
291
+ : "preparing",
292
+ },
293
+ };
294
+ }
295
+ if (!options.ownsUpdateLock) {
296
+ const lock = this.#inspectLock();
297
+ if (lock.status !== "unlocked") {
298
+ return { update: lockBlockedStatus(lock, this.#currentVersion) };
299
+ }
300
+ if (this.#operations.state() === "package_update") {
301
+ return {
302
+ update: blocked({
303
+ currentVersion: this.#currentVersion,
304
+ reasonCode: "update_in_progress",
305
+ }),
306
+ };
307
+ }
308
+ }
309
+ const resolution = this.#resolveInstallation();
310
+ if (resolution.status === "unavailable") {
311
+ return {
312
+ update: blocked({
313
+ currentVersion: this.#currentVersion,
314
+ reasonCode: resolution.reasonCode,
315
+ }),
316
+ };
317
+ }
318
+ const discovery = await this.#discover(resolution, { refresh: options.refresh });
319
+ if (discovery.status === "unavailable") {
320
+ return {
321
+ update: blocked({
322
+ currentVersion: this.#currentVersion,
323
+ reasonCode: discovery.reasonCode,
324
+ }),
325
+ };
326
+ }
327
+ if (discovery.status === "up_to_date") {
328
+ return {
329
+ update: {
330
+ current_version: this.#currentVersion,
331
+ latest_version: discovery.latestVersion,
332
+ status: "up_to_date",
333
+ can_update: false,
334
+ },
335
+ };
336
+ }
337
+ if (this.#operations.state() === "project_busy") {
338
+ return {
339
+ update: blocked({
340
+ currentVersion: this.#currentVersion,
341
+ latestVersion: discovery.latestVersion,
342
+ reasonCode: "console_busy",
343
+ }),
344
+ };
345
+ }
346
+ let projects;
347
+ try {
348
+ projects = await this.#inspectProjects();
349
+ }
350
+ catch {
351
+ return {
352
+ update: blocked({
353
+ currentVersion: this.#currentVersion,
354
+ latestVersion: discovery.latestVersion,
355
+ reasonCode: "host_identity_unverified",
356
+ }),
357
+ };
358
+ }
359
+ const hosts = await inspectPackageUpdateHosts({
360
+ projects,
361
+ readReadiness: this.#readReadiness,
362
+ });
363
+ if (hosts.status === "blocked") {
364
+ return {
365
+ update: blocked({
366
+ currentVersion: this.#currentVersion,
367
+ latestVersion: discovery.latestVersion,
368
+ reasonCode: hosts.reason_code,
369
+ }),
370
+ };
371
+ }
372
+ if (this.#operations.state() === "project_busy") {
373
+ return {
374
+ update: blocked({
375
+ currentVersion: this.#currentVersion,
376
+ latestVersion: discovery.latestVersion,
377
+ reasonCode: "console_busy",
378
+ }),
379
+ };
380
+ }
381
+ if (!options.ownsUpdateLock) {
382
+ const lock = this.#inspectLock();
383
+ if (lock.status !== "unlocked") {
384
+ return { update: lockBlockedStatus(lock, this.#currentVersion) };
385
+ }
386
+ }
387
+ return {
388
+ update: {
389
+ current_version: this.#currentVersion,
390
+ latest_version: discovery.latestVersion,
391
+ status: "ready",
392
+ can_update: true,
393
+ },
394
+ installation: resolution.installation,
395
+ target: discovery.target,
396
+ restore_project_ids: hosts.restore_project_ids,
397
+ };
398
+ }
399
+ }
400
+ //# sourceMappingURL=package-update-service.js.map
@@ -0,0 +1,55 @@
1
+ import { type ProjectId } from "@tutti/shared/ids";
2
+ import { readCliPackageIdentity } from "../cli/version.js";
3
+ import { type GlobalPackageInstallation, type PackageCommandRunner, type PublishedPackageTarget } from "../package-installation/index.js";
4
+ import { PackageUpdateLogger } from "./package-update-log.js";
5
+ import { type PackageUpdateRuntimeRecord } from "./package-update-record.js";
6
+ import { type PackageUpdateHostStopResult } from "./package-update-hosts.js";
7
+ export type PackageUpdateWorkerInput = {
8
+ schema_version: 1;
9
+ tutti_home: string;
10
+ cli_entrypoint: string;
11
+ installation: GlobalPackageInstallation;
12
+ target: PublishedPackageTarget;
13
+ restore_project_ids: ProjectId[];
14
+ completion_environment: NodeJS.ProcessEnv;
15
+ };
16
+ export type PackageUpdateWorkerResult = {
17
+ status: "handed_off";
18
+ } | {
19
+ status: "failed";
20
+ reason_code: string;
21
+ restart_failure_count: number;
22
+ };
23
+ export declare function parsePackageUpdateWorkerInput(value: unknown, options?: {
24
+ defaultTuttiHome?: string;
25
+ }): PackageUpdateWorkerInput | null;
26
+ export type PackageUpdateWorkerDependencies = {
27
+ now?: () => Date;
28
+ logger?: PackageUpdateLogger;
29
+ commandRunner?: PackageCommandRunner;
30
+ stopHost?: (projectId: ProjectId) => Promise<PackageUpdateHostStopResult>;
31
+ restoreHosts?: (projectIds: readonly ProjectId[]) => Promise<{
32
+ restored_project_ids: ProjectId[];
33
+ failed_project_ids: ProjectId[];
34
+ }>;
35
+ verifyPostInstall?: (input: PackageUpdateWorkerInput) => Promise<boolean>;
36
+ startCompletion: (input: PackageUpdateWorkerInput, record: PackageUpdateRuntimeRecord) => Promise<void>;
37
+ releaseLock?: () => boolean;
38
+ writeRecord?: (record: PackageUpdateRuntimeRecord) => void;
39
+ updateRecord?: (record: PackageUpdateRuntimeRecord, options: {
40
+ phase: PackageUpdateRuntimeRecord["phase"];
41
+ failureReasonCode?: string;
42
+ restartFailureCount?: number;
43
+ }) => PackageUpdateRuntimeRecord;
44
+ };
45
+ export declare function createPackageUpdateWorkerInput(options: {
46
+ tuttiHome: string;
47
+ cliEntrypoint: string;
48
+ installation: GlobalPackageInstallation;
49
+ target: PublishedPackageTarget;
50
+ restoreProjectIds: readonly ProjectId[];
51
+ completionEnvironment: NodeJS.ProcessEnv;
52
+ }): PackageUpdateWorkerInput;
53
+ export declare function verifyPackageUpdatePostInstall(input: PackageUpdateWorkerInput, runner?: PackageCommandRunner, readIdentity?: typeof readCliPackageIdentity): Promise<boolean>;
54
+ export declare function runPackageUpdateWorker(input: PackageUpdateWorkerInput, dependencies: PackageUpdateWorkerDependencies): Promise<PackageUpdateWorkerResult>;
55
+ //# sourceMappingURL=package-update-worker.d.ts.map