@xfey/tutti 0.1.97 → 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/index.js +15 -3
- package/dist/control-plane/scratchpad-source-messages.js +2 -1
- package/dist/control-plane/task-source-context.js +3 -2
- package/dist/control-plane/worklist-terminal-feedback.js +4 -4
- package/dist/server-shell/http/routes/agent-context-references.js +8 -14
- 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-viewer-routes.d.ts +0 -2
- 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/reference-files.js +6 -17
- 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/dist/workspace-ops/constants.d.ts +0 -1
- package/dist/workspace-ops/constants.js +0 -1
- package/dist/workspace-ops/index.d.ts +3 -2
- package/dist/workspace-ops/index.js +3 -2
- package/dist/workspace-ops/project-doc-templates.js +1 -1
- package/dist/workspace-ops/reference-files.js +3 -2
- package/dist/workspace-ops/reference-metadata.d.ts +2 -2
- package/dist/workspace-ops/reference-metadata.js +8 -22
- package/dist/workspace-ops/reference-summaries.js +11 -17
- package/dist/workspace-ops/reference-tree.d.ts +11 -0
- package/dist/workspace-ops/reference-tree.js +34 -0
- package/dist/workspace-ops/viewer-git.d.ts +1 -0
- package/dist/workspace-ops/viewer-git.js +11 -0
- 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 -5
- package/node_modules/@tutti/shared/dist/schemas/api/viewer-reference.js +0 -2
- package/package.json +1 -1
- package/prompts/procedures/context-sync.md +3 -3
- package/prompts/procedures/reference-file-summary.md +1 -1
- package/prompts/procedures/task-compile.md +2 -2
- package/prompts/prompt-flow-map.md +1 -1
- package/prompts/runs/task-execute.md +2 -2
- package/prompts/skills/read-references/SKILL.md +2 -2
- 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-lpr-1FjJ.js +0 -1
- package/web/assets/index-CQoq2PtU.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,7 +1,7 @@
|
|
|
1
|
-
import { readActiveClarificationProjection, readActiveTaskCompileContext, markScratchpadRefreshSucceeded, readPendingTaskCompileSubmission, readTaskProjectionByActivityRef, readScratchpadSourceState, readWorklistProjection, } from "../collaboration-state/index.js";
|
|
1
|
+
import { readActiveClarificationProjection, readActiveTaskCompileContext, markScratchpadRefreshSucceeded, readPendingTaskCompileSubmission, readTaskDetailProjection, readTaskProjectionByActivityRef, readScratchpadSourceState, readWorklistProjection, } from "../collaboration-state/index.js";
|
|
2
2
|
import { ProcedureEngine } from "../procedure-engine/index.js";
|
|
3
3
|
import { RunEngine } from "../run-engine/index.js";
|
|
4
|
-
import { WorkspaceOpsError, readReferenceSummaryTargets } from "../workspace-ops/index.js";
|
|
4
|
+
import { WorkspaceOpsError, isReferenceFilePath, readReferenceSummaryTargets, } from "../workspace-ops/index.js";
|
|
5
5
|
import { publishProcedureTransition as publishProcedureTransitionEvent, publishRunResultRecorded as publishRunResultRecordedEvent, publishRunTransition as publishRunTransitionEvent, publishTaskRunStarted as publishTaskRunStartedEvent, } from "./event-publishers.js";
|
|
6
6
|
import { startContextSyncAnalysisProcedure } from "./context-sync.js";
|
|
7
7
|
import { refreshProjectBriefProjection, } from "./project-brief-refresh.js";
|
|
@@ -488,10 +488,22 @@ export class Phase5ControlPlane {
|
|
|
488
488
|
this.pendingRunInputs.delete(activityRef);
|
|
489
489
|
if (state.activity.status === "finished") {
|
|
490
490
|
if (state.activity.task_id !== undefined) {
|
|
491
|
+
const taskId = state.activity.task_id;
|
|
492
|
+
const detail = readTaskDetailProjection(this.store.db, taskId);
|
|
493
|
+
const result = detail?.detail.result;
|
|
494
|
+
if (result?.result_kind === "finished" &&
|
|
495
|
+
result.outcome === "completed" &&
|
|
496
|
+
result.promotion?.status === "promoted") {
|
|
497
|
+
for (const path of result.changed_paths ?? []) {
|
|
498
|
+
if (isReferenceFilePath(path)) {
|
|
499
|
+
this.pendingReferenceSummaryPaths.add(path);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
491
503
|
emitTaskCompletedResultMessage({
|
|
492
504
|
store: this.store,
|
|
493
505
|
events: this.events,
|
|
494
|
-
taskId
|
|
506
|
+
taskId,
|
|
495
507
|
activityRef: state.activity.activity_ref,
|
|
496
508
|
...(this.projectContext === undefined
|
|
497
509
|
? {}
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
import { readViewerFile,
|
|
1
|
+
import { isReferenceFilePath, readViewerFile, referenceFolderGroupForPath, referenceMediaTypeForPath, } from "../workspace-ops/index.js";
|
|
2
2
|
import { createMainChatAgentMessage, hasMainChatTaskResultMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readTaskDetailProjection, readWorklistProjection, } from "../collaboration-state/index.js";
|
|
3
3
|
import { mainChatInvalidates } from "./invalidations.js";
|
|
4
4
|
const BODY_ITEM_LIMIT = 12;
|
|
@@ -62,7 +62,7 @@ function generatedFileProjection(file) {
|
|
|
62
62
|
name: file.name,
|
|
63
63
|
size_bytes: file.size_bytes,
|
|
64
64
|
};
|
|
65
|
-
const group =
|
|
65
|
+
const group = referenceFolderGroupForPath(file.path);
|
|
66
66
|
if (group !== undefined) {
|
|
67
67
|
projection.group = group;
|
|
68
68
|
}
|
|
@@ -73,7 +73,7 @@ function generatedFileProjection(file) {
|
|
|
73
73
|
return projection;
|
|
74
74
|
}
|
|
75
75
|
function readGeneratedFile(input) {
|
|
76
|
-
if (
|
|
76
|
+
if (!isReferenceFilePath(input.path)) {
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
try {
|
|
@@ -100,7 +100,7 @@ function generatedFilesForCompletedEntries(input, entries) {
|
|
|
100
100
|
continue;
|
|
101
101
|
}
|
|
102
102
|
for (const path of entry.detail?.detail.result.changed_paths ?? []) {
|
|
103
|
-
if (
|
|
103
|
+
if (isReferenceFilePath(path)) {
|
|
104
104
|
paths.add(path);
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { REFERENCE_INDEX_PATH, WorkspaceOpsError, readReferenceFileEntries, readReferenceSummaryIndex, readViewerFile, referenceFileCategoryForPath, referenceMediaTypeForPath, referenceSummaryProjectionForEntry, } from "../../../workspace-ops/index.js";
|
|
2
2
|
import { AgentContextBadRequestError } from "./agent-context-errors.js";
|
|
3
3
|
function requireWorkspaceRoot(options) {
|
|
4
4
|
if (options.workspaceOps === undefined) {
|
|
@@ -46,14 +46,11 @@ function unavailableReasonForContent(content) {
|
|
|
46
46
|
}
|
|
47
47
|
function referenceEntries(options) {
|
|
48
48
|
const workspaceRoot = requireWorkspaceRoot(options);
|
|
49
|
-
let tree;
|
|
50
49
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
path
|
|
54
|
-
|
|
55
|
-
...(options.now === undefined ? {} : { now: options.now }),
|
|
56
|
-
});
|
|
50
|
+
return readReferenceFileEntries({ workspaceRoot })
|
|
51
|
+
.filter((entry) => entry.path !== REFERENCE_INDEX_PATH)
|
|
52
|
+
.filter((entry) => referenceFileCategoryForPath(entry.path) !== undefined)
|
|
53
|
+
.sort((left, right) => left.path.localeCompare(right.path, "en", { sensitivity: "base" }));
|
|
57
54
|
}
|
|
58
55
|
catch (error) {
|
|
59
56
|
if (error instanceof WorkspaceOpsError && error.code === "not_found") {
|
|
@@ -61,11 +58,6 @@ function referenceEntries(options) {
|
|
|
61
58
|
}
|
|
62
59
|
throw error;
|
|
63
60
|
}
|
|
64
|
-
return tree.items
|
|
65
|
-
.filter((entry) => entry.kind === "file")
|
|
66
|
-
.filter((entry) => entry.path !== REFERENCE_INDEX_PATH)
|
|
67
|
-
.filter((entry) => referenceFileCategoryForPath(entry.path) !== undefined)
|
|
68
|
-
.sort((left, right) => left.path.localeCompare(right.path, "en", { sensitivity: "base" }));
|
|
69
61
|
}
|
|
70
62
|
function canReadReferenceText(options, path) {
|
|
71
63
|
try {
|
|
@@ -76,7 +68,9 @@ function canReadReferenceText(options, path) {
|
|
|
76
68
|
...(options.now === undefined ? {} : { now: options.now }),
|
|
77
69
|
});
|
|
78
70
|
const reason = unavailableReasonForContent(file.content);
|
|
79
|
-
return reason === undefined
|
|
71
|
+
return reason === undefined
|
|
72
|
+
? { can_read_text: true }
|
|
73
|
+
: { can_read_text: false, blocked_reason: reason };
|
|
80
74
|
}
|
|
81
75
|
catch {
|
|
82
76
|
return { can_read_text: false, blocked_reason: "restricted_path" };
|
|
@@ -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
|
});
|
|
@@ -283,14 +283,12 @@ export declare const HOST_PROJECT_VIEWER_OPENAPI_ROUTES: ({
|
|
|
283
283
|
index_path: import("@sinclair/typebox").TLiteral<"docs/reference/README.md">;
|
|
284
284
|
files_path: import("@sinclair/typebox").TLiteral<"docs/reference/files">;
|
|
285
285
|
images_path: import("@sinclair/typebox").TLiteral<"docs/reference/images">;
|
|
286
|
-
tutti_path: import("@sinclair/typebox").TLiteral<"docs/reference/tutti">;
|
|
287
286
|
index_status: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"available">, import("@sinclair/typebox").TLiteral<"missing">, import("@sinclair/typebox").TLiteral<"unreadable">]>;
|
|
288
287
|
index_unreadable_reason: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"restricted_path">, import("@sinclair/typebox").TLiteral<"secret_like">, import("@sinclair/typebox").TLiteral<"binary">, import("@sinclair/typebox").TLiteral<"unsupported_encoding">, import("@sinclair/typebox").TLiteral<"too_large">]>>;
|
|
289
288
|
}>;
|
|
290
289
|
items: import("@sinclair/typebox").TArray<import("@sinclair/typebox").TObject<{
|
|
291
290
|
path: import("@sinclair/typebox").TString;
|
|
292
291
|
name: import("@sinclair/typebox").TString;
|
|
293
|
-
source: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"human_upload">, import("@sinclair/typebox").TLiteral<"tutti_output">]>;
|
|
294
292
|
category: import("@sinclair/typebox").TUnion<[import("@sinclair/typebox").TLiteral<"file">, import("@sinclair/typebox").TLiteral<"image">]>;
|
|
295
293
|
size_bytes: import("@sinclair/typebox").TInteger;
|
|
296
294
|
media_type: import("@sinclair/typebox").TOptional<import("@sinclair/typebox").TString>;
|