@xfey/tutti 0.1.98 → 0.1.99
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/README.md +2 -1
- package/dist/artifact-feedback/index.d.ts +19 -0
- package/dist/artifact-feedback/index.js +196 -0
- package/dist/chat-assistant/index.js +5 -4
- package/dist/collaboration-state/task-compile-context.d.ts +1 -1
- package/dist/control-plane/scratchpad-source-messages.js +2 -1
- package/dist/control-plane/task-source-context.js +3 -2
- package/dist/server-shell/http/routes/project-api/artifacts-routes.d.ts +2 -1
- package/dist/server-shell/http/routes/project-api/artifacts-routes.js +1 -0
- package/dist/server-shell/http/routes/project-api/messages-routes.d.ts +3 -1
- package/dist/server-shell/http/routes/project-api/messages-routes.js +61 -29
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +8 -2
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +8 -2
- package/dist/server-shell/http/routes/project-api/payload-validation.js +12 -0
- package/dist/server-shell/http/routes/project-api/schemas.d.ts +8 -2
- package/dist/server-shell/http/routes/project-api/schemas.js +2 -0
- package/dist/server-shell/http/routes/project-api/types.d.ts +1 -0
- package/dist/server-shell/http/routes/project-api/workspace-projections.js +1 -0
- package/dist/server-shell/http/routes/project-api.js +2 -2
- package/node_modules/@tutti/shared/dist/schemas/api/artifact-feedback.d.ts +374 -0
- package/node_modules/@tutti/shared/dist/schemas/api/artifact-feedback.js +83 -0
- package/node_modules/@tutti/shared/dist/schemas/api/clarifications.d.ts +86 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +2 -1
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +2 -1
- package/node_modules/@tutti/shared/dist/schemas/api/messages.d.ts +441 -2
- package/node_modules/@tutti/shared/dist/schemas/api/messages.js +17 -3
- package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +13 -1
- package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.d.ts +86 -0
- package/package.json +1 -1
- package/web/assets/homepage-motion-scene-BtMS4SCZ.js +1 -0
- package/web/assets/index-DtZ_5QLQ.js +69 -0
- package/web/assets/index-JY06YzFV.css +1 -0
- package/web/index.html +2 -2
- package/web/assets/homepage-motion-scene-_nsq44Ph.js +0 -1
- package/web/assets/index-BtTlbKEP.js +0 -69
- package/web/assets/index-quWLFJgD.css +0 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
## 当前状态
|
|
33
33
|
|
|
34
|
-
- 已具备 Fastify host shell、后台 host manager CLI 套件、用户级 desktop integration / strict `tutti://projects` bridge、带 `service status / stop / restart`、卸载前 `shutdown`、并发启动锁、版本替换、strict terminal / desktop browser context、当前 runtime version bootstrap projection、idle / package update cleanup、全机readiness、exact npm global install、跨版本worker / recovery与私有日志的机器级 Local Console、Dev Runner、Host Project API、SSE、SQLite store、collaboration-state、workspace read-state、project timeline observability helper、Control Plane、Procedure / Run、workspace-ops、checks、Relay staged upload finalize、user skills 文件服务、artifacts 可视化产物查看层、Owner-only provider model discovery / update 和 OpenAI / Codex provider 接入。
|
|
34
|
+
- 已具备 Fastify host shell、后台 host manager CLI 套件、用户级 desktop integration / strict `tutti://projects` bridge、带 `service status / stop / restart`、卸载前 `shutdown`、并发启动锁、版本替换、strict terminal / desktop browser context、当前 runtime version bootstrap projection、idle / package update cleanup、全机readiness、exact npm global install、跨版本worker / recovery与私有日志的机器级 Local Console、Dev Runner、Host Project API、SSE、SQLite store、collaboration-state、workspace read-state、project timeline observability helper、Control Plane、Procedure / Run、workspace-ops、checks、Relay staged upload finalize、user skills 文件服务、artifacts 可视化产物查看层、current-generation Artifact feedback message / model formatter、Owner-only provider model discovery / update 和 OpenAI / Codex provider 接入。
|
|
35
35
|
- 新 Host project 的空群聊会由 collaboration-state 幂等写入一条 Tutti 欢迎消息;它不经过 browser message command,不触发 `@tutti`、Chat read-state、Scratchpad 或 Timeline / execution 流程。
|
|
36
36
|
- Host server 与 Local Console 支持在显式 `webStatic.root` 或 packaged `web/` 存在时服务 Web 静态产物;`/api/*`、SSE、health、host-local control 和 `/local-console/*` API 不被 SPA fallback 吞掉。
|
|
37
37
|
- Root `pack:dry-run` 会先清理 build output、重新构建,再复制 `apps/web/dist` 到 package-local `web/`,生成临时 staging `@xfey/tutti` artifact,并校验 npm pack 文件列表包含 CLI、migrations、prompts、Web index、bundled `@tutti/shared` / `@tutti/relay-client` 以及它们的外部 runtime dependencies,同时排除源码、source map、日志、SQLite、`.env`、`.tutti-dev` 与测试产物。
|
|
@@ -55,6 +55,7 @@
|
|
|
55
55
|
- 普通 `tutti launch` 默认后台运行 host,CLI 完成页展示 join URL 后直接退出;`tutti launch --foreground` 保留前台诊断模式。Host HTTP request log 与 Control Plane / Run Pipeline runtime log 写入 `TUTTI_HOME/logs/host.log`,不写入目标项目 repo 或常规 CLI stdout。
|
|
56
56
|
- Reference 和 Skills 上传的最终写入由 Host Project API 完成;Host 只通过 Relay host-control resolve 获取短期 R2 URL 下载 staged object,不接收浏览器 base64 文件正文,也不持久化 presigned URL。
|
|
57
57
|
- Host Project API command 的 browser session context 只信任 Relay tunnel metadata 中的 `relay_session_context`;route handler 不读取浏览器身份 header,也不接受 payload 内身份字段。
|
|
58
|
+
- Artifact feedback 仍写入现有 `messages` truth并复用command idempotency;Host只在当前ready generation完全匹配时接受,Anchor技术字段仅保留审计ref,模型上下文使用bounded semantic formatter。
|
|
58
59
|
- Fastify request log、debug log、SSE payload、activity event 和 run result 都必须经过 redaction,不得泄露 provider secret、host registration secret、host connection token、join token、cookie、host 绝对路径或 run workspace path。
|
|
59
60
|
- Repo path metadata policy 与更严格的 Viewer content path policy 已拆分为可测试 helper:Phase 6 Viewer / Reference Files API 继续拒绝 secret-like 内容读取,Run / merge changed paths 可以完整报告结构性安全的 repo-relative 文件名;Phase 7 checks executor 已以 safe npm script discovery、结构化 `spawn`、timeout、stdout / stderr redaction 与 no-shell 执行为首批命令安全边界。
|
|
60
61
|
- 当前源码 package root 仍是 `apps/server`;对外 npm 名由 staging artifact 使用 `@xfey/tutti`,internal workspace dependency bundling、对应外部 runtime dependency bundling 和 packed CLI install smoke 已接通。
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ArtifactFeedbackCommand, type ArtifactFeedbackRef, type ArtifactSemanticSnapshot, type MessageProjection } from "@tutti/shared/schemas/api";
|
|
2
|
+
export declare class ArtifactFeedbackValidationError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare function normalizeArtifactFeedbackCommand(input: ArtifactFeedbackCommand): ArtifactFeedbackCommand;
|
|
6
|
+
export declare function describeArtifactTarget(snapshot: ArtifactSemanticSnapshot): string;
|
|
7
|
+
export declare function createArtifactFeedbackMessage(options: {
|
|
8
|
+
artifactTitle: string;
|
|
9
|
+
feedback: ArtifactFeedbackCommand;
|
|
10
|
+
}): {
|
|
11
|
+
body: string;
|
|
12
|
+
ref: ArtifactFeedbackRef;
|
|
13
|
+
};
|
|
14
|
+
export declare function formatArtifactFeedbackForModel(ref: ArtifactFeedbackRef, options?: {
|
|
15
|
+
maxChars?: number;
|
|
16
|
+
}): string;
|
|
17
|
+
export declare function messageTextForModel(message: MessageProjection): string;
|
|
18
|
+
export declare function messageTextForTuttiMention(message: MessageProjection): string;
|
|
19
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { Value } from "@sinclair/typebox/value";
|
|
2
|
+
import { ARTIFACT_FEEDBACK_MAX_AUTHORED_TEXT_LENGTH, ARTIFACT_FEEDBACK_MAX_SERIALIZED_BYTES, ArtifactFeedbackCommandSchema, } from "@tutti/shared/schemas/api";
|
|
3
|
+
import { redactText, redactValue } from "@tutti/shared/utils";
|
|
4
|
+
export class ArtifactFeedbackValidationError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "ArtifactFeedbackValidationError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function normalizedLine(value) {
|
|
11
|
+
return value.trim().replace(/\s+/gu, " ");
|
|
12
|
+
}
|
|
13
|
+
function validateDocumentUrl(value) {
|
|
14
|
+
const normalized = value.trim();
|
|
15
|
+
let parsed;
|
|
16
|
+
try {
|
|
17
|
+
parsed = new URL(normalized, "https://artifact.invalid");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new ArtifactFeedbackValidationError("Artifact feedback page URL is invalid");
|
|
21
|
+
}
|
|
22
|
+
if (parsed.origin !== "https://artifact.invalid" || !normalized.startsWith("/")) {
|
|
23
|
+
throw new ArtifactFeedbackValidationError("Artifact feedback page URL must be relative");
|
|
24
|
+
}
|
|
25
|
+
if (redactText(normalized) !== normalized) {
|
|
26
|
+
throw new ArtifactFeedbackValidationError("Artifact feedback page URL contains restricted data");
|
|
27
|
+
}
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
export function normalizeArtifactFeedbackCommand(input) {
|
|
31
|
+
for (const annotation of input.annotations) {
|
|
32
|
+
validateDocumentUrl(annotation.document_url);
|
|
33
|
+
}
|
|
34
|
+
const redacted = redactValue(input);
|
|
35
|
+
if (!Value.Check(ArtifactFeedbackCommandSchema, redacted)) {
|
|
36
|
+
throw new ArtifactFeedbackValidationError("Artifact feedback payload is invalid");
|
|
37
|
+
}
|
|
38
|
+
const candidate = redacted;
|
|
39
|
+
const summary = candidate.summary === undefined ? undefined : normalizedLine(candidate.summary);
|
|
40
|
+
if (candidate.summary !== undefined && summary === "") {
|
|
41
|
+
throw new ArtifactFeedbackValidationError("Artifact feedback summary cannot be empty");
|
|
42
|
+
}
|
|
43
|
+
const annotations = candidate.annotations.map((annotation) => {
|
|
44
|
+
const comment = annotation.comment.trim();
|
|
45
|
+
if (comment === "") {
|
|
46
|
+
throw new ArtifactFeedbackValidationError("Artifact feedback comment cannot be empty");
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
document_url: validateDocumentUrl(annotation.document_url),
|
|
50
|
+
comment,
|
|
51
|
+
target: annotation.target,
|
|
52
|
+
};
|
|
53
|
+
});
|
|
54
|
+
const authoredLength = (summary?.length ?? 0) + annotations.reduce((total, item) => total + item.comment.length, 0);
|
|
55
|
+
if (authoredLength > ARTIFACT_FEEDBACK_MAX_AUTHORED_TEXT_LENGTH) {
|
|
56
|
+
throw new ArtifactFeedbackValidationError(`Artifact feedback text cannot exceed ${ARTIFACT_FEEDBACK_MAX_AUTHORED_TEXT_LENGTH} characters`);
|
|
57
|
+
}
|
|
58
|
+
const normalized = {
|
|
59
|
+
expected_preview_generation: candidate.expected_preview_generation.trim(),
|
|
60
|
+
...(summary === undefined ? {} : { summary }),
|
|
61
|
+
annotations,
|
|
62
|
+
};
|
|
63
|
+
if (normalized.expected_preview_generation === "" ||
|
|
64
|
+
Buffer.byteLength(JSON.stringify(normalized), "utf8") >
|
|
65
|
+
ARTIFACT_FEEDBACK_MAX_SERIALIZED_BYTES) {
|
|
66
|
+
throw new ArtifactFeedbackValidationError("Artifact feedback payload is too large");
|
|
67
|
+
}
|
|
68
|
+
return normalized;
|
|
69
|
+
}
|
|
70
|
+
function boundedMiddle(value, maxLength) {
|
|
71
|
+
const normalized = normalizedLine(value);
|
|
72
|
+
if (normalized.length <= maxLength) {
|
|
73
|
+
return normalized;
|
|
74
|
+
}
|
|
75
|
+
if (maxLength < 8) {
|
|
76
|
+
return normalized.slice(0, maxLength);
|
|
77
|
+
}
|
|
78
|
+
const head = Math.ceil((maxLength - 1) / 2);
|
|
79
|
+
const tail = Math.floor((maxLength - 1) / 2);
|
|
80
|
+
return `${normalized.slice(0, head)}…${normalized.slice(-tail)}`;
|
|
81
|
+
}
|
|
82
|
+
export function describeArtifactTarget(snapshot) {
|
|
83
|
+
const kind = snapshot.role ?? snapshot.tag_name;
|
|
84
|
+
const name = snapshot.accessible_name ?? snapshot.text_excerpt;
|
|
85
|
+
const direct = name === undefined ? kind : `${kind} “${boundedMiddle(name, 140)}”`;
|
|
86
|
+
const context = snapshot.context[0];
|
|
87
|
+
if (context === undefined) {
|
|
88
|
+
return direct;
|
|
89
|
+
}
|
|
90
|
+
const contextKind = context.role ?? context.tag_name;
|
|
91
|
+
const contextName = context.accessible_name;
|
|
92
|
+
return contextName === undefined
|
|
93
|
+
? `${direct}, inside ${contextKind}`
|
|
94
|
+
: `${direct}, inside ${contextKind} “${boundedMiddle(contextName, 100)}”`;
|
|
95
|
+
}
|
|
96
|
+
function describeArtifactState(snapshot) {
|
|
97
|
+
if (snapshot.state === undefined) {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
const labels = Object.entries(snapshot.state).map(([key, value]) => `${key}=${String(value)}`);
|
|
101
|
+
return labels.length === 0 ? undefined : labels.join(", ");
|
|
102
|
+
}
|
|
103
|
+
function buildMessageBody(ref, authoredBudget) {
|
|
104
|
+
const summaryBudget = Math.min(800, Math.floor(authoredBudget * 0.2));
|
|
105
|
+
const summary = ref.summary === undefined ? undefined : boundedMiddle(ref.summary, summaryBudget);
|
|
106
|
+
const remaining = authoredBudget - (summary?.length ?? 0);
|
|
107
|
+
const commentBudget = Math.max(120, Math.floor(remaining / ref.annotations.length));
|
|
108
|
+
const lines = [`Artifact feedback: ${normalizedLine(ref.artifact_title)}`];
|
|
109
|
+
if (summary !== undefined) {
|
|
110
|
+
lines.push(`Overall: ${summary}`);
|
|
111
|
+
}
|
|
112
|
+
for (const [index, annotation] of ref.annotations.entries()) {
|
|
113
|
+
lines.push("", `${index + 1}. Feedback: ${boundedMiddle(annotation.comment, commentBudget)}`, ` Target: ${boundedMiddle(describeArtifactTarget(annotation.target.snapshot), 220)}`, ` Page: ${boundedMiddle(annotation.document_url, 160)}`);
|
|
114
|
+
const state = describeArtifactState(annotation.target.snapshot);
|
|
115
|
+
if (state !== undefined) {
|
|
116
|
+
lines.push(` State: ${boundedMiddle(state, 100)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
export function createArtifactFeedbackMessage(options) {
|
|
122
|
+
const ref = {
|
|
123
|
+
kind: "artifact_feedback",
|
|
124
|
+
version: 1,
|
|
125
|
+
preview_generation: options.feedback.expected_preview_generation,
|
|
126
|
+
artifact_title: options.artifactTitle,
|
|
127
|
+
inspector_version: 1,
|
|
128
|
+
...(options.feedback.summary === undefined ? {} : { summary: options.feedback.summary }),
|
|
129
|
+
annotations: options.feedback.annotations,
|
|
130
|
+
};
|
|
131
|
+
for (let budget = ARTIFACT_FEEDBACK_MAX_AUTHORED_TEXT_LENGTH; budget >= 2_000; budget -= 250) {
|
|
132
|
+
const body = buildMessageBody(ref, budget);
|
|
133
|
+
if (body.length <= 8_000) {
|
|
134
|
+
return { body, ref };
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const body = buildMessageBody(ref, 1_500);
|
|
138
|
+
if (body.length > 8_000) {
|
|
139
|
+
throw new ArtifactFeedbackValidationError("Artifact feedback message is too large");
|
|
140
|
+
}
|
|
141
|
+
return { body, ref };
|
|
142
|
+
}
|
|
143
|
+
function semanticModelText(snapshot, maxLength) {
|
|
144
|
+
const fields = [
|
|
145
|
+
`Target: ${snapshot.element_kind}/${snapshot.tag_name}${snapshot.role === undefined ? "" : ` role=${snapshot.role}`}${snapshot.accessible_name === undefined
|
|
146
|
+
? ""
|
|
147
|
+
: ` name=“${normalizedLine(snapshot.accessible_name)}”`}`,
|
|
148
|
+
];
|
|
149
|
+
if (snapshot.text_excerpt !== undefined) {
|
|
150
|
+
fields.push(`Text: ${normalizedLine(snapshot.text_excerpt)}`);
|
|
151
|
+
}
|
|
152
|
+
if (snapshot.context.length > 0) {
|
|
153
|
+
fields.push(`Context: ${snapshot.context
|
|
154
|
+
.map((item) => [item.role ?? item.tag_name, item.accessible_name].filter(Boolean).join(" “") +
|
|
155
|
+
(item.accessible_name === undefined ? "" : "”"))
|
|
156
|
+
.join(" > ")}`);
|
|
157
|
+
}
|
|
158
|
+
const state = describeArtifactState(snapshot);
|
|
159
|
+
if (state !== undefined) {
|
|
160
|
+
fields.push(`State: ${state}`);
|
|
161
|
+
}
|
|
162
|
+
const perField = Math.max(16, Math.floor(maxLength / fields.length));
|
|
163
|
+
return fields.map((field) => boundedMiddle(field, perField)).join("; ");
|
|
164
|
+
}
|
|
165
|
+
export function formatArtifactFeedbackForModel(ref, options = {}) {
|
|
166
|
+
const maxChars = Math.max(800, options.maxChars ?? 2_000);
|
|
167
|
+
const header = `Artifact feedback: ${boundedMiddle(ref.artifact_title, 120)}`;
|
|
168
|
+
const overall = ref.summary === undefined
|
|
169
|
+
? undefined
|
|
170
|
+
: `Overall: ${boundedMiddle(ref.summary, Math.min(320, Math.floor(maxChars * 0.16)))}`;
|
|
171
|
+
const fixedLength = header.length + (overall?.length ?? 0) + 2;
|
|
172
|
+
const perAnnotation = Math.max(80, Math.floor((maxChars - fixedLength - ref.annotations.length * 2) / ref.annotations.length));
|
|
173
|
+
const annotations = ref.annotations.map((annotation, index) => {
|
|
174
|
+
const routeBudget = Math.max(24, Math.floor(perAnnotation * 0.18));
|
|
175
|
+
const semanticBudget = Math.max(36, Math.floor(perAnnotation * 0.42));
|
|
176
|
+
const prefix = `${index + 1}. ${boundedMiddle(annotation.document_url, routeBudget)} | `;
|
|
177
|
+
const semantic = semanticModelText(annotation.target.snapshot, semanticBudget);
|
|
178
|
+
const commentBudget = Math.max(24, perAnnotation - prefix.length - semantic.length - 13);
|
|
179
|
+
return `${prefix}${semantic} | Feedback: ${boundedMiddle(annotation.comment, commentBudget)}`;
|
|
180
|
+
});
|
|
181
|
+
return [header, ...(overall === undefined ? [] : [overall]), ...annotations].join("\n");
|
|
182
|
+
}
|
|
183
|
+
export function messageTextForModel(message) {
|
|
184
|
+
const feedback = message.refs?.artifact_feedback;
|
|
185
|
+
return feedback === undefined ? message.body : formatArtifactFeedbackForModel(feedback);
|
|
186
|
+
}
|
|
187
|
+
export function messageTextForTuttiMention(message) {
|
|
188
|
+
const feedback = message.refs?.artifact_feedback;
|
|
189
|
+
if (feedback === undefined) {
|
|
190
|
+
return message.body;
|
|
191
|
+
}
|
|
192
|
+
return [feedback.summary, ...feedback.annotations.map((annotation) => annotation.comment)]
|
|
193
|
+
.filter((value) => value !== undefined)
|
|
194
|
+
.join("\n");
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createClarificationRoundAgentMessage, createMainChatAgentMessage, readClarificationRoundMessagesWindow, readClarificationRoundProjection, readMainChatMessageWindow, readMainChatMessagesBeforeCursor, readMessageProjectionById, } from "../collaboration-state/index.js";
|
|
2
2
|
import { renderPromptTemplate } from "../prompt-templates/index.js";
|
|
3
|
+
import { messageTextForModel, messageTextForTuttiMention, } from "../artifact-feedback/index.js";
|
|
3
4
|
const CHAT_ASSISTANT_CONTEXT_MESSAGE_LIMIT = 3;
|
|
4
5
|
const CHAT_ASSISTANT_ROUND_MESSAGE_LIMIT = 50;
|
|
5
6
|
const CHAT_ASSISTANT_ROUND_MESSAGE_MAX_CHARS = 2_000;
|
|
@@ -51,7 +52,7 @@ function messageToContext(message) {
|
|
|
51
52
|
...(authorDisplayName === undefined || authorDisplayName === ""
|
|
52
53
|
? {}
|
|
53
54
|
: { author_display_name: authorDisplayName }),
|
|
54
|
-
text: message
|
|
55
|
+
text: messageTextForModel(message),
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
function messageRole(message) {
|
|
@@ -107,7 +108,7 @@ export class ReadOnlyChatAssistant {
|
|
|
107
108
|
this.maxQueuedMentions = options.maxQueuedMentions ?? 8;
|
|
108
109
|
}
|
|
109
110
|
enqueue(input) {
|
|
110
|
-
if (!containsTuttiMention(input.message
|
|
111
|
+
if (!containsTuttiMention(messageTextForTuttiMention(input.message))) {
|
|
111
112
|
return { triggered: false };
|
|
112
113
|
}
|
|
113
114
|
if (!this.accepting) {
|
|
@@ -179,7 +180,7 @@ export class ReadOnlyChatAssistant {
|
|
|
179
180
|
...(authorDisplayName === undefined || authorDisplayName === ""
|
|
180
181
|
? {}
|
|
181
182
|
: { author_display_name: authorDisplayName }),
|
|
182
|
-
text: triggerMessage
|
|
183
|
+
text: messageTextForModel(triggerMessage),
|
|
183
184
|
},
|
|
184
185
|
recent_messages: readMainChatMessageWindow(this.options.store.db, {
|
|
185
186
|
limit: CHAT_ASSISTANT_CONTEXT_MESSAGE_LIMIT,
|
|
@@ -213,7 +214,7 @@ export class ReadOnlyChatAssistant {
|
|
|
213
214
|
...(authorDisplayName === undefined || authorDisplayName === ""
|
|
214
215
|
? {}
|
|
215
216
|
: { author_display_name: authorDisplayName }),
|
|
216
|
-
text: triggerMessage
|
|
217
|
+
text: messageTextForModel(triggerMessage),
|
|
217
218
|
},
|
|
218
219
|
round_request: {
|
|
219
220
|
...(round.request_payload.title === undefined
|
|
@@ -16,12 +16,12 @@ export declare function completeTaskCompileContext(db: SqliteDatabase, input: Co
|
|
|
16
16
|
submitted_by_activity_ref?: string;
|
|
17
17
|
submitted_by_workflow_ref?: string;
|
|
18
18
|
id: "current";
|
|
19
|
+
state: "ready" | "empty" | "submitted";
|
|
19
20
|
topic: string;
|
|
20
21
|
background_summary: string;
|
|
21
22
|
current_consensus: string[];
|
|
22
23
|
open_questions: string[];
|
|
23
24
|
task_changes: string[];
|
|
24
|
-
state: "ready" | "empty" | "submitted";
|
|
25
25
|
receipts: {
|
|
26
26
|
compiled_tasks?: {
|
|
27
27
|
status: "failed" | "done" | "todo" | "running" | "pending";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { messageTextForModel } from "../artifact-feedback/index.js";
|
|
1
2
|
const MAX_SOURCE_MESSAGE_TEXT_CHARS = 2_000;
|
|
2
3
|
export const MAX_SOURCE_MESSAGES_TOTAL_CHARS = 10_000;
|
|
3
4
|
export const MIN_SOURCE_MESSAGE_TEXT_CHARS = 160;
|
|
@@ -48,7 +49,7 @@ export function truncateTextMiddle(text, maxChars) {
|
|
|
48
49
|
function sourceText(message) {
|
|
49
50
|
const reference = message.refs?.reference_file;
|
|
50
51
|
if (reference === undefined) {
|
|
51
|
-
return message
|
|
52
|
+
return messageTextForModel(message);
|
|
52
53
|
}
|
|
53
54
|
return `Reference file (${reference.category}:${reference.name})\n${message.body}`;
|
|
54
55
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readMessageProjectionById } from "../collaboration-state/index.js";
|
|
2
|
+
import { messageTextForModel } from "../artifact-feedback/index.js";
|
|
2
3
|
function promptSourceMessage(db, source) {
|
|
3
4
|
const message = readMessageProjectionById(db, source.message_id);
|
|
4
5
|
if (message === null) {
|
|
@@ -14,13 +15,13 @@ function promptSourceMessage(db, source) {
|
|
|
14
15
|
...(displayName === undefined || displayName === ""
|
|
15
16
|
? {}
|
|
16
17
|
: { author_display_name: displayName }),
|
|
17
|
-
text: message
|
|
18
|
+
text: messageTextForModel(message),
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
if (message.message_kind === "agent_text" && message.author.kind === "agent") {
|
|
21
22
|
return {
|
|
22
23
|
role: "tutti",
|
|
23
|
-
text: message
|
|
24
|
+
text: messageTextForModel(message),
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
throw new Error("Task source message has an unsupported kind or author");
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import { ArtifactPreviewManager } from "../../../../artifacts/index.js";
|
|
2
3
|
import type { WorkspaceEventBus } from "../../workspace-events.js";
|
|
3
4
|
import type { HostProjectApiRouteOptions } from "./types.js";
|
|
4
5
|
export declare function registerArtifactsRoutes(app: FastifyInstance, { options, workspaceEvents, }: {
|
|
5
6
|
options: HostProjectApiRouteOptions;
|
|
6
7
|
workspaceEvents: WorkspaceEventBus;
|
|
7
|
-
}):
|
|
8
|
+
}): ArtifactPreviewManager;
|
|
8
9
|
//# sourceMappingURL=artifacts-routes.d.ts.map
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import type { ArtifactPreviewManager } from "../../../../artifacts/index.js";
|
|
2
3
|
import type { WorkspaceEventBus } from "../../workspace-events.js";
|
|
3
4
|
import type { HostProjectApiRouteOptions } from "./types.js";
|
|
4
|
-
export declare function registerMessagesRoutes(app: FastifyInstance, { options, workspaceEvents, }: {
|
|
5
|
+
export declare function registerMessagesRoutes(app: FastifyInstance, { options, workspaceEvents, artifactPreviewManager, }: {
|
|
5
6
|
options: HostProjectApiRouteOptions;
|
|
6
7
|
workspaceEvents: WorkspaceEventBus;
|
|
8
|
+
artifactPreviewManager: ArtifactPreviewManager;
|
|
7
9
|
}): void;
|
|
8
10
|
//# sourceMappingURL=messages-routes.d.ts.map
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { GetMessagesRequestSchema, MessageWindowSchema, SendMainChatMessageBodySchema, SendMainChatMessageResponseSchema, } from "@tutti/shared/schemas/api";
|
|
2
|
-
import { createMainChatMessage, readMainChatMessageWindow, } from "../../../../collaboration-state/index.js";
|
|
2
|
+
import { createMainChatMessage, readMessageProjectionById, readMainChatMessageWindow, } from "../../../../collaboration-state/index.js";
|
|
3
|
+
import { createArtifactFeedbackMessage, } from "../../../../artifact-feedback/index.js";
|
|
3
4
|
import { executeIdempotentCommand } from "../../../command-idempotency/index.js";
|
|
4
5
|
import { HOST_PROJECT_API_BASE_PATH } from "./constants.js";
|
|
5
6
|
import { mainChatMessageInvalidates } from "./invalidations.js";
|
|
6
7
|
import { requireValidMessageWindowRequest, requireValidSendMainChatPayload, } from "./payload-validation.js";
|
|
7
|
-
import { requireHostProjectSession, requireProjectStore, } from "./session-requirements.js";
|
|
8
|
-
export function registerMessagesRoutes(app, { options, workspaceEvents, }) {
|
|
8
|
+
import { requireHostProjectSession, requireProjectStore, requireWorkspaceRoot, } from "./session-requirements.js";
|
|
9
|
+
export function registerMessagesRoutes(app, { options, workspaceEvents, artifactPreviewManager, }) {
|
|
9
10
|
app.get(`${HOST_PROJECT_API_BASE_PATH}/messages`, {
|
|
10
11
|
schema: {
|
|
11
12
|
querystring: GetMessagesRequestSchema,
|
|
@@ -34,45 +35,76 @@ export function registerMessagesRoutes(app, { options, workspaceEvents, }) {
|
|
|
34
35
|
command_id: request.body.command_id,
|
|
35
36
|
command_name: "messages.create",
|
|
36
37
|
target_ref: "main_chat",
|
|
37
|
-
payload: {
|
|
38
|
-
body: payload.body,
|
|
39
|
-
author_ref: session.account.account_ref,
|
|
40
|
-
},
|
|
38
|
+
payload: { ...payload, author_ref: session.account.account_ref },
|
|
41
39
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
42
|
-
execute: (tx) =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
execute: (tx) => {
|
|
41
|
+
let body;
|
|
42
|
+
let refs;
|
|
43
|
+
if ("artifact_feedback" in payload) {
|
|
44
|
+
const projection = artifactPreviewManager.readProjection({
|
|
45
|
+
workspaceRoot: requireWorkspaceRoot(options),
|
|
46
|
+
});
|
|
47
|
+
if (projection.artifact.status !== "declared" ||
|
|
48
|
+
projection.artifact.preview.status !== "ready") {
|
|
49
|
+
return { disposition: { kind: "artifact_unavailable" } };
|
|
50
|
+
}
|
|
51
|
+
if (projection.artifact.preview_generation !==
|
|
52
|
+
payload.artifact_feedback.expected_preview_generation) {
|
|
53
|
+
return { disposition: { kind: "artifact_changed" } };
|
|
54
|
+
}
|
|
55
|
+
const feedbackMessage = createArtifactFeedbackMessage({
|
|
56
|
+
artifactTitle: projection.artifact.artifact.title,
|
|
57
|
+
feedback: payload.artifact_feedback,
|
|
58
|
+
});
|
|
59
|
+
body = feedbackMessage.body;
|
|
60
|
+
refs = { artifact_feedback: feedbackMessage.ref };
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
body = payload.body;
|
|
64
|
+
}
|
|
65
|
+
const message = createMainChatMessage(tx, {
|
|
66
|
+
body,
|
|
67
|
+
author: {
|
|
68
|
+
kind: "human",
|
|
69
|
+
ref: session.account.account_ref,
|
|
70
|
+
display_name: session.account.display_name,
|
|
71
|
+
avatar: session.account.avatar,
|
|
72
|
+
},
|
|
73
|
+
...(refs === undefined ? {} : { refs }),
|
|
74
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
disposition: { kind: "created", message_id: message.id },
|
|
78
|
+
result: { message },
|
|
79
|
+
};
|
|
80
|
+
},
|
|
57
81
|
});
|
|
82
|
+
const replayedMessage = execution.result === undefined &&
|
|
83
|
+
execution.disposition.kind === "created" &&
|
|
84
|
+
execution.disposition.message_id !== undefined
|
|
85
|
+
? readMessageProjectionById(store.db, execution.disposition.message_id)
|
|
86
|
+
: null;
|
|
87
|
+
const result = execution.result ?? (replayedMessage === null ? undefined : { message: replayedMessage });
|
|
58
88
|
const response = {
|
|
59
89
|
command_id: execution.command_id,
|
|
60
90
|
disposition: execution.disposition,
|
|
61
|
-
|
|
91
|
+
...(execution.disposition.kind === "created"
|
|
92
|
+
? { invalidates: mainChatMessageInvalidates() }
|
|
93
|
+
: {}),
|
|
62
94
|
};
|
|
63
|
-
if (
|
|
64
|
-
response.result =
|
|
95
|
+
if (result !== undefined) {
|
|
96
|
+
response.result = result;
|
|
65
97
|
}
|
|
66
|
-
if (!execution.replayed &&
|
|
98
|
+
if (!execution.replayed && result !== undefined) {
|
|
67
99
|
workspaceEvents.publish({
|
|
68
100
|
event_type: "message.created",
|
|
69
101
|
payload: {
|
|
70
|
-
message:
|
|
102
|
+
message: result.message,
|
|
71
103
|
},
|
|
72
104
|
invalidates: mainChatMessageInvalidates(),
|
|
73
105
|
});
|
|
74
|
-
options.controlPlane?.notifyScratchpadSourceChanged(
|
|
75
|
-
options.chatAssistant?.enqueue({ message:
|
|
106
|
+
options.controlPlane?.notifyScratchpadSourceChanged(result.message);
|
|
107
|
+
options.chatAssistant?.enqueue({ message: result.message });
|
|
76
108
|
}
|
|
77
109
|
return response;
|
|
78
110
|
});
|
|
@@ -585,7 +585,7 @@ export declare const HOST_PROJECT_WORKSPACE_OPENAPI_ROUTES: ({
|
|
|
585
585
|
};
|
|
586
586
|
readonly capabilities: {
|
|
587
587
|
readonly type: "object";
|
|
588
|
-
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification"];
|
|
588
|
+
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification", "artifact_feedback"];
|
|
589
589
|
readonly additionalProperties: false;
|
|
590
590
|
readonly properties: {
|
|
591
591
|
readonly sse: {
|
|
@@ -616,6 +616,9 @@ export declare const HOST_PROJECT_WORKSPACE_OPENAPI_ROUTES: ({
|
|
|
616
616
|
readonly clarification: {
|
|
617
617
|
readonly type: "boolean";
|
|
618
618
|
};
|
|
619
|
+
readonly artifact_feedback: {
|
|
620
|
+
readonly type: "boolean";
|
|
621
|
+
};
|
|
619
622
|
};
|
|
620
623
|
};
|
|
621
624
|
};
|
|
@@ -776,7 +779,7 @@ export declare const HOST_PROJECT_WORKSPACE_OPENAPI_ROUTES: ({
|
|
|
776
779
|
};
|
|
777
780
|
readonly capabilities: {
|
|
778
781
|
readonly type: "object";
|
|
779
|
-
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification"];
|
|
782
|
+
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification", "artifact_feedback"];
|
|
780
783
|
readonly additionalProperties: false;
|
|
781
784
|
readonly properties: {
|
|
782
785
|
readonly sse: {
|
|
@@ -807,6 +810,9 @@ export declare const HOST_PROJECT_WORKSPACE_OPENAPI_ROUTES: ({
|
|
|
807
810
|
readonly clarification: {
|
|
808
811
|
readonly type: "boolean";
|
|
809
812
|
};
|
|
813
|
+
readonly artifact_feedback: {
|
|
814
|
+
readonly type: "boolean";
|
|
815
|
+
};
|
|
810
816
|
};
|
|
811
817
|
};
|
|
812
818
|
};
|
|
@@ -1868,7 +1868,7 @@ export declare const HOST_PROJECT_OPENAPI_ROUTES: ({
|
|
|
1868
1868
|
};
|
|
1869
1869
|
readonly capabilities: {
|
|
1870
1870
|
readonly type: "object";
|
|
1871
|
-
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification"];
|
|
1871
|
+
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification", "artifact_feedback"];
|
|
1872
1872
|
readonly additionalProperties: false;
|
|
1873
1873
|
readonly properties: {
|
|
1874
1874
|
readonly sse: {
|
|
@@ -1899,6 +1899,9 @@ export declare const HOST_PROJECT_OPENAPI_ROUTES: ({
|
|
|
1899
1899
|
readonly clarification: {
|
|
1900
1900
|
readonly type: "boolean";
|
|
1901
1901
|
};
|
|
1902
|
+
readonly artifact_feedback: {
|
|
1903
|
+
readonly type: "boolean";
|
|
1904
|
+
};
|
|
1902
1905
|
};
|
|
1903
1906
|
};
|
|
1904
1907
|
};
|
|
@@ -2059,7 +2062,7 @@ export declare const HOST_PROJECT_OPENAPI_ROUTES: ({
|
|
|
2059
2062
|
};
|
|
2060
2063
|
readonly capabilities: {
|
|
2061
2064
|
readonly type: "object";
|
|
2062
|
-
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification"];
|
|
2065
|
+
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification", "artifact_feedback"];
|
|
2063
2066
|
readonly additionalProperties: false;
|
|
2064
2067
|
readonly properties: {
|
|
2065
2068
|
readonly sse: {
|
|
@@ -2090,6 +2093,9 @@ export declare const HOST_PROJECT_OPENAPI_ROUTES: ({
|
|
|
2090
2093
|
readonly clarification: {
|
|
2091
2094
|
readonly type: "boolean";
|
|
2092
2095
|
};
|
|
2096
|
+
readonly artifact_feedback: {
|
|
2097
|
+
readonly type: "boolean";
|
|
2098
|
+
};
|
|
2093
2099
|
};
|
|
2094
2100
|
};
|
|
2095
2101
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ArtifactFeedbackValidationError, normalizeArtifactFeedbackCommand, } from "../../../../artifact-feedback/index.js";
|
|
1
2
|
import { HostProjectBadRequestError, HostProjectValidationError } from "./errors.js";
|
|
2
3
|
export function requireValidMessageWindowRequest(query) {
|
|
3
4
|
if (query.before !== undefined && query.after !== undefined) {
|
|
@@ -9,6 +10,17 @@ export function requireValidMessageWindowRequest(query) {
|
|
|
9
10
|
return query;
|
|
10
11
|
}
|
|
11
12
|
export function requireValidSendMainChatPayload(payload) {
|
|
13
|
+
if ("artifact_feedback" in payload) {
|
|
14
|
+
try {
|
|
15
|
+
return { artifact_feedback: normalizeArtifactFeedbackCommand(payload.artifact_feedback) };
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
if (error instanceof ArtifactFeedbackValidationError) {
|
|
19
|
+
throw new HostProjectValidationError(error.message);
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
12
24
|
if (payload.body.trim() === "") {
|
|
13
25
|
throw new HostProjectValidationError("Message body cannot be empty");
|
|
14
26
|
}
|
|
@@ -560,7 +560,7 @@ export declare const WorkspaceResponseSchema: {
|
|
|
560
560
|
};
|
|
561
561
|
readonly capabilities: {
|
|
562
562
|
readonly type: "object";
|
|
563
|
-
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification"];
|
|
563
|
+
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification", "artifact_feedback"];
|
|
564
564
|
readonly additionalProperties: false;
|
|
565
565
|
readonly properties: {
|
|
566
566
|
readonly sse: {
|
|
@@ -591,6 +591,9 @@ export declare const WorkspaceResponseSchema: {
|
|
|
591
591
|
readonly clarification: {
|
|
592
592
|
readonly type: "boolean";
|
|
593
593
|
};
|
|
594
|
+
readonly artifact_feedback: {
|
|
595
|
+
readonly type: "boolean";
|
|
596
|
+
};
|
|
594
597
|
};
|
|
595
598
|
};
|
|
596
599
|
};
|
|
@@ -740,7 +743,7 @@ export declare const BootstrapResponseSchema: {
|
|
|
740
743
|
};
|
|
741
744
|
readonly capabilities: {
|
|
742
745
|
readonly type: "object";
|
|
743
|
-
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification"];
|
|
746
|
+
readonly required: readonly ["sse", "relay_join", "repo_viewer", "provider_config_query", "provider_model_update", "browser_secret_write", "task_auto_scheduler", "task_run_now_scheduler", "clarification", "artifact_feedback"];
|
|
744
747
|
readonly additionalProperties: false;
|
|
745
748
|
readonly properties: {
|
|
746
749
|
readonly sse: {
|
|
@@ -771,6 +774,9 @@ export declare const BootstrapResponseSchema: {
|
|
|
771
774
|
readonly clarification: {
|
|
772
775
|
readonly type: "boolean";
|
|
773
776
|
};
|
|
777
|
+
readonly artifact_feedback: {
|
|
778
|
+
readonly type: "boolean";
|
|
779
|
+
};
|
|
774
780
|
};
|
|
775
781
|
};
|
|
776
782
|
};
|
|
@@ -316,6 +316,7 @@ export const WorkspaceResponseSchema = {
|
|
|
316
316
|
"task_auto_scheduler",
|
|
317
317
|
"task_run_now_scheduler",
|
|
318
318
|
"clarification",
|
|
319
|
+
"artifact_feedback",
|
|
319
320
|
],
|
|
320
321
|
additionalProperties: false,
|
|
321
322
|
properties: {
|
|
@@ -328,6 +329,7 @@ export const WorkspaceResponseSchema = {
|
|
|
328
329
|
task_auto_scheduler: { type: "boolean" },
|
|
329
330
|
task_run_now_scheduler: { type: "boolean" },
|
|
330
331
|
clarification: { type: "boolean" },
|
|
332
|
+
artifact_feedback: { type: "boolean" },
|
|
331
333
|
},
|
|
332
334
|
},
|
|
333
335
|
},
|