@xfey/tutti 0.1.16 → 0.1.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collaboration-state/run-recording.js +3 -0
- package/dist/collaboration-state/run-results.js +3 -0
- package/dist/collaboration-state/types.d.ts +1 -0
- package/dist/control-plane/index.js +3 -0
- package/dist/control-plane/task-compile-start.d.ts +3 -0
- package/dist/control-plane/task-compile-start.js +47 -11
- package/dist/control-plane/types.d.ts +1 -0
- package/dist/control-plane/worklist-terminal-feedback.d.ts +1 -0
- package/dist/control-plane/worklist-terminal-feedback.js +109 -0
- package/dist/run-pipeline/openai.js +6 -0
- package/dist/run-pipeline/promotion-reconcile.d.ts +2 -2
- package/dist/run-pipeline/promotion-reconcile.js +3 -0
- package/dist/run-pipeline/result-projections.d.ts +1 -0
- package/dist/run-pipeline/result-projections.js +3 -0
- package/dist/run-pipeline/task-run-output.d.ts +9 -3
- package/dist/run-pipeline/task-run-output.js +24 -4
- package/dist/server-shell/cli/cli.js +3 -22
- package/dist/server-shell/cli/machine-local.d.ts +4 -0
- package/dist/server-shell/cli/machine-local.js +15 -1
- package/dist/server-shell/cli/project-resolver.d.ts +8 -0
- package/dist/server-shell/cli/project-resolver.js +67 -5
- package/dist/server-shell/cli/provider-tui.d.ts +12 -0
- package/dist/server-shell/cli/provider-tui.js +15 -20
- package/dist/server-shell/cli/runtime-commands.d.ts +10 -1
- package/dist/server-shell/cli/runtime-commands.js +45 -7
- package/dist/server-shell/http/routes/project-api/openapi-execution-routes.d.ts +1 -0
- package/dist/server-shell/http/routes/project-api/openapi-viewer-routes.d.ts +2 -0
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +1 -0
- package/dist/server-shell/http/routes/project-api/reference-files.js +9 -3
- package/dist/workspace-ops/constants.d.ts +1 -0
- package/dist/workspace-ops/constants.js +1 -0
- package/dist/workspace-ops/index.d.ts +2 -2
- package/dist/workspace-ops/index.js +2 -2
- package/dist/workspace-ops/reference-metadata.d.ts +2 -0
- package/dist/workspace-ops/reference-metadata.js +26 -1
- package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +9 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
- package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +52 -0
- package/node_modules/@tutti/shared/dist/schemas/api/messages.js +10 -0
- package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +2 -1
- package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +14 -0
- package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.js +2 -0
- package/node_modules/@tutti/shared/dist/schemas/api/work-items.d.ts +3 -0
- package/node_modules/@tutti/shared/dist/schemas/api/work-items.js +2 -0
- package/package.json +1 -1
- package/prompts/prompt-flow-map.md +2 -2
- package/prompts/runs/README.md +2 -1
- package/prompts/runs/task-continuation.md +8 -2
- package/prompts/runs/task-retry.md +8 -2
- package/prompts/runs/task-run.md +8 -2
- package/web/assets/index-4JInvPEN.js +29 -0
- package/web/assets/index-P5uZBm0s.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/index-DoZOFmIq.js +0 -29
- package/web/assets/index-mXkNKFjm.css +0 -1
|
@@ -29,6 +29,9 @@ export function recordRunResult(db, input) {
|
|
|
29
29
|
};
|
|
30
30
|
if (input.result.result_kind === "finished") {
|
|
31
31
|
detailResult.outcome = input.result.outcome;
|
|
32
|
+
if (input.result.user_note_candidate !== undefined) {
|
|
33
|
+
detailResult.user_note_candidate = input.result.user_note_candidate;
|
|
34
|
+
}
|
|
32
35
|
detailResult.checks = input.result.checks;
|
|
33
36
|
detailResult.docs = input.result.docs;
|
|
34
37
|
if (input.result.skip_reason_code !== undefined) {
|
|
@@ -38,6 +38,9 @@ export function mapRunResultRow(row) {
|
|
|
38
38
|
result_kind: "finished",
|
|
39
39
|
outcome: result.outcome === "failed" ? "failed" : "completed",
|
|
40
40
|
summary: typeof result.summary === "string" ? result.summary : "",
|
|
41
|
+
...(typeof result.user_note_candidate === "string"
|
|
42
|
+
? { user_note_candidate: result.user_note_candidate }
|
|
43
|
+
: {}),
|
|
41
44
|
checks: result.checks === "failed" || result.checks === "skipped" ? result.checks : "passed",
|
|
42
45
|
docs: result.docs === "checked_no_change" ||
|
|
43
46
|
result.docs === "not_checked" ||
|
|
@@ -120,6 +120,7 @@ export type RecordRunResultInput = {
|
|
|
120
120
|
summary: string;
|
|
121
121
|
checks: "passed" | "failed" | "skipped";
|
|
122
122
|
docs: "updated" | "checked_no_change" | "not_checked" | "sync_deferred";
|
|
123
|
+
user_note_candidate?: string;
|
|
123
124
|
skip_reason_code?: TaskDetailResultProjection["skip_reason_code"];
|
|
124
125
|
skip_reason_summary?: string;
|
|
125
126
|
external_side_effects?: TaskDetailResultProjection["external_side_effects"];
|
|
@@ -313,6 +313,9 @@ export class Phase5ControlPlane {
|
|
|
313
313
|
emitWorklistCompletedFeedback({
|
|
314
314
|
store: this.store,
|
|
315
315
|
events: this.events,
|
|
316
|
+
...(this.projectContext === undefined
|
|
317
|
+
? {}
|
|
318
|
+
: { workspaceRoot: this.projectContext.workspaceRoot }),
|
|
316
319
|
now: this.now,
|
|
317
320
|
});
|
|
318
321
|
this.maybeStartContextSyncAfterWorklistCompletion();
|
|
@@ -51,7 +51,7 @@ export function startControlPlaneTaskCompile(options) {
|
|
|
51
51
|
sourceBatch,
|
|
52
52
|
});
|
|
53
53
|
}
|
|
54
|
-
if (scratchpad
|
|
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
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
|
190
|
-
|
|
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
|
|
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
|
|
@@ -123,6 +123,7 @@ export type RunPipelineResult = RunPipelineResultMetadata & ({
|
|
|
123
123
|
result_kind: "finished";
|
|
124
124
|
outcome: "completed" | "failed";
|
|
125
125
|
summary: string;
|
|
126
|
+
user_note_candidate?: string;
|
|
126
127
|
checks: "passed" | "failed" | "skipped";
|
|
127
128
|
docs: "updated" | "checked_no_change" | "not_checked" | "sync_deferred";
|
|
128
129
|
skip_reason_code?: NonNullable<TaskDetailProjection["detail"]["result"]["skip_reason_code"]>;
|
|
@@ -5,6 +5,7 @@ import type { WorkspaceEventBus } from "../server-shell/http/workspace-events.js
|
|
|
5
5
|
type EmitWorklistFeedbackInput = {
|
|
6
6
|
store: HostProjectStore;
|
|
7
7
|
events: WorkspaceEventBus;
|
|
8
|
+
workspaceRoot?: string;
|
|
8
9
|
now: () => Date;
|
|
9
10
|
};
|
|
10
11
|
export declare function emitWorklistCompletedFeedback(input: EmitWorklistFeedbackInput): MessageProjection | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readViewerFile, referenceFileSourceForPath, referenceMediaTypeForPath, referenceTuttiGroupForPath, } from "../workspace-ops/index.js";
|
|
1
2
|
import { createMainChatAgentMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readTaskDetailProjection, readWorklistProjection, } from "../collaboration-state/index.js";
|
|
2
3
|
import { mainChatInvalidates } from "./invalidations.js";
|
|
3
4
|
const BODY_ITEM_LIMIT = 12;
|
|
@@ -27,6 +28,100 @@ function feedbackItemFromEntry(entry) {
|
|
|
27
28
|
}
|
|
28
29
|
return item;
|
|
29
30
|
}
|
|
31
|
+
function userNoteCandidateFromEntry(entry) {
|
|
32
|
+
const trimmed = (entry.detail?.detail.result.user_note_candidate ?? "").trim();
|
|
33
|
+
return trimmed.length === 0 ? undefined : trimmed;
|
|
34
|
+
}
|
|
35
|
+
function normalizedUserNoteLine(value) {
|
|
36
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
37
|
+
}
|
|
38
|
+
function finalNoteForCompletedEntries(entries) {
|
|
39
|
+
const notes = entries
|
|
40
|
+
.map(userNoteCandidateFromEntry)
|
|
41
|
+
.filter((note) => note !== undefined)
|
|
42
|
+
.map(normalizedUserNoteLine)
|
|
43
|
+
.filter((note) => note.length > 0);
|
|
44
|
+
if (notes.length === 0) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
if (entries.length === 1) {
|
|
48
|
+
return notes[0];
|
|
49
|
+
}
|
|
50
|
+
return `Completed work across ${entries.length} tasks: ${notes.join(" ")}`;
|
|
51
|
+
}
|
|
52
|
+
function shouldInspectGeneratedFiles(entry) {
|
|
53
|
+
const result = entry.detail?.detail.result;
|
|
54
|
+
return (result?.result_kind === "finished" &&
|
|
55
|
+
result.outcome === "completed" &&
|
|
56
|
+
result.promotion?.status === "promoted" &&
|
|
57
|
+
Array.isArray(result.changed_paths));
|
|
58
|
+
}
|
|
59
|
+
function generatedFileProjection(file) {
|
|
60
|
+
const projection = {
|
|
61
|
+
path: file.path,
|
|
62
|
+
name: file.name,
|
|
63
|
+
size_bytes: file.size_bytes,
|
|
64
|
+
};
|
|
65
|
+
const group = referenceTuttiGroupForPath(file.path);
|
|
66
|
+
if (group !== undefined) {
|
|
67
|
+
projection.group = group;
|
|
68
|
+
}
|
|
69
|
+
const mediaType = referenceMediaTypeForPath(file.path);
|
|
70
|
+
if (mediaType !== undefined) {
|
|
71
|
+
projection.media_type = mediaType;
|
|
72
|
+
}
|
|
73
|
+
return projection;
|
|
74
|
+
}
|
|
75
|
+
function readGeneratedFile(input) {
|
|
76
|
+
if (referenceFileSourceForPath(input.path) !== "tutti_output") {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const file = readViewerFile({
|
|
81
|
+
workspaceRoot: input.workspaceRoot,
|
|
82
|
+
path: input.path,
|
|
83
|
+
maxBytes: 1,
|
|
84
|
+
now: input.now,
|
|
85
|
+
});
|
|
86
|
+
return generatedFileProjection(file.file);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function generatedFilesForCompletedEntries(input, entries) {
|
|
93
|
+
const workspaceRoot = input.workspaceRoot;
|
|
94
|
+
if (workspaceRoot === undefined) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const paths = new Set();
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (!shouldInspectGeneratedFiles(entry)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (const path of entry.detail?.detail.result.changed_paths ?? []) {
|
|
103
|
+
if (referenceFileSourceForPath(path) === "tutti_output") {
|
|
104
|
+
paths.add(path);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [...paths]
|
|
109
|
+
.map((path) => readGeneratedFile({
|
|
110
|
+
workspaceRoot,
|
|
111
|
+
path,
|
|
112
|
+
now: input.now,
|
|
113
|
+
}))
|
|
114
|
+
.filter((file) => file !== null)
|
|
115
|
+
.sort((left, right) => {
|
|
116
|
+
const groupOrder = (left.group ?? "").localeCompare(right.group ?? "", "en", {
|
|
117
|
+
sensitivity: "base",
|
|
118
|
+
});
|
|
119
|
+
if (groupOrder !== 0) {
|
|
120
|
+
return groupOrder;
|
|
121
|
+
}
|
|
122
|
+
return left.name.localeCompare(right.name, "en", { sensitivity: "base" });
|
|
123
|
+
});
|
|
124
|
+
}
|
|
30
125
|
function feedbackCounters(entries) {
|
|
31
126
|
const completed = entries.filter((entry) => entry.task.status === "done").length;
|
|
32
127
|
const failed = entries.filter((entry) => entry.task.status === "failed").length;
|
|
@@ -51,6 +146,16 @@ function taskCountLabel(count) {
|
|
|
51
146
|
}
|
|
52
147
|
function markdownFallback(feedback) {
|
|
53
148
|
const lines = [feedback.title, ""];
|
|
149
|
+
if (feedback.final_note !== undefined && feedback.final_note.trim().length > 0) {
|
|
150
|
+
lines.push(feedback.final_note.trim(), "");
|
|
151
|
+
}
|
|
152
|
+
if (feedback.generated_files !== undefined && feedback.generated_files.length > 0) {
|
|
153
|
+
lines.push("Generated files:");
|
|
154
|
+
for (const file of feedback.generated_files) {
|
|
155
|
+
lines.push(`- ${file.name}`);
|
|
156
|
+
}
|
|
157
|
+
lines.push("");
|
|
158
|
+
}
|
|
54
159
|
const visibleItems = feedback.items.slice(0, BODY_ITEM_LIMIT);
|
|
55
160
|
for (const item of visibleItems) {
|
|
56
161
|
const prefix = item.status === "done" ? "- Done" : "- Failed";
|
|
@@ -102,6 +207,8 @@ export function emitWorklistCompletedFeedback(input) {
|
|
|
102
207
|
return null;
|
|
103
208
|
}
|
|
104
209
|
const items = newCompletedEntries.map(feedbackItemFromEntry);
|
|
210
|
+
const finalNote = finalNoteForCompletedEntries(newCompletedEntries);
|
|
211
|
+
const generatedFiles = generatedFilesForCompletedEntries(input, newCompletedEntries);
|
|
105
212
|
const feedback = {
|
|
106
213
|
kind: "worklist_terminal",
|
|
107
214
|
status: "completed",
|
|
@@ -110,6 +217,8 @@ export function emitWorklistCompletedFeedback(input) {
|
|
|
110
217
|
feedback_key: completedFeedbackKey(items),
|
|
111
218
|
counters: feedbackCounters(newCompletedEntries),
|
|
112
219
|
items,
|
|
220
|
+
...(finalNote === undefined ? {} : { final_note: finalNote }),
|
|
221
|
+
...(generatedFiles.length === 0 ? {} : { generated_files: generatedFiles }),
|
|
113
222
|
};
|
|
114
223
|
return publishFeedbackMessage(input, feedback);
|
|
115
224
|
}
|
|
@@ -332,6 +332,9 @@ async function runTaskAttempt(options, input) {
|
|
|
332
332
|
if (changedPaths.length === 0 && taskOutput.decision === "completed_no_repo_changes") {
|
|
333
333
|
return completedWithoutRepoChanges({
|
|
334
334
|
summary: taskOutput.summary,
|
|
335
|
+
...(taskOutput.user_note_candidate === undefined
|
|
336
|
+
? {}
|
|
337
|
+
: { userNoteCandidate: taskOutput.user_note_candidate }),
|
|
335
338
|
});
|
|
336
339
|
}
|
|
337
340
|
if (changedPaths.length === 0) {
|
|
@@ -424,6 +427,9 @@ async function runTaskAttempt(options, input) {
|
|
|
424
427
|
checks,
|
|
425
428
|
completion: "Promotion complete.",
|
|
426
429
|
}),
|
|
430
|
+
...(taskOutput.user_note_candidate === undefined
|
|
431
|
+
? {}
|
|
432
|
+
: { user_note_candidate: taskOutput.user_note_candidate }),
|
|
427
433
|
checks: checkStatus(checks),
|
|
428
434
|
docs,
|
|
429
435
|
...checkSkipFields(checks),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { RunPipelineInput, RunPipelineResult } from "../control-plane/index.js";
|
|
2
2
|
import { type ResultCommit, type RunWorkspaceHandle } from "../workspace-ops/index.js";
|
|
3
3
|
import type { OpenAiRunPipelineRunnerOptions } from "./openai.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type { CompletedNoRepoChangesTaskRunOutput, CompletedTaskRunOutput } from "./task-run-output.js";
|
|
5
5
|
export declare function runPromotionReconcile(input: {
|
|
6
6
|
options: OpenAiRunPipelineRunnerOptions;
|
|
7
7
|
pipelineInput: RunPipelineInput;
|
|
8
8
|
runHandle: RunWorkspaceHandle;
|
|
9
|
-
taskOutput:
|
|
9
|
+
taskOutput: CompletedTaskRunOutput | CompletedNoRepoChangesTaskRunOutput;
|
|
10
10
|
sourceCommit: ResultCommit;
|
|
11
11
|
changedPaths: string[];
|
|
12
12
|
}): Promise<RunPipelineResult>;
|
|
@@ -93,6 +93,9 @@ export async function runPromotionReconcile(input) {
|
|
|
93
93
|
checks,
|
|
94
94
|
completion: "Promotion reconciled and complete.",
|
|
95
95
|
}),
|
|
96
|
+
...(input.taskOutput.user_note_candidate === undefined
|
|
97
|
+
? {}
|
|
98
|
+
: { user_note_candidate: input.taskOutput.user_note_candidate }),
|
|
96
99
|
checks: checkStatus(checks),
|
|
97
100
|
docs,
|
|
98
101
|
...checkSkipFields(checks),
|
|
@@ -35,5 +35,6 @@ export declare function failedBeforeChecks(input: {
|
|
|
35
35
|
}): RunPipelineResult;
|
|
36
36
|
export declare function completedWithoutRepoChanges(input: {
|
|
37
37
|
summary: string;
|
|
38
|
+
userNoteCandidate?: string;
|
|
38
39
|
}): RunPipelineResult;
|
|
39
40
|
//# sourceMappingURL=result-projections.d.ts.map
|
|
@@ -104,6 +104,9 @@ export function completedWithoutRepoChanges(input) {
|
|
|
104
104
|
result_kind: "finished",
|
|
105
105
|
outcome: "completed",
|
|
106
106
|
summary: input.summary,
|
|
107
|
+
...(input.userNoteCandidate === undefined
|
|
108
|
+
? {}
|
|
109
|
+
: { user_note_candidate: input.userNoteCandidate }),
|
|
107
110
|
checks: "skipped",
|
|
108
111
|
docs: "checked_no_change",
|
|
109
112
|
skip_reason_code: "validation_not_applicable",
|
|
@@ -5,10 +5,12 @@ export type InitialTaskRunOutput = {
|
|
|
5
5
|
result: {
|
|
6
6
|
completed: {
|
|
7
7
|
summary: string;
|
|
8
|
+
user_note_candidate: string | null;
|
|
8
9
|
};
|
|
9
10
|
} | {
|
|
10
11
|
completed_no_repo_changes: {
|
|
11
12
|
summary: string;
|
|
13
|
+
user_note_candidate: string | null;
|
|
12
14
|
};
|
|
13
15
|
} | {
|
|
14
16
|
needs_human: ClarificationRequestPayload;
|
|
@@ -18,13 +20,17 @@ export type InitialTaskRunOutput = {
|
|
|
18
20
|
};
|
|
19
21
|
};
|
|
20
22
|
};
|
|
21
|
-
export type
|
|
23
|
+
export type CompletedTaskRunOutput = {
|
|
22
24
|
decision: "completed";
|
|
23
25
|
summary: string;
|
|
24
|
-
|
|
26
|
+
user_note_candidate?: string;
|
|
27
|
+
};
|
|
28
|
+
export type CompletedNoRepoChangesTaskRunOutput = {
|
|
25
29
|
decision: "completed_no_repo_changes";
|
|
26
30
|
summary: string;
|
|
27
|
-
|
|
31
|
+
user_note_candidate?: string;
|
|
32
|
+
};
|
|
33
|
+
export type TaskRunOutput = CompletedTaskRunOutput | CompletedNoRepoChangesTaskRunOutput | {
|
|
28
34
|
decision: "needs_human";
|
|
29
35
|
summary: string;
|
|
30
36
|
clarification_request_payload: ClarificationRequestPayload;
|
|
@@ -22,10 +22,13 @@ export const TASK_RUN_OUTPUT_SCHEMA = {
|
|
|
22
22
|
properties: {
|
|
23
23
|
completed: {
|
|
24
24
|
type: "object",
|
|
25
|
-
required: ["summary"],
|
|
25
|
+
required: ["summary", "user_note_candidate"],
|
|
26
26
|
additionalProperties: false,
|
|
27
27
|
properties: {
|
|
28
28
|
summary: { type: "string" },
|
|
29
|
+
user_note_candidate: {
|
|
30
|
+
anyOf: [{ type: "string" }, { type: "null" }],
|
|
31
|
+
},
|
|
29
32
|
},
|
|
30
33
|
},
|
|
31
34
|
},
|
|
@@ -37,10 +40,13 @@ export const TASK_RUN_OUTPUT_SCHEMA = {
|
|
|
37
40
|
properties: {
|
|
38
41
|
completed_no_repo_changes: {
|
|
39
42
|
type: "object",
|
|
40
|
-
required: ["summary"],
|
|
43
|
+
required: ["summary", "user_note_candidate"],
|
|
41
44
|
additionalProperties: false,
|
|
42
45
|
properties: {
|
|
43
46
|
summary: { type: "string" },
|
|
47
|
+
user_note_candidate: {
|
|
48
|
+
anyOf: [{ type: "string" }, { type: "null" }],
|
|
49
|
+
},
|
|
44
50
|
},
|
|
45
51
|
},
|
|
46
52
|
},
|
|
@@ -96,6 +102,16 @@ function isSummaryRecord(value) {
|
|
|
96
102
|
Object.keys(value).every((key) => key === "summary") &&
|
|
97
103
|
typeof value.summary === "string");
|
|
98
104
|
}
|
|
105
|
+
function isCompletedRecord(value) {
|
|
106
|
+
return (isRecord(value) &&
|
|
107
|
+
Object.keys(value).every((key) => key === "summary" || key === "user_note_candidate") &&
|
|
108
|
+
typeof value.summary === "string" &&
|
|
109
|
+
(typeof value.user_note_candidate === "string" || value.user_note_candidate === null));
|
|
110
|
+
}
|
|
111
|
+
function normalizedUserNoteCandidate(value) {
|
|
112
|
+
const trimmed = (value ?? "").trim();
|
|
113
|
+
return trimmed.length === 0 ? undefined : trimmed;
|
|
114
|
+
}
|
|
99
115
|
export function isInitialTaskRunOutput(value) {
|
|
100
116
|
if (!isRecord(value) || !isRecord(value.result) || Object.keys(value).length !== 1) {
|
|
101
117
|
return false;
|
|
@@ -105,10 +121,10 @@ export function isInitialTaskRunOutput(value) {
|
|
|
105
121
|
return false;
|
|
106
122
|
}
|
|
107
123
|
if ("completed" in value.result) {
|
|
108
|
-
return
|
|
124
|
+
return isCompletedRecord(value.result.completed);
|
|
109
125
|
}
|
|
110
126
|
if ("completed_no_repo_changes" in value.result) {
|
|
111
|
-
return
|
|
127
|
+
return isCompletedRecord(value.result.completed_no_repo_changes);
|
|
112
128
|
}
|
|
113
129
|
if ("needs_human" in value.result) {
|
|
114
130
|
return isTaskRunClarificationRequestPayload(value.result.needs_human);
|
|
@@ -120,15 +136,19 @@ export function isInitialTaskRunOutput(value) {
|
|
|
120
136
|
}
|
|
121
137
|
export function normalizeTaskRunOutput(output) {
|
|
122
138
|
if ("completed" in output.result) {
|
|
139
|
+
const userNoteCandidate = normalizedUserNoteCandidate(output.result.completed.user_note_candidate);
|
|
123
140
|
return {
|
|
124
141
|
decision: "completed",
|
|
125
142
|
summary: output.result.completed.summary,
|
|
143
|
+
...(userNoteCandidate === undefined ? {} : { user_note_candidate: userNoteCandidate }),
|
|
126
144
|
};
|
|
127
145
|
}
|
|
128
146
|
if ("completed_no_repo_changes" in output.result) {
|
|
147
|
+
const userNoteCandidate = normalizedUserNoteCandidate(output.result.completed_no_repo_changes.user_note_candidate);
|
|
129
148
|
return {
|
|
130
149
|
decision: "completed_no_repo_changes",
|
|
131
150
|
summary: output.result.completed_no_repo_changes.summary,
|
|
151
|
+
...(userNoteCandidate === undefined ? {} : { user_note_candidate: userNoteCandidate }),
|
|
132
152
|
};
|
|
133
153
|
}
|
|
134
154
|
if ("needs_human" in output.result) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { formatCliError
|
|
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
|