@xfey/tutti 0.1.15 → 0.1.17

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 (36) hide show
  1. package/dist/collaboration-state/index.d.ts +2 -2
  2. package/dist/collaboration-state/index.js +1 -1
  3. package/dist/collaboration-state/scratchpad.d.ts +2 -2
  4. package/dist/collaboration-state/scratchpad.js +94 -7
  5. package/dist/collaboration-state/storage-types.d.ts +14 -0
  6. package/dist/collaboration-state/types.d.ts +9 -1
  7. package/dist/control-plane/task-compile-output.js +7 -3
  8. package/dist/control-plane/task-compile-start.d.ts +3 -0
  9. package/dist/control-plane/task-compile-start.js +47 -11
  10. package/dist/server-shell/cli/cli.js +3 -22
  11. package/dist/server-shell/cli/machine-local.d.ts +4 -0
  12. package/dist/server-shell/cli/machine-local.js +15 -1
  13. package/dist/server-shell/cli/project-resolver.d.ts +8 -0
  14. package/dist/server-shell/cli/project-resolver.js +67 -5
  15. package/dist/server-shell/cli/provider-tui.d.ts +12 -0
  16. package/dist/server-shell/cli/provider-tui.js +15 -20
  17. package/dist/server-shell/cli/runtime-commands.d.ts +10 -1
  18. package/dist/server-shell/cli/runtime-commands.js +45 -7
  19. package/dist/server-shell/http/routes/project-api/openapi-collaboration-routes.d.ts +14 -1
  20. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +14 -1
  21. package/dist/server-shell/http/routes/project-api/openapi.d.ts +28 -2
  22. package/dist/server-shell/http/routes/project-api/schemas.d.ts +14 -1
  23. package/migrations/0012_scratchpad_receipts.sql +31 -0
  24. package/migrations/README.md +2 -1
  25. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
  26. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
  27. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +14 -1
  28. package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +2 -1
  29. package/node_modules/@tutti/shared/dist/schemas/api/work-items.d.ts +24 -1
  30. package/node_modules/@tutti/shared/dist/schemas/api/work-items.js +15 -1
  31. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.d.ts +28 -2
  32. package/package.json +1 -1
  33. package/web/assets/index-BhNJQ717.js +29 -0
  34. package/web/assets/{index-CohZfGSh.css → index-mXkNKFjm.css} +1 -1
  35. package/web/index.html +2 -2
  36. package/web/assets/index-YQvFdpG8.js +0 -29
@@ -1,7 +1,7 @@
1
1
  export { CollaborationStateCursorError } from "./serialization.js";
2
2
  export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
3
  export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesAfter, readMainChatMessagesAfterCursor, readMainChatMessagesBeforeCursor, } from "./messages.js";
4
- export { clearScratchpadProjection, createEmptyScratchpadProjection, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
4
+ export { createEmptyScratchpadProjection, readScratchpadProjection, submitScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
5
5
  export { advanceScratchpadTaskCompileSourceCursor, markScratchpadRefreshFailed, markScratchpadRefreshStarted, markScratchpadRefreshSucceeded, markScratchpadSourceDirty, readScratchpadSourceState, } from "./scratchpad-source-state.js";
6
6
  export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readSealedClarificationRoundsWithoutSuccessor, readSubmittedTaskCompileClarificationRounds, readSubmittedTaskCompileClarificationRoundsAfterCursor, } from "./clarification-records.js";
7
7
  export { createClarificationRoundAgentMessage, createClarificationRoundMessage, readClarificationRoundMessagesWindow, } from "./clarification-messages.js";
@@ -13,5 +13,5 @@ export { recoverStaleRunningTasksAsFailed } from "./recovery.js";
13
13
  export { markReadStateScope, readReadStateSummary, recordReadNotificationEvent, } from "./read-state.js";
14
14
  export { readTaskDetailProjection } from "./task-details.js";
15
15
  export { applyTaskCompileProposal, markTaskRunStarted, readNextRunnableTask, readRecoverableScheduledRun, readTaskProjection, readWorklistProjection, } from "./worklist.js";
16
- export type { ApplyTaskCompileProposalResult, CloseTaskBoundClarificationRetaskingRequiredInput, CloseTaskBoundClarificationRetaskingRequiredResult, CreateClarificationRoundAgentMessageInput, CreateClarificationRoundMessageInput, CreateMainChatAgentMessageInput, CreateMainChatMessageInput, CreateMainChatSystemMessageInput, CursorPageOptions, MarkReadStateScopeInput, MarkReadStateScopeResult, MarkTaskRunStartedInput, MarkTaskRunStartedResult, MaterializeClarificationSuccessorInput, MaterializeTaskBoundRunSuccessorInput, MaterializeTaskBoundRunSuccessorResult, MessageAuthorInput, NextRunnableTaskResult, OpenNextTaskBoundClarificationRoundInput, OpenNextTaskBoundClarificationRoundResult, OpenNextTaskCompileClarificationRoundInput, OpenNextTaskCompileClarificationRoundResult, OpenTaskCompileClarificationRoundInput, OpenTaskCompileClarificationRoundResult, ReadMainChatMessagesAfterOptions, ReadMessageWindowOptions, ReadReadStateSummaryInput, RecoverStaleRunningTasksResult, RecoverableScheduledRun, RecordReadNotificationEventInput, RecordRecoverySummaryInput, RecordRecoverySummaryResult, RecordRunNeedsHumanResultInput, RecordRunNeedsHumanResultResult, RecordRunResultInput, RecordRunResultResult, RunResultPromotionProjection, ScratchpadSnapshotInput, SealedClarificationRoundWithoutSuccessor, SubmitClarificationRoundInput, TaskCompileProposalInput, TaskCompileProposalModuleInput, TaskCompileProposalTaskInput, } from "./types.js";
16
+ export type { ApplyTaskCompileProposalResult, CloseTaskBoundClarificationRetaskingRequiredInput, CloseTaskBoundClarificationRetaskingRequiredResult, CreateClarificationRoundAgentMessageInput, CreateClarificationRoundMessageInput, CreateMainChatAgentMessageInput, CreateMainChatMessageInput, CreateMainChatSystemMessageInput, CursorPageOptions, MarkReadStateScopeInput, MarkReadStateScopeResult, MarkTaskRunStartedInput, MarkTaskRunStartedResult, MaterializeClarificationSuccessorInput, MaterializeTaskBoundRunSuccessorInput, MaterializeTaskBoundRunSuccessorResult, MessageAuthorInput, NextRunnableTaskResult, OpenNextTaskBoundClarificationRoundInput, OpenNextTaskBoundClarificationRoundResult, OpenNextTaskCompileClarificationRoundInput, OpenNextTaskCompileClarificationRoundResult, OpenTaskCompileClarificationRoundInput, OpenTaskCompileClarificationRoundResult, ReadMainChatMessagesAfterOptions, ReadMessageWindowOptions, ReadReadStateSummaryInput, RecoverStaleRunningTasksResult, RecoverableScheduledRun, RecordReadNotificationEventInput, RecordRecoverySummaryInput, RecordRecoverySummaryResult, RecordRunNeedsHumanResultInput, RecordRunNeedsHumanResultResult, RecordRunResultInput, RecordRunResultResult, RunResultPromotionProjection, ScratchpadSnapshotInput, SubmitScratchpadInput, SubmitScratchpadResult, SealedClarificationRoundWithoutSuccessor, SubmitClarificationRoundInput, TaskCompileProposalInput, TaskCompileProposalModuleInput, TaskCompileProposalTaskInput, } from "./types.js";
17
17
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,7 @@
1
1
  export { CollaborationStateCursorError } from "./serialization.js";
2
2
  export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
3
  export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesAfter, readMainChatMessagesAfterCursor, readMainChatMessagesBeforeCursor, } from "./messages.js";
4
- export { clearScratchpadProjection, createEmptyScratchpadProjection, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
4
+ export { createEmptyScratchpadProjection, readScratchpadProjection, submitScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
5
5
  export { advanceScratchpadTaskCompileSourceCursor, markScratchpadRefreshFailed, markScratchpadRefreshStarted, markScratchpadRefreshSucceeded, markScratchpadSourceDirty, readScratchpadSourceState, } from "./scratchpad-source-state.js";
6
6
  export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readSealedClarificationRoundsWithoutSuccessor, readSubmittedTaskCompileClarificationRounds, readSubmittedTaskCompileClarificationRoundsAfterCursor, } from "./clarification-records.js";
7
7
  export { createClarificationRoundAgentMessage, createClarificationRoundMessage, readClarificationRoundMessagesWindow, } from "./clarification-messages.js";
@@ -1,8 +1,8 @@
1
1
  import type { ScratchpadProjection } from "@tutti/shared/schemas/api";
2
2
  import type { SqliteDatabase } from "../store/index.js";
3
- import type { ScratchpadSnapshotInput } from "./types.js";
3
+ import type { ScratchpadSnapshotInput, SubmitScratchpadInput } from "./types.js";
4
4
  export declare function createEmptyScratchpadProjection(): ScratchpadProjection;
5
5
  export declare function upsertScratchpadProjection(db: SqliteDatabase, input: ScratchpadSnapshotInput): ScratchpadProjection;
6
- export declare function clearScratchpadProjection(db: SqliteDatabase): ScratchpadProjection;
6
+ export declare function submitScratchpadProjection(db: SqliteDatabase, input: SubmitScratchpadInput): ScratchpadProjection;
7
7
  export declare function readScratchpadProjection(db: SqliteDatabase): ScratchpadProjection;
8
8
  //# sourceMappingURL=scratchpad.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { nowIso, parseJson, stringifyJson } from "./serialization.js";
2
+ const SCRATCHPAD_RECEIPT_LIMIT = 100;
2
3
  export function createEmptyScratchpadProjection() {
3
4
  return {
4
5
  id: "current",
@@ -8,8 +9,37 @@ export function createEmptyScratchpadProjection() {
8
9
  current_consensus: [],
9
10
  open_questions: [],
10
11
  task_changes: [],
12
+ receipts: [],
11
13
  };
12
14
  }
15
+ function readScratchpadReceipts(db) {
16
+ const rows = db
17
+ .prepare(`
18
+ SELECT
19
+ workflow_ref,
20
+ activity_ref,
21
+ topic,
22
+ background_summary,
23
+ current_consensus_json,
24
+ open_questions_json,
25
+ task_changes_json,
26
+ submitted_at
27
+ FROM scratchpad_receipts
28
+ ORDER BY submitted_at DESC, workflow_ref DESC
29
+ LIMIT ${SCRATCHPAD_RECEIPT_LIMIT}
30
+ `)
31
+ .all();
32
+ return rows.map((row) => ({
33
+ workflow_ref: row.workflow_ref,
34
+ activity_ref: row.activity_ref,
35
+ topic: row.topic,
36
+ background_summary: row.background_summary,
37
+ current_consensus: parseJson(row.current_consensus_json, []),
38
+ open_questions: parseJson(row.open_questions_json, []),
39
+ task_changes: parseJson(row.task_changes_json, []),
40
+ submitted_at: row.submitted_at,
41
+ }));
42
+ }
13
43
  export function upsertScratchpadProjection(db, input) {
14
44
  const updatedAt = nowIso(input.now);
15
45
  db.prepare(`
@@ -34,51 +64,108 @@ export function upsertScratchpadProjection(db, input) {
34
64
  ?
35
65
  )
36
66
  ON CONFLICT(id) DO UPDATE SET
67
+ state = 'ready',
37
68
  topic = excluded.topic,
38
69
  background_summary = excluded.background_summary,
39
70
  current_consensus_json = excluded.current_consensus_json,
40
71
  open_questions_json = excluded.open_questions_json,
41
72
  task_changes_json = excluded.task_changes_json,
42
73
  updated_by_activity_ref = excluded.updated_by_activity_ref,
43
- updated_at = excluded.updated_at
74
+ updated_at = excluded.updated_at,
75
+ submitted_by_activity_ref = NULL,
76
+ submitted_by_workflow_ref = NULL,
77
+ submitted_at = NULL
44
78
  `).run(input.topic, input.background_summary, stringifyJson(input.current_consensus), stringifyJson(input.open_questions), stringifyJson(input.task_changes), input.updated_by_activity_ref ?? null, updatedAt);
45
79
  return readScratchpadProjection(db);
46
80
  }
47
- export function clearScratchpadProjection(db) {
48
- db.prepare("DELETE FROM scratchpad WHERE id = 'current'").run();
49
- return createEmptyScratchpadProjection();
81
+ export function submitScratchpadProjection(db, input) {
82
+ const submittedAt = nowIso(input.now);
83
+ const scratchpad = readScratchpadProjection(db);
84
+ if (scratchpad.state === "empty") {
85
+ return scratchpad;
86
+ }
87
+ db.prepare(`
88
+ INSERT INTO scratchpad_receipts (
89
+ workflow_ref,
90
+ activity_ref,
91
+ topic,
92
+ background_summary,
93
+ current_consensus_json,
94
+ open_questions_json,
95
+ task_changes_json,
96
+ submitted_at
97
+ )
98
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
99
+ ON CONFLICT(workflow_ref) DO UPDATE SET
100
+ activity_ref = excluded.activity_ref,
101
+ topic = excluded.topic,
102
+ background_summary = excluded.background_summary,
103
+ current_consensus_json = excluded.current_consensus_json,
104
+ open_questions_json = excluded.open_questions_json,
105
+ task_changes_json = excluded.task_changes_json,
106
+ submitted_at = excluded.submitted_at
107
+ `).run(input.workflow_ref, input.activity_ref, scratchpad.topic, scratchpad.background_summary, stringifyJson(scratchpad.current_consensus), stringifyJson(scratchpad.open_questions), stringifyJson(scratchpad.task_changes), submittedAt);
108
+ db.prepare(`
109
+ UPDATE scratchpad
110
+ SET
111
+ state = 'submitted',
112
+ submitted_by_activity_ref = ?,
113
+ submitted_by_workflow_ref = ?,
114
+ submitted_at = ?,
115
+ updated_at = ?
116
+ WHERE id = 'current'
117
+ `).run(input.activity_ref, input.workflow_ref, submittedAt, submittedAt);
118
+ return readScratchpadProjection(db);
50
119
  }
51
120
  export function readScratchpadProjection(db) {
121
+ const receipts = readScratchpadReceipts(db);
52
122
  const row = db
53
123
  .prepare(`
54
124
  SELECT
125
+ state,
55
126
  topic,
56
127
  background_summary,
57
128
  current_consensus_json,
58
129
  open_questions_json,
59
130
  task_changes_json,
60
131
  updated_by_activity_ref,
61
- updated_at
132
+ updated_at,
133
+ submitted_by_activity_ref,
134
+ submitted_by_workflow_ref,
135
+ submitted_at
62
136
  FROM scratchpad
63
137
  WHERE id = 'current'
64
138
  `)
65
139
  .get();
66
140
  if (row === undefined) {
67
- return createEmptyScratchpadProjection();
141
+ return {
142
+ ...createEmptyScratchpadProjection(),
143
+ receipts,
144
+ };
68
145
  }
69
146
  const scratchpad = {
70
147
  id: "current",
71
- state: "ready",
148
+ state: row.state,
72
149
  topic: row.topic,
73
150
  background_summary: row.background_summary,
74
151
  current_consensus: parseJson(row.current_consensus_json, []),
75
152
  open_questions: parseJson(row.open_questions_json, []),
76
153
  task_changes: parseJson(row.task_changes_json, []),
77
154
  updated_at: row.updated_at,
155
+ receipts,
78
156
  };
79
157
  if (row.updated_by_activity_ref !== null) {
80
158
  scratchpad.updated_by_activity_ref = row.updated_by_activity_ref;
81
159
  }
160
+ if (row.submitted_by_activity_ref !== null) {
161
+ scratchpad.submitted_by_activity_ref = row.submitted_by_activity_ref;
162
+ }
163
+ if (row.submitted_by_workflow_ref !== null) {
164
+ scratchpad.submitted_by_workflow_ref = row.submitted_by_workflow_ref;
165
+ }
166
+ if (row.submitted_at !== null) {
167
+ scratchpad.submitted_at = row.submitted_at;
168
+ }
82
169
  return scratchpad;
83
170
  }
84
171
  //# sourceMappingURL=scratchpad.js.map
@@ -13,6 +13,7 @@ export type MessageRow = {
13
13
  created_at: string;
14
14
  };
15
15
  export type ScratchpadRow = {
16
+ state: "ready" | "submitted";
16
17
  topic: string;
17
18
  background_summary: string;
18
19
  current_consensus_json: string;
@@ -20,6 +21,19 @@ export type ScratchpadRow = {
20
21
  task_changes_json: string;
21
22
  updated_by_activity_ref: string | null;
22
23
  updated_at: string;
24
+ submitted_by_activity_ref: string | null;
25
+ submitted_by_workflow_ref: string | null;
26
+ submitted_at: string | null;
27
+ };
28
+ export type ScratchpadReceiptRow = {
29
+ workflow_ref: string;
30
+ activity_ref: string;
31
+ topic: string;
32
+ background_summary: string;
33
+ current_consensus_json: string;
34
+ open_questions_json: string;
35
+ task_changes_json: string;
36
+ submitted_at: string;
23
37
  };
24
38
  export type ScratchpadSourceStateRow = {
25
39
  main_chat_cursor_created_at: string | null;
@@ -1,7 +1,7 @@
1
1
  import type { RunLineage } from "@tutti/shared/domain";
2
2
  import type { AccountAvatarProjection } from "@tutti/shared/domain";
3
3
  import type { ActivityRef, ClarificationRoundRef, TaskId, TaskModuleId, WorkflowInvocationRef } from "@tutti/shared/ids";
4
- import type { ClarificationRequestPayload, ClarificationRoundProjection, ClarificationSuccessorRef, ClarificationThreadProjection, MessageProjection, ReadStateScope, ReadStateSummaryProjection, RecoverySummaryProjection, RunResultProjection, TaskContractProjection, TaskDetailProjection, TaskDetailResultProjection, TaskProjection, WorklistProjection } from "@tutti/shared/schemas/api";
4
+ import type { ClarificationRequestPayload, ClarificationRoundProjection, ClarificationSuccessorRef, ClarificationThreadProjection, MessageProjection, ReadStateScope, ReadStateSummaryProjection, RecoverySummaryProjection, RunResultProjection, ScratchpadProjection, TaskContractProjection, TaskDetailProjection, TaskDetailResultProjection, TaskProjection, WorklistProjection } from "@tutti/shared/schemas/api";
5
5
  export type MessageAuthorInput = {
6
6
  kind: "human" | "system" | "agent";
7
7
  ref?: string;
@@ -56,6 +56,14 @@ export type ScratchpadSnapshotInput = {
56
56
  updated_by_activity_ref?: ActivityRef;
57
57
  now?: () => Date;
58
58
  };
59
+ export type SubmitScratchpadInput = {
60
+ workflow_ref: WorkflowInvocationRef;
61
+ activity_ref: ActivityRef;
62
+ now?: () => Date;
63
+ };
64
+ export type SubmitScratchpadResult = {
65
+ scratchpad: ScratchpadProjection;
66
+ };
59
67
  export type TaskCompileProposalInput = {
60
68
  workflow_ref: WorkflowInvocationRef;
61
69
  proposal: {
@@ -1,4 +1,4 @@
1
- import { applyTaskCompileProposal, clearScratchpadProjection, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, readWorklistProjection, } from "../collaboration-state/index.js";
1
+ import { applyTaskCompileProposal, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, readWorklistProjection, submitScratchpadProjection, } from "../collaboration-state/index.js";
2
2
  import { clarificationInvalidates, taskCompileInvalidates, } from "./invalidations.js";
3
3
  import { normalizeTaskCompileProposal } from "./task-proposal.js";
4
4
  const EMPTY_INITIAL_WORKLIST_CLARIFICATION = {
@@ -86,7 +86,11 @@ export function applyTaskCompileProcedureOutput(input) {
86
86
  if (applied.changed_task_ids.length > 0) {
87
87
  input.onTasksAdded?.();
88
88
  }
89
- const clearedScratchpad = clearScratchpadProjection(input.store.db);
89
+ const submittedScratchpad = submitScratchpadProjection(input.store.db, {
90
+ workflow_ref: input.workflowRef,
91
+ activity_ref: input.activityRef,
92
+ now: input.now,
93
+ });
90
94
  input.events.publish({
91
95
  event_type: "worklist.changed",
92
96
  payload: {
@@ -101,7 +105,7 @@ export function applyTaskCompileProcedureOutput(input) {
101
105
  input.events.publish({
102
106
  event_type: "scratchpad.updated",
103
107
  payload: {
104
- scratchpad: clearedScratchpad,
108
+ scratchpad: submittedScratchpad,
105
109
  activity_ref: input.activityRef,
106
110
  },
107
111
  invalidates: taskCompileInvalidates(),
@@ -40,5 +40,8 @@ export declare function runControlPlaneTaskCompileProcedure(options: {
40
40
  kind: "workflow";
41
41
  workflow_ref: `wf_${string}`;
42
42
  };
43
+ } | {
44
+ kind: "failed";
45
+ summary: string;
43
46
  }>;
44
47
  //# sourceMappingURL=task-compile-start.d.ts.map
@@ -51,7 +51,7 @@ export function startControlPlaneTaskCompile(options) {
51
51
  sourceBatch,
52
52
  });
53
53
  }
54
- if (scratchpad.state === "empty") {
54
+ if (!scratchpadHasSubmittableContent(scratchpad)) {
55
55
  return { disposition: { kind: "scratchpad_empty" } };
56
56
  }
57
57
  if (shouldInitializeProjectContext) {
@@ -66,6 +66,10 @@ export function startControlPlaneTaskCompile(options) {
66
66
  });
67
67
  }
68
68
  function startControlPlaneTaskCompileActivity(options) {
69
+ const scratchpad = readScratchpadProjection(options.store.db);
70
+ if (!scratchpadHasSubmittableContent(scratchpad)) {
71
+ return { disposition: { kind: "scratchpad_empty" } };
72
+ }
69
73
  const workflowRef = createWorkflowInvocationRef();
70
74
  const start = options.engine.startProcedure({
71
75
  workflow_ref: workflowRef,
@@ -120,13 +124,18 @@ function startScratchpadRefreshBeforeTaskCompileActivity(options) {
120
124
  now: options.now,
121
125
  onTransition: (state) => {
122
126
  options.publishProcedureTransition(state);
123
- if (state.activity.status === "finished") {
124
- if (shouldInitializeControlPlaneProjectContext(options)) {
125
- void startProjectContextBootstrapActivity(options);
126
- }
127
- else {
128
- void startControlPlaneTaskCompileActivity(options);
129
- }
127
+ if (state.transition_kind !== "terminal" || state.activity.status !== "finished") {
128
+ return;
129
+ }
130
+ const scratchpad = readScratchpadProjection(options.store.db);
131
+ if (!scratchpadHasSubmittableContent(scratchpad)) {
132
+ return;
133
+ }
134
+ if (shouldInitializeControlPlaneProjectContext(options)) {
135
+ void startProjectContextBootstrapActivity(options);
136
+ }
137
+ else {
138
+ void startControlPlaneTaskCompileActivity(options);
130
139
  }
131
140
  },
132
141
  onError: ({ context, error }) => logProcedureExecutionError({
@@ -186,9 +195,17 @@ function startProjectContextBootstrapActivity(options) {
186
195
  now: options.now,
187
196
  onTransition: (state) => {
188
197
  options.publishProcedureTransition(state);
189
- if (state.activity.status === "finished") {
190
- void startControlPlaneTaskCompileActivity(options);
198
+ if (state.transition_kind !== "terminal" || state.activity.status !== "finished") {
199
+ return;
200
+ }
201
+ if (shouldInitializeControlPlaneProjectContext(options)) {
202
+ return;
191
203
  }
204
+ const scratchpad = readScratchpadProjection(options.store.db);
205
+ if (!scratchpadHasSubmittableContent(scratchpad)) {
206
+ return;
207
+ }
208
+ void startControlPlaneTaskCompileActivity(options);
192
209
  },
193
210
  onError: ({ context, error }) => logProcedureExecutionError({
194
211
  logger: options.logger,
@@ -261,9 +278,16 @@ function startProjectContextBootstrapActivity(options) {
261
278
  };
262
279
  }
263
280
  export async function runControlPlaneTaskCompileProcedure(options) {
281
+ const scratchpad = readScratchpadProjection(options.store.db);
282
+ if (!scratchpadHasSubmittableContent(scratchpad)) {
283
+ return {
284
+ kind: "failed",
285
+ summary: "Scratchpad is empty. Discuss more before running.",
286
+ };
287
+ }
264
288
  const output = await options.runner.runTaskCompile({
265
289
  workflow_ref: options.workflowRef,
266
- scratchpad: readScratchpadProjection(options.store.db),
290
+ scratchpad,
267
291
  worklist: readWorklistProjection(options.store.db),
268
292
  }, {
269
293
  workflow_ref: options.workflowRef,
@@ -282,4 +306,16 @@ export async function runControlPlaneTaskCompileProcedure(options) {
282
306
  onTasksAdded: options.onTasksAdded,
283
307
  });
284
308
  }
309
+ function scratchpadHasSubmittableContent(scratchpad) {
310
+ if (scratchpad.state !== "ready") {
311
+ return false;
312
+ }
313
+ return [
314
+ scratchpad.topic,
315
+ scratchpad.background_summary,
316
+ ...scratchpad.current_consensus,
317
+ ...scratchpad.open_questions,
318
+ ...scratchpad.task_changes,
319
+ ].some((item) => item.trim().length > 0);
320
+ }
285
321
  //# sourceMappingURL=task-compile-start.js.map
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
- import { formatCliError, formatCliErrorReason } from "./errors.js";
2
+ import { formatCliError } from "./errors.js";
3
3
  import { parseCliArgs } from "./args.js";
4
4
  import { runBackgroundLaunchCommand, runForegroundLaunchCommand, runInternalHostRunCommand, } from "./launch-command.js";
5
5
  import { resolveExistingProjectContext } from "./project-resolver.js";
6
6
  import { runProviderSetupTui } from "./provider-tui.js";
7
- import { runInviteCommand, runLogsCommand, runProviderStatusCommand, runPsManageCommand, runPsCommand, runStopCommand, } from "./runtime-commands.js";
7
+ import { runDoctorCommand, runInviteCommand, runLogsCommand, runProviderStatusCommand, runPsManageCommand, runPsCommand, runStopCommand, } from "./runtime-commands.js";
8
8
  import { readCliVersion } from "./version.js";
9
9
  const HELP_TEXT = `Usage:
10
10
  tutti launch [target] [--yes] [--foreground]
@@ -108,25 +108,6 @@ async function runProviderSetupCommand(target) {
108
108
  ].join("\n"));
109
109
  process.stdout.write("\n");
110
110
  }
111
- function runDoctorCommand(target) {
112
- const checks = [];
113
- checks.push(`Node: ${process.version}`);
114
- checks.push(`Tutti: ${readCliVersion()}`);
115
- try {
116
- const project = resolveExistingProjectContext({
117
- ...(target === undefined ? {} : { target }),
118
- });
119
- checks.push(`Project: ${project.display_name}`);
120
- checks.push(`Project ID: ${project.project_id}`);
121
- checks.push(`Provider: ${project.provider_config.status}`);
122
- checks.push(`Host: ${project.runtime_endpoint === null ? "not running" : project.runtime_endpoint.base_url}`);
123
- }
124
- catch (error) {
125
- checks.push("Project: unavailable");
126
- checks.push(`Project detail: ${formatCliErrorReason(error)}`);
127
- }
128
- process.stdout.write(`${checks.join("\n")}\n`);
129
- }
130
111
  try {
131
112
  const command = parseCliArgs(process.argv.slice(2));
132
113
  switch (command.kind) {
@@ -171,7 +152,7 @@ try {
171
152
  process.stdout.write(`${runProviderStatusCommand(command.target)}\n`);
172
153
  break;
173
154
  case "doctor":
174
- runDoctorCommand(command.target);
155
+ process.stdout.write(`${runDoctorCommand(command.target)}\n`);
175
156
  break;
176
157
  }
177
158
  }
@@ -61,6 +61,10 @@ export declare function deleteMachineRuntimeEndpoint(options: {
61
61
  projectId: ProjectId;
62
62
  expectedToken?: string;
63
63
  }): boolean;
64
+ export declare function deleteMachineProjectLocalState(options: {
65
+ tuttiHome: string;
66
+ projectId: ProjectId;
67
+ }): boolean;
64
68
  export declare function readHostRegistrationSecret(tuttiHome: string, projectId: ProjectId): HostRegistrationSecretFile;
65
69
  export declare function updateMachineProjectRelayBinding(options: {
66
70
  tuttiHome: string;
@@ -1,5 +1,5 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
2
+ import { appendFileSync, chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, unlinkSync, writeFileSync, } from "node:fs";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { ID_PREFIXES, isPrefixedId } from "@tutti/shared/ids";
5
5
  import { LaunchError } from "./errors.js";
@@ -150,6 +150,20 @@ export function deleteMachineRuntimeEndpoint(options) {
150
150
  unlinkSync(endpointPath);
151
151
  return true;
152
152
  }
153
+ export function deleteMachineProjectLocalState(options) {
154
+ let deleted = false;
155
+ const localStoreRoot = getProjectLocalStoreRoot(options.tuttiHome, options.projectId);
156
+ if (existsSync(localStoreRoot)) {
157
+ rmSync(localStoreRoot, { recursive: true, force: true });
158
+ deleted = true;
159
+ }
160
+ const secretPath = getHostRegistrationSecretPath(options.tuttiHome, options.projectId);
161
+ if (existsSync(secretPath)) {
162
+ unlinkSync(secretPath);
163
+ deleted = true;
164
+ }
165
+ return deleted;
166
+ }
153
167
  export function readHostRegistrationSecret(tuttiHome, projectId) {
154
168
  const readResult = readJsonFile(getHostRegistrationSecretPath(tuttiHome, projectId));
155
169
  if (readResult.kind === "missing") {
@@ -11,6 +11,9 @@ export type ExistingProjectContext = {
11
11
  provider_config: OpenAiProviderConfigProjection;
12
12
  provider_base_url?: string;
13
13
  };
14
+ export type ManagedProjectContext = ExistingProjectContext & {
15
+ workspace_exists: boolean;
16
+ };
14
17
  export declare function resolveLaunchWorkspacePath(options: {
15
18
  target?: string;
16
19
  cwd?: string;
@@ -22,4 +25,9 @@ export declare function resolveExistingProjectContext(options: {
22
25
  cwd?: string;
23
26
  env?: NodeJS.ProcessEnv;
24
27
  }): ExistingProjectContext;
28
+ export declare function resolveManagedProjectContext(options: {
29
+ target?: string;
30
+ cwd?: string;
31
+ env?: NodeJS.ProcessEnv;
32
+ }): ManagedProjectContext;
25
33
  //# sourceMappingURL=project-resolver.d.ts.map
@@ -70,28 +70,50 @@ function resolveSingleTargetMatch(target, matches) {
70
70
  throw new LaunchError("project_target_not_found", `Project target was not found: ${target}`, "Run `tutti ps` to list known projects, or pass a project workspace path.");
71
71
  }
72
72
  function resolveProjectTargetWorkspaceRoot(options) {
73
+ return resolveProjectTargetEntry(options).workspace_root;
74
+ }
75
+ function resolveProjectTargetEntry(options) {
73
76
  const target = options.target.trim();
74
77
  if (target === "") {
75
- return resolve(options.cwd, ".");
78
+ return readProjectTargetEntryFromWorkspace(resolve(options.cwd, "."));
76
79
  }
77
80
  const pathCandidate = resolve(options.cwd, target);
78
81
  if (existsSync(pathCandidate)) {
79
- return pathCandidate;
82
+ return readProjectTargetEntryFromWorkspace(pathCandidate);
80
83
  }
81
84
  const entries = listProjectTargetEntries(options.tuttiHome);
85
+ const pathMatches = entries.filter((entry) => resolve(entry.workspace_root) === pathCandidate);
86
+ if (pathMatches.length > 0) {
87
+ return resolveSingleTargetMatch(target, pathMatches);
88
+ }
82
89
  const exactId = entries.filter((entry) => entry.project_id === target);
83
90
  if (exactId.length > 0) {
84
- return resolveSingleTargetMatch(target, exactId).workspace_root;
91
+ return resolveSingleTargetMatch(target, exactId);
85
92
  }
86
93
  if (target.startsWith(ID_PREFIXES.project)) {
87
94
  const idPrefix = entries.filter((entry) => entry.project_id.startsWith(target));
88
95
  if (idPrefix.length > 0) {
89
- return resolveSingleTargetMatch(target, idPrefix).workspace_root;
96
+ return resolveSingleTargetMatch(target, idPrefix);
90
97
  }
91
98
  }
92
99
  const normalizedTarget = normalizeTargetName(target);
93
100
  const nameMatches = entries.filter((entry) => entry.aliases.some((alias) => normalizeTargetName(alias) === normalizedTarget));
94
- return resolveSingleTargetMatch(target, nameMatches).workspace_root;
101
+ return resolveSingleTargetMatch(target, nameMatches);
102
+ }
103
+ function readProjectTargetEntryFromWorkspace(workspaceRoot) {
104
+ assertSafeProjectRoot(workspaceRoot);
105
+ const identity = readProjectIdentity(workspaceRoot);
106
+ if (identity.kind !== "present") {
107
+ throw new LaunchError("project_identity_conflict", "Current directory is not a Tutti project", "Run `tutti launch` from a project root first.");
108
+ }
109
+ const workspaceName = basename(workspaceRoot) || identity.project_id;
110
+ const displayName = readProjectDisplayName(workspaceRoot) ?? workspaceName;
111
+ return {
112
+ project_id: identity.project_id,
113
+ workspace_root: workspaceRoot,
114
+ display_name: displayName,
115
+ aliases: Array.from(new Set([displayName, workspaceName].filter((alias) => alias.trim() !== ""))),
116
+ };
95
117
  }
96
118
  export function resolveLaunchWorkspacePath(options) {
97
119
  const cwd = options.cwd ?? process.cwd();
@@ -154,4 +176,44 @@ export function resolveExistingProjectContext(options) {
154
176
  : {}),
155
177
  };
156
178
  }
179
+ export function resolveManagedProjectContext(options) {
180
+ const cwd = options.cwd ?? process.cwd();
181
+ const env = options.env ?? process.env;
182
+ const tuttiHome = resolveTuttiHome(env.TUTTI_HOME, cwd);
183
+ if (options.target === undefined || existsSync(resolve(cwd, options.target))) {
184
+ const existing = resolveExistingProjectContext({
185
+ ...(options.target === undefined ? {} : { target: options.target }),
186
+ cwd,
187
+ env,
188
+ });
189
+ return {
190
+ ...existing,
191
+ workspace_exists: true,
192
+ };
193
+ }
194
+ const entry = resolveProjectTargetEntry({
195
+ target: options.target,
196
+ cwd,
197
+ tuttiHome,
198
+ });
199
+ const binding = readBinding(tuttiHome, entry.project_id);
200
+ const runtimeEndpoint = readRuntimeEndpoint(tuttiHome, entry.project_id);
201
+ const projectConfig = readProjectOpenAiProviderConfig(tuttiHome, entry.project_id);
202
+ return {
203
+ workspace_root: entry.workspace_root,
204
+ workspace_exists: existsSync(entry.workspace_root),
205
+ tutti_home: tuttiHome,
206
+ project_id: entry.project_id,
207
+ display_name: entry.display_name,
208
+ binding,
209
+ runtime_endpoint: runtimeEndpoint,
210
+ provider_config: readOpenAiProviderConfigProjection({
211
+ tuttiHome,
212
+ projectId: entry.project_id,
213
+ }),
214
+ ...(projectConfig.kind === "ok" && projectConfig.config.api_base_url !== undefined
215
+ ? { provider_base_url: projectConfig.config.api_base_url }
216
+ : {}),
217
+ };
218
+ }
157
219
  //# sourceMappingURL=project-resolver.js.map
@@ -1,5 +1,12 @@
1
1
  import { type ConfigureProjectOpenAiProviderResult } from "../../providers/openai/index.js";
2
2
  import type { ProjectId } from "@tutti/shared/ids";
3
+ type ProviderSetupStep = "base-url" | "api-key";
4
+ type ProviderFormState = {
5
+ step: ProviderSetupStep;
6
+ baseUrl: string;
7
+ apiKey: string;
8
+ error: string | undefined;
9
+ };
3
10
  type ProviderSetupLogEvent = {
4
11
  ts: string;
5
12
  level: "error";
@@ -17,6 +24,11 @@ export type ProviderSetupResult = {
17
24
  default_model: string;
18
25
  validated_at: string;
19
26
  };
27
+ export declare function renderProviderForm(options: {
28
+ state: ProviderFormState;
29
+ footer?: string;
30
+ tty?: boolean;
31
+ }): string;
20
32
  export declare function providerValidationFailureMessage(result: ConfigureProjectOpenAiProviderResult): string;
21
33
  export declare function writeProviderSetupValidationLog(options: {
22
34
  tuttiHome: string;