@xfey/tutti 0.1.13 → 0.1.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/control-plane/workflows/openai.d.ts +1 -0
  2. package/dist/control-plane/workflows/openai.js +5 -0
  3. package/dist/providers/openai/app-server/read-only-procedure.d.ts +1 -0
  4. package/dist/providers/openai/app-server/read-only-procedure.js +3 -1
  5. package/dist/providers/openai/app-server/runtime-telemetry.js +4 -5
  6. package/dist/providers/openai/app-server/skills.d.ts +2 -2
  7. package/dist/providers/openai/app-server/skills.js +8 -7
  8. package/dist/providers/openai/app-server/workspace-write-run.d.ts +1 -0
  9. package/dist/providers/openai/app-server/workspace-write-run.js +4 -2
  10. package/dist/providers/openai/chat-assistant.d.ts +1 -0
  11. package/dist/providers/openai/chat-assistant.js +3 -0
  12. package/dist/run-pipeline/openai.d.ts +1 -0
  13. package/dist/run-pipeline/openai.js +2 -0
  14. package/dist/run-pipeline/task-run-invocation.js +1 -0
  15. package/dist/server-shell/cli/host-server-runtime.js +16 -1
  16. package/dist/server-shell/cli/project-identity.d.ts +3 -0
  17. package/dist/server-shell/cli/project-identity.js +24 -0
  18. package/dist/server-shell/http/routes/agent-context-shared.d.ts +1 -0
  19. package/dist/server-shell/http/routes/agent-context-shared.js +4 -0
  20. package/dist/server-shell/http/routes/agent-context-skills-routes.js +3 -3
  21. package/dist/server-shell/http/routes/project-api/invalidations.d.ts +1 -0
  22. package/dist/server-shell/http/routes/project-api/invalidations.js +4 -1
  23. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +8 -0
  24. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.js +14 -1
  25. package/dist/server-shell/http/routes/project-api/openapi.d.ts +8 -0
  26. package/dist/server-shell/http/routes/project-api/payload-validation.d.ts +2 -1
  27. package/dist/server-shell/http/routes/project-api/payload-validation.js +7 -0
  28. package/dist/server-shell/http/routes/project-api/schemas.d.ts +8 -0
  29. package/dist/server-shell/http/routes/project-api/schemas.js +1 -0
  30. package/dist/server-shell/http/routes/project-api/skills-helpers.d.ts +1 -0
  31. package/dist/server-shell/http/routes/project-api/skills-helpers.js +4 -1
  32. package/dist/server-shell/http/routes/project-api/skills-routes.js +5 -5
  33. package/dist/server-shell/http/routes/project-api/system-routes.js +66 -4
  34. package/dist/server-shell/http/routes/project-api/types.d.ts +2 -0
  35. package/dist/skills/index.d.ts +7 -6
  36. package/dist/skills/index.js +18 -21
  37. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -1
  38. package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -1
  39. package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +5 -1
  40. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.d.ts +18 -0
  41. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.js +15 -0
  42. package/package.json +1 -1
  43. package/prompts/prompt-flow-map.md +5 -5
  44. package/prompts/skills/read-user-skills/SKILL.md +1 -1
  45. package/web/assets/index-CohZfGSh.css +1 -0
  46. package/web/assets/index-YQvFdpG8.js +29 -0
  47. package/web/index.html +8 -2
  48. package/web/assets/index-CDUT1Etl.js +0 -29
  49. package/web/assets/index-DIQpGeKY.css +0 -1
@@ -17,6 +17,7 @@ export type OpenAiProcedureWorkflowRunnerOptions = {
17
17
  turnTimeoutMs?: number;
18
18
  baseEnv?: NodeJS.ProcessEnv;
19
19
  tuttiHome?: string;
20
+ userSkillsRoot?: string;
20
21
  builtInSkillsRoot?: string;
21
22
  agentContext?: CodexAppServerAgentContextRuntime;
22
23
  };
@@ -7,6 +7,7 @@ import { readOpenAiApiKeyCredential } from "../../providers/openai/credential-st
7
7
  import { resolveOpenAiProviderConfig, } from "../../providers/openai/provider-config.js";
8
8
  import { SCRATCHPAD_REFRESH_OUTPUT_SCHEMA, TASK_COMPILE_OUTPUT_SCHEMA, PROJECT_CONTEXT_BOOTSTRAP_OUTPUT_SCHEMA, CONTEXT_SYNC_OUTPUT_SCHEMA, PROJECT_BRIEF_REFRESH_OUTPUT_SCHEMA, FOLLOW_UP_CHECK_OUTPUT_SCHEMA, REFERENCE_FILE_SUMMARY_OUTPUT_SCHEMA, isContextSyncWorkflowOutput, isFollowUpCheckStructuredOutput, isProjectBriefRefreshWorkflowOutput, isProjectContextBootstrapWorkflowOutput, isReferenceFileSummaryStructuredOutput, isScratchpadRefreshStructuredOutput, isTaskCompileStructuredOutput, } from "./structured-output.js";
9
9
  import { runCodexAppServerReadOnlyProcedure } from "../../providers/openai/app-server/read-only-procedure.js";
10
+ import { getProjectUserSkillsRoot } from "../../skills/index.js";
10
11
  function readOnlyAppServerOptions(options, prompt, outputSchema, skillSelectionName) {
11
12
  if (options.readOnlyAppServer === undefined) {
12
13
  throw new Error("Codex app-server read-only Procedure adapter is not configured");
@@ -36,6 +37,9 @@ function readOnlyAppServerOptions(options, prompt, outputSchema, skillSelectionN
36
37
  ...(options.readOnlyAppServer.tuttiHome === undefined
37
38
  ? {}
38
39
  : { tuttiHome: options.readOnlyAppServer.tuttiHome }),
40
+ ...(options.readOnlyAppServer.userSkillsRoot === undefined
41
+ ? {}
42
+ : { userSkillsRoot: options.readOnlyAppServer.userSkillsRoot }),
39
43
  ...(options.readOnlyAppServer.builtInSkillsRoot === undefined
40
44
  ? {}
41
45
  : { builtInSkillsRoot: options.readOnlyAppServer.builtInSkillsRoot }),
@@ -421,6 +425,7 @@ export function resolveProjectOpenAiProcedureWorkflowRunner(options) {
421
425
  apiKey: credential.secret,
422
426
  workspaceRoot: options.workspaceRoot,
423
427
  tuttiHome: options.tuttiHome,
428
+ userSkillsRoot: getProjectUserSkillsRoot(options.tuttiHome, options.projectId),
424
429
  ...(options.agentContext === undefined ? {} : { agentContext: options.agentContext }),
425
430
  },
426
431
  ...(options.recordUsage === undefined ? {} : { recordUsage: options.recordUsage }),
@@ -16,6 +16,7 @@ export type CodexAppServerReadOnlyProcedureInput<TOutput> = {
16
16
  webSearchMode?: CodexAppServerWebSearchMode;
17
17
  skillSelectionName?: CodexAppServerSkillSelectionName;
18
18
  tuttiHome?: string;
19
+ userSkillsRoot?: string;
19
20
  builtInSkillsRoot?: string;
20
21
  agentContext?: CodexAppServerAgentContextRuntime;
21
22
  strictLeakCheck?: boolean;
@@ -81,7 +81,7 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
81
81
  try {
82
82
  skillInputs = resolveCodexAppServerSkillInputs({
83
83
  ...(options.skillSelectionName === undefined ? {} : { selectionName: options.skillSelectionName }),
84
- ...(options.tuttiHome === undefined ? {} : { tuttiHome: options.tuttiHome }),
84
+ ...(options.userSkillsRoot === undefined ? {} : { userSkillsRoot: options.userSkillsRoot }),
85
85
  ...(options.builtInSkillsRoot === undefined
86
86
  ? {}
87
87
  : { builtInSkillsRoot: options.builtInSkillsRoot }),
@@ -100,6 +100,7 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
100
100
  options.workspaceRoot,
101
101
  options.appServerCwd ?? "",
102
102
  options.tuttiHome ?? "",
103
+ options.userSkillsRoot ?? "",
103
104
  options.builtInSkillsRoot ?? "",
104
105
  ]));
105
106
  }
@@ -145,6 +146,7 @@ export async function runCodexAppServerReadOnlyProcedure(options) {
145
146
  appServerCwd,
146
147
  ...readablePaths,
147
148
  options.tuttiHome ?? "",
149
+ options.userSkillsRoot ?? "",
148
150
  options.builtInSkillsRoot ?? "",
149
151
  ...(contextRuntimeHint?.sensitiveValues ?? []),
150
152
  ...skillInputs.map((skill) => skill.path),
@@ -20,10 +20,12 @@ function actionFromNotification(notification) {
20
20
  if (combined.includes("commandexecution") || combined.includes("command_execution")) {
21
21
  return "running_command";
22
22
  }
23
- if (combined.includes("filechange") || combined.includes("file_change")) {
23
+ if (method === "turn/diff/updated" ||
24
+ combined.includes("filechange") ||
25
+ combined.includes("file_change")) {
24
26
  return "editing_files";
25
27
  }
26
- if (combined.includes("plan")) {
28
+ if (method === "turn/plan/updated" || combined.includes("plan")) {
27
29
  return "planning";
28
30
  }
29
31
  if (combined.includes("mcptoolcall") ||
@@ -37,9 +39,6 @@ function actionFromNotification(notification) {
37
39
  if (combined.includes("reasoning")) {
38
40
  return "reading_context";
39
41
  }
40
- if (method === "item/agentMessage/delta" || method === "item/completed") {
41
- return "finalizing";
42
- }
43
42
  return undefined;
44
43
  }
45
44
  function runtimeTokenUsage(usage) {
@@ -56,13 +56,13 @@ export declare function resolveCodexAppServerSkillInputs(options: {
56
56
  selectionName?: CodexAppServerSkillSelectionName;
57
57
  selection?: CodexAppServerSkillSelection;
58
58
  builtInSkillsRoot?: string;
59
- tuttiHome?: string;
59
+ userSkillsRoot?: string;
60
60
  }): CodexAppServerSkillInput[];
61
61
  export declare function resolveCodexAppServerSkillsListWarmup(options: {
62
62
  cwd: string;
63
63
  selectionName?: CodexAppServerSkillSelectionName;
64
64
  selection?: CodexAppServerSkillSelection;
65
- tuttiHome?: string;
65
+ userSkillsRoot?: string;
66
66
  }): CodexAppServerSkillsListWarmup | undefined;
67
67
  export declare function buildCodexAppServerTurnInput(options: {
68
68
  prompt: string;
@@ -1,7 +1,7 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { join, resolve } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
- import { getUserSkillsRoot, listUserSkills } from "../../../skills/index.js";
4
+ import { listUserSkills } from "../../../skills/index.js";
5
5
  export class CodexAppServerSkillSelectionError extends Error {
6
6
  code;
7
7
  constructor(code, message) {
@@ -117,7 +117,7 @@ function resolveBuiltInSkill(options) {
117
117
  function resolveUserSkill(options) {
118
118
  return {
119
119
  name: options.name,
120
- path: join(getUserSkillsRoot(options.tuttiHome), options.name),
120
+ path: join(options.userSkillsRoot, options.name),
121
121
  };
122
122
  }
123
123
  export function resolveCodexAppServerSkillSelection(selectionName) {
@@ -134,8 +134,9 @@ export function resolveCodexAppServerSkillInputs(options) {
134
134
  : resolveCodexAppServerSkillSelection(options.selectionName));
135
135
  const builtInSkillsRoot = options.builtInSkillsRoot ?? DEFAULT_BUILT_IN_SKILLS_ROOT;
136
136
  const builtInSkills = selection.builtInSkillNames.map((name) => resolveBuiltInSkill({ builtInSkillsRoot, name }));
137
- const userSkills = selection.userSkillMode === "all_enabled" && options.tuttiHome !== undefined
138
- ? listUserSkills(options.tuttiHome).map((skill) => resolveUserSkill({ tuttiHome: options.tuttiHome, name: skill.name }))
137
+ const userSkillsRoot = options.userSkillsRoot;
138
+ const userSkills = selection.userSkillMode === "all_enabled" && userSkillsRoot !== undefined
139
+ ? listUserSkills(userSkillsRoot).map((skill) => resolveUserSkill({ userSkillsRoot, name: skill.name }))
139
140
  : [];
140
141
  return [...builtInSkills, ...userSkills];
141
142
  }
@@ -148,10 +149,10 @@ export function resolveCodexAppServerSkillsListWarmup(options) {
148
149
  contextApiScopes: [],
149
150
  }
150
151
  : resolveCodexAppServerSkillSelection(options.selectionName));
151
- if (selection.userSkillMode !== "all_enabled" || options.tuttiHome === undefined) {
152
+ if (selection.userSkillMode !== "all_enabled" || options.userSkillsRoot === undefined) {
152
153
  return undefined;
153
154
  }
154
- if (listUserSkills(options.tuttiHome).length === 0) {
155
+ if (listUserSkills(options.userSkillsRoot).length === 0) {
155
156
  return undefined;
156
157
  }
157
158
  return {
@@ -160,7 +161,7 @@ export function resolveCodexAppServerSkillsListWarmup(options) {
160
161
  perCwdExtraUserRoots: [
161
162
  {
162
163
  cwd: options.cwd,
163
- extraUserRoots: [getUserSkillsRoot(options.tuttiHome)],
164
+ extraUserRoots: [options.userSkillsRoot],
164
165
  },
165
166
  ],
166
167
  };
@@ -40,6 +40,7 @@ export type CodexAppServerWorkspaceWriteRunInput<TOutput> = {
40
40
  approvalsReviewer?: CodexAppServerApprovalsReviewer;
41
41
  skillSelectionName?: CodexAppServerSkillSelectionName;
42
42
  tuttiHome?: string;
43
+ userSkillsRoot?: string;
43
44
  builtInSkillsRoot?: string;
44
45
  agentContext?: CodexAppServerAgentContextRuntime;
45
46
  approvalHandler?: CodexAppServerApprovalHandler;
@@ -153,7 +153,7 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
153
153
  try {
154
154
  skillInputs = resolveCodexAppServerSkillInputs({
155
155
  ...(options.skillSelectionName === undefined ? {} : { selectionName: options.skillSelectionName }),
156
- ...(options.tuttiHome === undefined ? {} : { tuttiHome: options.tuttiHome }),
156
+ ...(options.userSkillsRoot === undefined ? {} : { userSkillsRoot: options.userSkillsRoot }),
157
157
  ...(options.builtInSkillsRoot === undefined
158
158
  ? {}
159
159
  : { builtInSkillsRoot: options.builtInSkillsRoot }),
@@ -161,7 +161,7 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
161
161
  skillsListWarmup = resolveCodexAppServerSkillsListWarmup({
162
162
  cwd: options.workspaceRoot,
163
163
  ...(options.skillSelectionName === undefined ? {} : { selectionName: options.skillSelectionName }),
164
- ...(options.tuttiHome === undefined ? {} : { tuttiHome: options.tuttiHome }),
164
+ ...(options.userSkillsRoot === undefined ? {} : { userSkillsRoot: options.userSkillsRoot }),
165
165
  });
166
166
  contextRuntimeHint = resolveCodexAppServerContextRuntimeHint({
167
167
  ...(options.skillSelectionName === undefined ? {} : { selectionName: options.skillSelectionName }),
@@ -176,6 +176,7 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
176
176
  options.apiKey,
177
177
  options.workspaceRoot,
178
178
  options.tuttiHome ?? "",
179
+ options.userSkillsRoot ?? "",
179
180
  options.builtInSkillsRoot ?? "",
180
181
  ]));
181
182
  }
@@ -219,6 +220,7 @@ export async function runCodexAppServerWorkspaceWriteRun(options) {
219
220
  codexHome,
220
221
  options.workspaceRoot,
221
222
  options.tuttiHome ?? "",
223
+ options.userSkillsRoot ?? "",
222
224
  options.builtInSkillsRoot ?? "",
223
225
  ...(contextRuntimeHint?.sensitiveValues ?? []),
224
226
  ...skillInputs.map((skill) => skill.path),
@@ -8,6 +8,7 @@ export type OpenAiReadOnlyChatAssistantModelOptions = {
8
8
  apiKey: string;
9
9
  workspaceRoot: string;
10
10
  tuttiHome?: string;
11
+ userSkillsRoot?: string;
11
12
  builtInSkillsRoot?: string;
12
13
  agentContext?: CodexAppServerAgentContextRuntime;
13
14
  codexCommand?: string;
@@ -2,6 +2,7 @@ import { CHAT_ASSISTANT_OUTPUT_SCHEMA, isChatAssistantOutput, } from "../../chat
2
2
  import { readOpenAiApiKeyCredential } from "./credential-store.js";
3
3
  import { resolveOpenAiProviderConfig, } from "./provider-config.js";
4
4
  import { runCodexAppServerReadOnlyProcedure } from "./app-server/read-only-procedure.js";
5
+ import { getProjectUserSkillsRoot } from "../../skills/index.js";
5
6
  export function createOpenAiReadOnlyChatAssistantModel(options) {
6
7
  return {
7
8
  async answer(input) {
@@ -19,6 +20,7 @@ export function createOpenAiReadOnlyChatAssistantModel(options) {
19
20
  webSearchMode: "live",
20
21
  skillSelectionName: "chat_assistant",
21
22
  ...(options.tuttiHome === undefined ? {} : { tuttiHome: options.tuttiHome }),
23
+ ...(options.userSkillsRoot === undefined ? {} : { userSkillsRoot: options.userSkillsRoot }),
22
24
  ...(options.builtInSkillsRoot === undefined
23
25
  ? {}
24
26
  : { builtInSkillsRoot: options.builtInSkillsRoot }),
@@ -60,6 +62,7 @@ export function resolveProjectOpenAiReadOnlyChatAssistantModel(options) {
60
62
  apiKey: credential.secret,
61
63
  workspaceRoot: options.workspaceRoot,
62
64
  tuttiHome: options.tuttiHome,
65
+ userSkillsRoot: getProjectUserSkillsRoot(options.tuttiHome, options.projectId),
63
66
  ...(options.agentContext === undefined ? {} : { agentContext: options.agentContext }),
64
67
  ...(options.recordUsage === undefined ? {} : { recordUsage: options.recordUsage }),
65
68
  }),
@@ -13,6 +13,7 @@ export type OpenAiRunPipelineRunnerOptions = {
13
13
  mergeWorkspaceRoot?: string;
14
14
  promptsRoot?: string;
15
15
  tuttiHome?: string;
16
+ userSkillsRoot?: string;
16
17
  builtInSkillsRoot?: string;
17
18
  agentContext?: CodexAppServerAgentContextRuntime;
18
19
  approvalManager?: ApprovalRequestManager;
@@ -7,6 +7,7 @@ import { runWorkspaceWriteTask } from "./task-run-invocation.js";
7
7
  import { readOpenAiApiKeyCredential } from "../providers/openai/credential-store.js";
8
8
  import { resolveOpenAiProviderConfig, } from "../providers/openai/provider-config.js";
9
9
  import { CodexAppServerWorkspaceWriteRunError } from "../providers/openai/app-server/workspace-write-run.js";
10
+ import { getProjectUserSkillsRoot } from "../skills/index.js";
10
11
  import { cleanupRunWorkspace, collectRunDiff, createResultCommit, prepareRunWorkspace, promoteRunResult, WorkspaceOpsError, } from "../workspace-ops/index.js";
11
12
  export { TASK_RUN_OUTPUT_SCHEMA } from "./task-run-output.js";
12
13
  function isRecord(value) {
@@ -535,6 +536,7 @@ export function resolveProjectOpenAiRunPipelineRunner(options) {
535
536
  runWorkspaceRoot: join(options.runWorkspaceRoot, "run-workspaces"),
536
537
  mergeWorkspaceRoot: join(options.mergeWorkspaceRoot ?? options.runWorkspaceRoot, "merge-workspaces"),
537
538
  tuttiHome: options.tuttiHome,
539
+ userSkillsRoot: getProjectUserSkillsRoot(options.tuttiHome, options.projectId),
538
540
  ...(options.agentContext === undefined ? {} : { agentContext: options.agentContext }),
539
541
  ...(options.approvalManager === undefined
540
542
  ? {}
@@ -99,6 +99,7 @@ export async function runWorkspaceWriteTask(options, input, handle) {
99
99
  ? {}
100
100
  : { apiBaseUrl: options.config.api_base_url }),
101
101
  ...(options.tuttiHome === undefined ? {} : { tuttiHome: options.tuttiHome }),
102
+ ...(options.userSkillsRoot === undefined ? {} : { userSkillsRoot: options.userSkillsRoot }),
102
103
  ...(options.builtInSkillsRoot === undefined
103
104
  ? {}
104
105
  : { builtInSkillsRoot: options.builtInSkillsRoot }),
@@ -21,7 +21,7 @@ import { createTrustedRelaySessionMetadataStore, } from "../session/relay-sessio
21
21
  import { LaunchError } from "./errors.js";
22
22
  import { relayStateFromLaunchStatus } from "./host-relay-status.js";
23
23
  import { appendHostLogLine, readHostRegistrationSecret, createMachineRuntimeEndpointToken, deleteMachineRuntimeEndpoint, ensureHostLogFile, getHostLogFilePath, writeMachineRuntimeEndpoint, } from "./machine-local.js";
24
- import { readProjectDisplayName, writeProjectDisplayName } from "./project-identity.js";
24
+ import { readProjectDescription, readProjectDisplayName, writeProjectDescription, writeProjectDisplayName, } from "./project-identity.js";
25
25
  export const HOST_SERVER_VERSION = "0.0.0";
26
26
  function createHostRuntimeLogger(options) {
27
27
  return {
@@ -163,6 +163,7 @@ export async function startForegroundHostServer(options) {
163
163
  };
164
164
  let projectDisplayName = readProjectDisplayName(options.preparation.workspace_root) ??
165
165
  (basename(options.preparation.workspace_root) || options.preparation.project_id);
166
+ let projectDescription = readProjectDescription(options.preparation.workspace_root) ?? "";
166
167
  const projectContext = {
167
168
  workspaceRoot: options.preparation.workspace_root,
168
169
  projectName: projectDisplayName,
@@ -174,6 +175,7 @@ export async function startForegroundHostServer(options) {
174
175
  const projectApiProject = {
175
176
  project_id: options.preparation.project_id,
176
177
  display_name: projectDisplayName,
178
+ ...(projectDescription.length === 0 ? {} : { description: projectDescription }),
177
179
  identity_source: "git_config",
178
180
  };
179
181
  const updateRuntimeProjectDisplayName = (displayName) => {
@@ -183,6 +185,16 @@ export async function startForegroundHostServer(options) {
183
185
  chatAssistantProject.display_name = displayName;
184
186
  projectApiProject.display_name = displayName;
185
187
  };
188
+ const updateRuntimeProjectDescription = (description) => {
189
+ writeProjectDescription(options.preparation.workspace_root, description);
190
+ projectDescription = description;
191
+ if (description.length === 0) {
192
+ delete projectApiProject.description;
193
+ }
194
+ else {
195
+ projectApiProject.description = description;
196
+ }
197
+ };
186
198
  const controlPlaneLogger = createHostRuntimeLogger({
187
199
  component: "control-plane",
188
200
  logFilePath,
@@ -353,6 +365,9 @@ export async function startForegroundHostServer(options) {
353
365
  updateProjectDisplayName: (displayName) => {
354
366
  updateRuntimeProjectDisplayName(displayName);
355
367
  },
368
+ updateProjectDescription: (description) => {
369
+ updateRuntimeProjectDescription(description);
370
+ },
356
371
  ...(resolveStagedUpload === undefined ? {} : { resolveStagedUpload }),
357
372
  capabilities: {
358
373
  clarification: true,
@@ -1,6 +1,7 @@
1
1
  import { type ProjectId } from "@tutti/shared/ids";
2
2
  export declare const PROJECT_ID_GIT_CONFIG_KEY = "tutti.project-id";
3
3
  export declare const PROJECT_DISPLAY_NAME_GIT_CONFIG_KEY = "tutti.project-display-name";
4
+ export declare const PROJECT_DESCRIPTION_GIT_CONFIG_KEY = "tutti.project-description";
4
5
  export type ProjectIdentityReadResult = {
5
6
  kind: "missing";
6
7
  } | {
@@ -12,6 +13,8 @@ export declare function readProjectIdentity(workspaceRoot: string): ProjectIdent
12
13
  export declare function writeProjectIdentity(workspaceRoot: string, projectId: ProjectId): void;
13
14
  export declare function readProjectDisplayName(workspaceRoot: string): string | null;
14
15
  export declare function writeProjectDisplayName(workspaceRoot: string, displayName: string): void;
16
+ export declare function readProjectDescription(workspaceRoot: string): string | null;
17
+ export declare function writeProjectDescription(workspaceRoot: string, description: string): void;
15
18
  export declare function ensureProjectIdentity(workspaceRoot: string, projectId?: ProjectId): {
16
19
  project_id: ProjectId;
17
20
  created: boolean;
@@ -5,6 +5,7 @@ import { createProjectId, ID_PREFIXES, isPrefixedId } from "@tutti/shared/ids";
5
5
  import { LaunchError } from "./errors.js";
6
6
  export const PROJECT_ID_GIT_CONFIG_KEY = "tutti.project-id";
7
7
  export const PROJECT_DISPLAY_NAME_GIT_CONFIG_KEY = "tutti.project-display-name";
8
+ export const PROJECT_DESCRIPTION_GIT_CONFIG_KEY = "tutti.project-description";
8
9
  function runGitRaw(args, cwd) {
9
10
  const result = spawnSync("git", [...args], {
10
11
  cwd,
@@ -76,6 +77,29 @@ export function readProjectDisplayName(workspaceRoot) {
76
77
  export function writeProjectDisplayName(workspaceRoot, displayName) {
77
78
  runGit(["config", "--local", PROJECT_DISPLAY_NAME_GIT_CONFIG_KEY, displayName], resolve(workspaceRoot));
78
79
  }
80
+ export function readProjectDescription(workspaceRoot) {
81
+ const result = runGitRaw(["config", "--local", "--get", PROJECT_DESCRIPTION_GIT_CONFIG_KEY], resolve(workspaceRoot));
82
+ if (result.status !== 0) {
83
+ return null;
84
+ }
85
+ const description = result.stdout.trim();
86
+ return description.length === 0 ? null : description;
87
+ }
88
+ export function writeProjectDescription(workspaceRoot, description) {
89
+ const workspace = resolve(workspaceRoot);
90
+ const trimmedDescription = description.trim();
91
+ if (trimmedDescription.length === 0) {
92
+ const result = runGitRaw(["config", "--local", "--unset", PROJECT_DESCRIPTION_GIT_CONFIG_KEY], workspace);
93
+ if (result.status === 0 || result.status === 5) {
94
+ return;
95
+ }
96
+ const stderr = result.stderr.trim();
97
+ throw new LaunchError("git_bootstrap_failed", stderr.length > 0
98
+ ? stderr
99
+ : `git config --local --unset ${PROJECT_DESCRIPTION_GIT_CONFIG_KEY} failed`, "Resolve the Git error and run `tutti launch` again.");
100
+ }
101
+ runGit(["config", "--local", PROJECT_DESCRIPTION_GIT_CONFIG_KEY, trimmedDescription], workspace);
102
+ }
79
103
  export function ensureProjectIdentity(workspaceRoot, projectId = createProjectId()) {
80
104
  const existing = readProjectIdentity(workspaceRoot);
81
105
  if (existing.kind === "present") {
@@ -31,4 +31,5 @@ export type AgentContextRouteOptions = Pick<HostProjectApiRouteOptions, "project
31
31
  export declare function requireProjectStore(options: AgentContextRouteOptions): HostProjectStore;
32
32
  export declare function requireWorkspaceRoot(options: AgentContextRouteOptions): string;
33
33
  export declare function requireTuttiHome(options: AgentContextRouteOptions): string;
34
+ export declare function requireProjectUserSkillsRoot(options: AgentContextRouteOptions): string;
34
35
  //# sourceMappingURL=agent-context-shared.d.ts.map
@@ -1,3 +1,4 @@
1
+ import { getProjectUserSkillsRoot } from "../../../skills/index.js";
1
2
  import { AgentContextBadRequestError } from "./agent-context-errors.js";
2
3
  export const AGENT_CONTEXT_BASE_PATH = "/internal/agent-context";
3
4
  export const MESSAGE_LIMIT_DEFAULT = 50;
@@ -41,4 +42,7 @@ export function requireTuttiHome(options) {
41
42
  }
42
43
  return options.tuttiHome;
43
44
  }
45
+ export function requireProjectUserSkillsRoot(options) {
46
+ return getProjectUserSkillsRoot(requireTuttiHome(options), options.project.project_id);
47
+ }
44
48
  //# sourceMappingURL=agent-context-shared.js.map
@@ -3,7 +3,7 @@ import { listUserSkills, readUserSkill, SkillServiceError, } from "../../../skil
3
3
  import { authorizeAgentContextRequest } from "./agent-context-auth.js";
4
4
  import { AgentContextBadRequestError } from "./agent-context-errors.js";
5
5
  import { boundedLimit, pageByOffset, queryString, truncateText } from "./agent-context-query.js";
6
- import { AGENT_CONTEXT_BASE_PATH, USER_SKILLS_LIMIT_DEFAULT, USER_SKILLS_LIMIT_MAX, USER_SKILL_MD_DEFAULT_MAX_CHARS, USER_SKILL_MD_MAX_CHARS, requireTuttiHome, } from "./agent-context-shared.js";
6
+ import { AGENT_CONTEXT_BASE_PATH, USER_SKILLS_LIMIT_DEFAULT, USER_SKILLS_LIMIT_MAX, USER_SKILL_MD_DEFAULT_MAX_CHARS, USER_SKILL_MD_MAX_CHARS, requireProjectUserSkillsRoot, } from "./agent-context-shared.js";
7
7
  function skillUnavailable(input) {
8
8
  return {
9
9
  kind: "unavailable",
@@ -37,7 +37,7 @@ export function registerAgentContextSkillRoutes(app, options) {
37
37
  max: USER_SKILLS_LIMIT_MAX,
38
38
  });
39
39
  const cursor = queryString(request.query.cursor);
40
- const page = pageByOffset(listUserSkills(requireTuttiHome(options)), {
40
+ const page = pageByOffset(listUserSkills(requireProjectUserSkillsRoot(options)), {
41
41
  endpoint: "skills",
42
42
  ...(cursor === undefined ? {} : { cursor }),
43
43
  limit,
@@ -59,7 +59,7 @@ export function registerAgentContextSkillRoutes(app, options) {
59
59
  });
60
60
  const skillName = request.params.skillName;
61
61
  try {
62
- const skill = readUserSkill(requireTuttiHome(options), skillName);
62
+ const skill = readUserSkill(requireProjectUserSkillsRoot(options), skillName);
63
63
  const skillMd = truncateText(redactText(skill.skill_md), maxChars);
64
64
  return {
65
65
  kind: "skill",
@@ -6,5 +6,6 @@ export declare function schedulerRunNowInvalidates(disposition: RunSchedulerNowD
6
6
  export declare function clarificationRoundInvalidates(roundId: ClarificationRoundRef): QueryInvalidationHint[];
7
7
  export declare function approvalInvalidates(): QueryInvalidationHint[];
8
8
  export declare function readStateInvalidates(): QueryInvalidationHint[];
9
+ export declare function projectMetadataInvalidates(): QueryInvalidationHint[];
9
10
  export declare function projectDisplayNameInvalidates(): QueryInvalidationHint[];
10
11
  //# sourceMappingURL=invalidations.d.ts.map
@@ -51,7 +51,10 @@ export function approvalInvalidates() {
51
51
  export function readStateInvalidates() {
52
52
  return [{ kind: "read_state" }, { kind: "bootstrap" }];
53
53
  }
54
- export function projectDisplayNameInvalidates() {
54
+ export function projectMetadataInvalidates() {
55
55
  return [{ kind: "workspace" }, { kind: "bootstrap" }];
56
56
  }
57
+ export function projectDisplayNameInvalidates() {
58
+ return projectMetadataInvalidates();
59
+ }
57
60
  //# sourceMappingURL=invalidations.js.map
@@ -435,6 +435,10 @@ export declare const HOST_PROJECT_WORKSPACE_OPENAPI_ROUTES: ({
435
435
  readonly type: "string";
436
436
  readonly minLength: 1;
437
437
  };
438
+ readonly description: {
439
+ readonly type: "string";
440
+ readonly maxLength: 160;
441
+ };
438
442
  readonly identity_source: {
439
443
  readonly type: "string";
440
444
  readonly enum: readonly ["git_config"];
@@ -631,6 +635,10 @@ export declare const HOST_PROJECT_WORKSPACE_OPENAPI_ROUTES: ({
631
635
  readonly type: "string";
632
636
  readonly minLength: 1;
633
637
  };
638
+ readonly description: {
639
+ readonly type: "string";
640
+ readonly maxLength: 160;
641
+ };
634
642
  readonly identity_source: {
635
643
  readonly type: "string";
636
644
  readonly enum: readonly ["git_config"];
@@ -1,4 +1,4 @@
1
- import { MarkReadBodySchema, MarkReadResponseSchema, ReadStateSummaryProjectionSchema, UpdateProjectDisplayNameBodySchema, UpdateProjectDisplayNameResponseSchema, } from "@tutti/shared/schemas/api";
1
+ import { MarkReadBodySchema, MarkReadResponseSchema, ReadStateSummaryProjectionSchema, UpdateProjectDescriptionBodySchema, UpdateProjectDescriptionResponseSchema, UpdateProjectDisplayNameBodySchema, UpdateProjectDisplayNameResponseSchema, } from "@tutti/shared/schemas/api";
2
2
  import { HOST_PROJECT_API_BASE_PATH } from "./constants.js";
3
3
  import { BootstrapResponseSchema, ProviderConfigProjectionSchema, ProviderUsageProjectionSchema, SessionResponseSchema, WorkspaceResponseSchema, } from "./schemas.js";
4
4
  export const HOST_PROJECT_WORKSPACE_OPENAPI_ROUTES = [
@@ -57,6 +57,19 @@ export const HOST_PROJECT_WORKSPACE_OPENAPI_ROUTES = [
57
57
  },
58
58
  },
59
59
  },
60
+ {
61
+ method: "post",
62
+ path: `${HOST_PROJECT_API_BASE_PATH}/workspace/project-description`,
63
+ summary: "Update the current project description.",
64
+ tags: ["workspace"],
65
+ requestBody: UpdateProjectDescriptionBodySchema,
66
+ responses: {
67
+ 200: {
68
+ description: "Project description update command response.",
69
+ schema: UpdateProjectDescriptionResponseSchema,
70
+ },
71
+ },
72
+ },
60
73
  {
61
74
  method: "get",
62
75
  path: `${HOST_PROJECT_API_BASE_PATH}/bootstrap`,
@@ -1600,6 +1600,10 @@ export declare const HOST_PROJECT_OPENAPI_ROUTES: ({
1600
1600
  readonly type: "string";
1601
1601
  readonly minLength: 1;
1602
1602
  };
1603
+ readonly description: {
1604
+ readonly type: "string";
1605
+ readonly maxLength: 160;
1606
+ };
1603
1607
  readonly identity_source: {
1604
1608
  readonly type: "string";
1605
1609
  readonly enum: readonly ["git_config"];
@@ -1784,6 +1788,10 @@ export declare const HOST_PROJECT_OPENAPI_ROUTES: ({
1784
1788
  readonly type: "string";
1785
1789
  readonly minLength: 1;
1786
1790
  };
1791
+ readonly description: {
1792
+ readonly type: "string";
1793
+ readonly maxLength: 160;
1794
+ };
1787
1795
  readonly identity_source: {
1788
1796
  readonly type: "string";
1789
1797
  readonly enum: readonly ["git_config"];
@@ -1,6 +1,7 @@
1
- import type { GetMessagesRequest, SendClarificationRoundMessagePayload, SendMainChatMessagePayload, UpdateProjectDisplayNamePayload } from "@tutti/shared/schemas/api";
1
+ import type { GetMessagesRequest, SendClarificationRoundMessagePayload, SendMainChatMessagePayload, UpdateProjectDescriptionPayload, UpdateProjectDisplayNamePayload } from "@tutti/shared/schemas/api";
2
2
  export declare function requireValidMessageWindowRequest(query: GetMessagesRequest): GetMessagesRequest;
3
3
  export declare function requireValidSendMainChatPayload(payload: SendMainChatMessagePayload): SendMainChatMessagePayload;
4
4
  export declare function requireValidRoundMessagePayload(payload: SendClarificationRoundMessagePayload): SendClarificationRoundMessagePayload;
5
5
  export declare function requireValidProjectDisplayNamePayload(payload: UpdateProjectDisplayNamePayload): UpdateProjectDisplayNamePayload;
6
+ export declare function requireValidProjectDescriptionPayload(payload: UpdateProjectDescriptionPayload): UpdateProjectDescriptionPayload;
6
7
  //# sourceMappingURL=payload-validation.d.ts.map
@@ -33,4 +33,11 @@ export function requireValidProjectDisplayNamePayload(payload) {
33
33
  }
34
34
  return { display_name: displayName };
35
35
  }
36
+ export function requireValidProjectDescriptionPayload(payload) {
37
+ const description = payload.description.trim();
38
+ if (description.length > 160) {
39
+ throw new HostProjectValidationError("Project description cannot exceed 160 characters");
40
+ }
41
+ return { description };
42
+ }
36
43
  //# sourceMappingURL=payload-validation.js.map
@@ -440,6 +440,10 @@ export declare const WorkspaceResponseSchema: {
440
440
  readonly type: "string";
441
441
  readonly minLength: 1;
442
442
  };
443
+ readonly description: {
444
+ readonly type: "string";
445
+ readonly maxLength: 160;
446
+ };
443
447
  readonly identity_source: {
444
448
  readonly type: "string";
445
449
  readonly enum: readonly ["git_config"];
@@ -613,6 +617,10 @@ export declare const BootstrapResponseSchema: {
613
617
  readonly type: "string";
614
618
  readonly minLength: 1;
615
619
  };
620
+ readonly description: {
621
+ readonly type: "string";
622
+ readonly maxLength: 160;
623
+ };
616
624
  readonly identity_source: {
617
625
  readonly type: "string";
618
626
  readonly enum: readonly ["git_config"];
@@ -231,6 +231,7 @@ export const WorkspaceResponseSchema = {
231
231
  properties: {
232
232
  project_id: ProjectIdSchema,
233
233
  display_name: { type: "string", minLength: 1 },
234
+ description: { type: "string", maxLength: 160 },
234
235
  identity_source: { type: "string", enum: ["git_config"] },
235
236
  },
236
237
  },
@@ -2,6 +2,7 @@ import type { QueryInvalidationHint } from "@tutti/shared/schemas/api";
2
2
  import type { HostProjectApiRouteOptions } from "./types.js";
3
3
  export declare function skillsChangedInvalidates(): QueryInvalidationHint[];
4
4
  export declare function requireTuttiHome(options: HostProjectApiRouteOptions): string;
5
+ export declare function requireProjectUserSkillsRoot(options: HostProjectApiRouteOptions): string;
5
6
  export declare function withAsyncSkillServiceErrorMapping<T>(fn: () => Promise<T>): Promise<T>;
6
7
  export declare function withSkillServiceErrorMapping<T>(fn: () => T): T;
7
8
  //# sourceMappingURL=skills-helpers.d.ts.map
@@ -1,4 +1,4 @@
1
- import { SkillServiceError } from "../../../../skills/index.js";
1
+ import { getProjectUserSkillsRoot, SkillServiceError } from "../../../../skills/index.js";
2
2
  import { HostProjectBadRequestError, HostProjectNotFoundError, HostProjectStoreUnavailableError, HostProjectValidationError, } from "./errors.js";
3
3
  export function skillsChangedInvalidates() {
4
4
  return [{ kind: "skills" }, { kind: "read_state" }, { kind: "bootstrap" }];
@@ -9,6 +9,9 @@ export function requireTuttiHome(options) {
9
9
  }
10
10
  return options.tuttiHome;
11
11
  }
12
+ export function requireProjectUserSkillsRoot(options) {
13
+ return getProjectUserSkillsRoot(requireTuttiHome(options), options.project.project_id);
14
+ }
12
15
  function toHostProjectSkillError(error) {
13
16
  switch (error.code) {
14
17
  case "invalid_skill_name":