@xfey/tutti 0.1.45 → 0.1.46

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 CHANGED
@@ -26,6 +26,7 @@
26
26
  ## 当前状态
27
27
 
28
28
  - 已具备 Fastify host shell、后台 host manager CLI 套件、带 `service status / stop / restart`、卸载前 `shutdown`、并发启动锁、版本替换、独立 browser context、idle / package update cleanup 与私有日志的机器级 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 可视化产物查看层和 OpenAI / Codex provider 接入。
29
+ - 新 Host project 的空群聊会由 collaboration-state 幂等写入一条 Tutti 欢迎消息;它不经过 browser message command,不触发 `@tutti`、Chat read-state、Scratchpad 或 Timeline / execution 流程。
29
30
  - Host server 与 Local Console 支持在显式 `webStatic.root` 或 packaged `web/` 存在时服务 Web 静态产物;`/api/*`、SSE、health、host-local control 和 `/local-console/*` API 不被 SPA fallback 吞掉。
30
31
  - 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` 与测试产物。
31
32
  - Root `smoke:packed-cli` 同样从干净 build output 生成 staging `@xfey/tutti` tarball、在临时目录安装,并验证 SQLite native binding、`tutti --help` / `tutti service --help` / `tutti service status` / `tutti shutdown --help` / `tutti launch --help` / `tutti archive --help` / `tutti --version`、migrations、prompts、Web static index、bundled internal dependencies、bundled external runtime dependencies、CLI QR dependency、仓库锁定的 `@openai/codex` binary 可执行性和临时全局安装路径;该 smoke 不需要 Tutti source repo 存在。
@@ -1,6 +1,6 @@
1
1
  export { CollaborationStateCursorError } from "./serialization.js";
2
2
  export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
- export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, hasMainChatArtifactPreviewMessage, hasMainChatTaskResultMessage, hasMainChatWorklistCompileMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesAfter, readMainChatMessagesAfterCursor, readMainChatMessagesBeforeCursor, } from "./messages.js";
3
+ export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, ensureInitialProjectWelcomeMessage, hasMainChatArtifactPreviewMessage, hasMainChatTaskResultMessage, hasMainChatWorklistCompileMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesAfter, readMainChatMessagesAfterCursor, readMainChatMessagesBeforeCursor, } from "./messages.js";
4
4
  export { createEmptyScratchpadProjection, readScratchpadReceiptSourceRefs, readScratchpadProjection, submitScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
5
5
  export { advanceScratchpadTaskCompileSourceCursor, markScratchpadRefreshFailed, markScratchpadRefreshStarted, markScratchpadRefreshSucceeded, markScratchpadSourceDirty, readScratchpadSourceState, } from "./scratchpad-source-state.js";
6
6
  export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readSealedClarificationRoundsWithoutSuccessor, readSubmittedTaskCompileClarificationRounds, readSubmittedTaskCompileClarificationRoundsAfterCursor, } from "./clarification-records.js";
@@ -1,6 +1,6 @@
1
1
  export { CollaborationStateCursorError } from "./serialization.js";
2
2
  export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
- export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, hasMainChatArtifactPreviewMessage, hasMainChatTaskResultMessage, hasMainChatWorklistCompileMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesAfter, readMainChatMessagesAfterCursor, readMainChatMessagesBeforeCursor, } from "./messages.js";
3
+ export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, ensureInitialProjectWelcomeMessage, hasMainChatArtifactPreviewMessage, hasMainChatTaskResultMessage, hasMainChatWorklistCompileMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesAfter, readMainChatMessagesAfterCursor, readMainChatMessagesBeforeCursor, } from "./messages.js";
4
4
  export { createEmptyScratchpadProjection, readScratchpadReceiptSourceRefs, readScratchpadProjection, submitScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
5
5
  export { advanceScratchpadTaskCompileSourceCursor, markScratchpadRefreshFailed, markScratchpadRefreshStarted, markScratchpadRefreshSucceeded, markScratchpadSourceDirty, readScratchpadSourceState, } from "./scratchpad-source-state.js";
6
6
  export { deleteStaleClarificationSubmitReplayHints, readActiveClarificationProjection, readClarificationRoundProjection, readClarificationRoundsForThread, readClarificationThreadResponse, readSealedClarificationRoundsWithoutSuccessor, readSubmittedTaskCompileClarificationRounds, readSubmittedTaskCompileClarificationRoundsAfterCursor, } from "./clarification-records.js";
@@ -1,12 +1,21 @@
1
1
  import { type MessageId, type RunResultId, type TaskId, type WorkflowInvocationRef } from "@tutti/shared/ids";
2
2
  import type { MessageProjection, MessageWindow } from "@tutti/shared/schemas/api";
3
- import type { SqliteDatabase } from "../store/index.js";
3
+ import { type SqliteDatabase } from "../store/index.js";
4
4
  import type { CreateMainChatAgentMessageInput, CreateMainChatMessageInput, CreateMainChatSystemMessageInput, ReadMainChatMessagesAfterOptions, ReadMessageWindowOptions } from "./types.js";
5
5
  import type { MessageRow } from "./storage-types.js";
6
6
  export declare function mapMessageRow(row: MessageRow, db?: SqliteDatabase): MessageProjection;
7
7
  export declare function createMainChatMessage(db: SqliteDatabase, input: CreateMainChatMessageInput): MessageProjection;
8
8
  export declare function createMainChatSystemMessage(db: SqliteDatabase, input: CreateMainChatSystemMessageInput): MessageProjection;
9
9
  export declare function createMainChatAgentMessage(db: SqliteDatabase, input: CreateMainChatAgentMessageInput): MessageProjection;
10
+ /**
11
+ * Seeds onboarding copy only while a project chat is still empty. The direct
12
+ * repository write is intentional: this is not user discussion, so it must not
13
+ * trigger the chat assistant, read-state notifications, or Scratchpad work.
14
+ */
15
+ export declare function ensureInitialProjectWelcomeMessage(db: SqliteDatabase, input: {
16
+ projectName: string;
17
+ now?: () => Date;
18
+ }): MessageProjection | null;
10
19
  export declare function readMessageProjectionById(db: SqliteDatabase, messageId: MessageId): MessageProjection | null;
11
20
  export declare function hasMainChatWorklistFeedbackMessage(db: SqliteDatabase, feedbackKey: string): boolean;
12
21
  export declare function hasMainChatTaskResultMessage(db: SqliteDatabase, resultKey: string): boolean;
@@ -1,6 +1,18 @@
1
1
  import { createMessageId, } from "@tutti/shared/ids";
2
+ import { withHostStoreTransaction } from "../store/index.js";
2
3
  import { clampLimit, decodeCursor, messageCursor, nowIso, parseJson, stringifyJson, } from "./serialization.js";
3
4
  import { recordReadNotificationEvent } from "./read-state.js";
5
+ function escapeMarkdownText(value) {
6
+ return value.replace(/[!-/:-@[-`{-~]/gu, "\\$&");
7
+ }
8
+ function initialProjectWelcomeMessage(projectName) {
9
+ const normalizedProjectName = projectName.trim().replace(/\s+/gu, " ") || "this project";
10
+ return [
11
+ `Welcome to ${escapeMarkdownText(normalizedProjectName)} 👋`,
12
+ "This is where your team can shape ideas, align on goals, and bring the project to life. Tutti will keep the Scratchpad updated as the conversation develops.",
13
+ "When you’re ready, click Run to begin implementation. You can also mention `@tutti` whenever you need help.",
14
+ ].join("\n\n");
15
+ }
4
16
  function readClarificationCardForMessage(db, messageId) {
5
17
  const row = db
6
18
  .prepare(`
@@ -148,7 +160,7 @@ export function createMainChatSystemMessage(db, input) {
148
160
  }
149
161
  return message;
150
162
  }
151
- export function createMainChatAgentMessage(db, input) {
163
+ function insertMainChatAgentMessage(db, input, options) {
152
164
  const createdAt = nowIso(input.now);
153
165
  const id = createMessageId();
154
166
  db.prepare(`
@@ -165,13 +177,15 @@ export function createMainChatAgentMessage(db, input) {
165
177
  )
166
178
  VALUES (?, 'main_chat', 'agent', 'tutti', 'Tutti', 'agent_text', ?, ?, ?)
167
179
  `).run(id, input.body, input.refs === undefined ? null : stringifyJson(input.refs), createdAt);
168
- recordReadNotificationEvent(db, {
169
- scope: "chat",
170
- source_kind: "message",
171
- source_ref: id,
172
- actor: { kind: "agent", ref: "tutti" },
173
- occurred_at: createdAt,
174
- });
180
+ if (options.recordReadState) {
181
+ recordReadNotificationEvent(db, {
182
+ scope: "chat",
183
+ source_kind: "message",
184
+ source_ref: id,
185
+ actor: { kind: "agent", ref: "tutti" },
186
+ occurred_at: createdAt,
187
+ });
188
+ }
175
189
  const message = {
176
190
  id,
177
191
  scope: { kind: "main_chat" },
@@ -185,6 +199,27 @@ export function createMainChatAgentMessage(db, input) {
185
199
  }
186
200
  return message;
187
201
  }
202
+ export function createMainChatAgentMessage(db, input) {
203
+ return insertMainChatAgentMessage(db, input, { recordReadState: true });
204
+ }
205
+ /**
206
+ * Seeds onboarding copy only while a project chat is still empty. The direct
207
+ * repository write is intentional: this is not user discussion, so it must not
208
+ * trigger the chat assistant, read-state notifications, or Scratchpad work.
209
+ */
210
+ export function ensureInitialProjectWelcomeMessage(db, input) {
211
+ return withHostStoreTransaction(db, (tx) => {
212
+ const existing = tx.prepare("SELECT 1 AS found FROM messages LIMIT 1").get();
213
+ if (existing !== undefined) {
214
+ return null;
215
+ }
216
+ return insertMainChatAgentMessage(tx, {
217
+ body: initialProjectWelcomeMessage(input.projectName),
218
+ refs: { scratchpad_source: "exclude" },
219
+ ...(input.now === undefined ? {} : { now: input.now }),
220
+ }, { recordReadState: false });
221
+ });
222
+ }
188
223
  export function readMessageProjectionById(db, messageId) {
189
224
  const row = db
190
225
  .prepare(`
@@ -330,7 +365,11 @@ export function readMainChatMessageWindow(db, options = {}) {
330
365
  .all(target.created_at, target.created_at, target.id, afterLimit + 1);
331
366
  hasOlder = olderRows.length > beforeLimit;
332
367
  hasNewer = newerRows.length > afterLimit;
333
- rows = [...olderRows.slice(0, beforeLimit).reverse(), target, ...newerRows.slice(0, afterLimit)];
368
+ rows = [
369
+ ...olderRows.slice(0, beforeLimit).reverse(),
370
+ target,
371
+ ...newerRows.slice(0, afterLimit),
372
+ ];
334
373
  }
335
374
  }
336
375
  else if (before !== null) {
@@ -3,6 +3,7 @@ import { serializeRuntimeLogLine } from "@tutti/shared/utils";
3
3
  import { completeRelayUpload, resolveRelayUpload } from "@tutti/relay-client";
4
4
  import { ApprovalRequestManager } from "../../approvals/index.js";
5
5
  import { ReadOnlyChatAssistant } from "../../chat-assistant/index.js";
6
+ import { ensureInitialProjectWelcomeMessage } from "../../collaboration-state/index.js";
6
7
  import { resolveControlPlaneCommandRecovery } from "../../control-plane/command-recovery.js";
7
8
  import { Phase5ControlPlane } from "../../control-plane/index.js";
8
9
  import { resolveProjectOpenAiProcedureWorkflowRunner } from "../../control-plane/workflows/index.js";
@@ -156,6 +157,7 @@ export async function startForegroundHostServer(options) {
156
157
  };
157
158
  let projectDisplayName = readProjectDisplayName(options.preparation.workspace_root) ??
158
159
  (basename(options.preparation.workspace_root) || options.preparation.project_id);
160
+ ensureInitialProjectWelcomeMessage(store.db, { projectName: projectDisplayName, now });
159
161
  let projectDescription = readProjectDescription(options.preparation.workspace_root) ?? "";
160
162
  const projectContext = {
161
163
  workspaceRoot: options.preparation.workspace_root,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfey/tutti",
3
- "version": "0.1.45",
3
+ "version": "0.1.46",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",