@xfey/tutti 0.1.14 → 0.1.16

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 (62) hide show
  1. package/dist/collaboration-state/index.d.ts +2 -2
  2. package/dist/collaboration-state/index.js +1 -1
  3. package/dist/collaboration-state/scratchpad.d.ts +2 -2
  4. package/dist/collaboration-state/scratchpad.js +94 -7
  5. package/dist/collaboration-state/storage-types.d.ts +14 -0
  6. package/dist/collaboration-state/types.d.ts +9 -1
  7. package/dist/control-plane/task-compile-output.js +7 -3
  8. package/dist/control-plane/task-compile-start.js +1 -1
  9. package/dist/control-plane/workflows/openai.d.ts +1 -0
  10. package/dist/control-plane/workflows/openai.js +5 -0
  11. package/dist/providers/openai/app-server/read-only-procedure.d.ts +1 -0
  12. package/dist/providers/openai/app-server/read-only-procedure.js +3 -1
  13. package/dist/providers/openai/app-server/skills.d.ts +2 -2
  14. package/dist/providers/openai/app-server/skills.js +8 -7
  15. package/dist/providers/openai/app-server/workspace-write-run.d.ts +1 -0
  16. package/dist/providers/openai/app-server/workspace-write-run.js +4 -2
  17. package/dist/providers/openai/chat-assistant.d.ts +1 -0
  18. package/dist/providers/openai/chat-assistant.js +3 -0
  19. package/dist/run-pipeline/openai.d.ts +1 -0
  20. package/dist/run-pipeline/openai.js +2 -0
  21. package/dist/run-pipeline/task-run-invocation.js +1 -0
  22. package/dist/server-shell/cli/host-server-runtime.js +16 -1
  23. package/dist/server-shell/cli/project-identity.d.ts +3 -0
  24. package/dist/server-shell/cli/project-identity.js +24 -0
  25. package/dist/server-shell/http/routes/agent-context-shared.d.ts +1 -0
  26. package/dist/server-shell/http/routes/agent-context-shared.js +4 -0
  27. package/dist/server-shell/http/routes/agent-context-skills-routes.js +3 -3
  28. package/dist/server-shell/http/routes/project-api/invalidations.d.ts +1 -0
  29. package/dist/server-shell/http/routes/project-api/invalidations.js +4 -1
  30. package/dist/server-shell/http/routes/project-api/openapi-collaboration-routes.d.ts +14 -1
  31. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +22 -1
  32. package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.js +14 -1
  33. package/dist/server-shell/http/routes/project-api/openapi.d.ts +36 -2
  34. package/dist/server-shell/http/routes/project-api/payload-validation.d.ts +2 -1
  35. package/dist/server-shell/http/routes/project-api/payload-validation.js +7 -0
  36. package/dist/server-shell/http/routes/project-api/schemas.d.ts +22 -1
  37. package/dist/server-shell/http/routes/project-api/schemas.js +1 -0
  38. package/dist/server-shell/http/routes/project-api/skills-helpers.d.ts +1 -0
  39. package/dist/server-shell/http/routes/project-api/skills-helpers.js +4 -1
  40. package/dist/server-shell/http/routes/project-api/skills-routes.js +5 -5
  41. package/dist/server-shell/http/routes/project-api/system-routes.js +66 -4
  42. package/dist/server-shell/http/routes/project-api/types.d.ts +2 -0
  43. package/dist/skills/index.d.ts +7 -6
  44. package/dist/skills/index.js +18 -21
  45. package/migrations/0012_scratchpad_receipts.sql +31 -0
  46. package/migrations/README.md +2 -1
  47. package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +2 -2
  48. package/node_modules/@tutti/shared/dist/schemas/api/index.js +2 -2
  49. package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +14 -1
  50. package/node_modules/@tutti/shared/dist/schemas/api/types.d.ts +7 -2
  51. package/node_modules/@tutti/shared/dist/schemas/api/work-items.d.ts +24 -1
  52. package/node_modules/@tutti/shared/dist/schemas/api/work-items.js +15 -1
  53. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.d.ts +46 -2
  54. package/node_modules/@tutti/shared/dist/schemas/api/workspace-commands.js +15 -0
  55. package/package.json +1 -1
  56. package/prompts/prompt-flow-map.md +5 -5
  57. package/prompts/skills/read-user-skills/SKILL.md +1 -1
  58. package/web/assets/index-DoZOFmIq.js +29 -0
  59. package/web/assets/index-mXkNKFjm.css +1 -0
  60. package/web/index.html +8 -2
  61. package/web/assets/index-Cm_uyHbH.js +0 -29
  62. package/web/assets/index-DIQpGeKY.css +0 -1
@@ -1,7 +1,7 @@
1
1
  export { CollaborationStateCursorError } from "./serialization.js";
2
2
  export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
3
  export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesAfter, readMainChatMessagesAfterCursor, readMainChatMessagesBeforeCursor, } from "./messages.js";
4
- export { clearScratchpadProjection, createEmptyScratchpadProjection, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
4
+ export { createEmptyScratchpadProjection, 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";
7
7
  export { createClarificationRoundAgentMessage, createClarificationRoundMessage, readClarificationRoundMessagesWindow, } from "./clarification-messages.js";
@@ -13,5 +13,5 @@ export { recoverStaleRunningTasksAsFailed } from "./recovery.js";
13
13
  export { markReadStateScope, readReadStateSummary, recordReadNotificationEvent, } from "./read-state.js";
14
14
  export { readTaskDetailProjection } from "./task-details.js";
15
15
  export { applyTaskCompileProposal, markTaskRunStarted, readNextRunnableTask, readRecoverableScheduledRun, readTaskProjection, readWorklistProjection, } from "./worklist.js";
16
- export type { ApplyTaskCompileProposalResult, CloseTaskBoundClarificationRetaskingRequiredInput, CloseTaskBoundClarificationRetaskingRequiredResult, CreateClarificationRoundAgentMessageInput, CreateClarificationRoundMessageInput, CreateMainChatAgentMessageInput, CreateMainChatMessageInput, CreateMainChatSystemMessageInput, CursorPageOptions, MarkReadStateScopeInput, MarkReadStateScopeResult, MarkTaskRunStartedInput, MarkTaskRunStartedResult, MaterializeClarificationSuccessorInput, MaterializeTaskBoundRunSuccessorInput, MaterializeTaskBoundRunSuccessorResult, MessageAuthorInput, NextRunnableTaskResult, OpenNextTaskBoundClarificationRoundInput, OpenNextTaskBoundClarificationRoundResult, OpenNextTaskCompileClarificationRoundInput, OpenNextTaskCompileClarificationRoundResult, OpenTaskCompileClarificationRoundInput, OpenTaskCompileClarificationRoundResult, ReadMainChatMessagesAfterOptions, ReadMessageWindowOptions, ReadReadStateSummaryInput, RecoverStaleRunningTasksResult, RecoverableScheduledRun, RecordReadNotificationEventInput, RecordRecoverySummaryInput, RecordRecoverySummaryResult, RecordRunNeedsHumanResultInput, RecordRunNeedsHumanResultResult, RecordRunResultInput, RecordRunResultResult, RunResultPromotionProjection, ScratchpadSnapshotInput, SealedClarificationRoundWithoutSuccessor, SubmitClarificationRoundInput, TaskCompileProposalInput, TaskCompileProposalModuleInput, TaskCompileProposalTaskInput, } from "./types.js";
16
+ export type { ApplyTaskCompileProposalResult, CloseTaskBoundClarificationRetaskingRequiredInput, CloseTaskBoundClarificationRetaskingRequiredResult, CreateClarificationRoundAgentMessageInput, CreateClarificationRoundMessageInput, CreateMainChatAgentMessageInput, CreateMainChatMessageInput, CreateMainChatSystemMessageInput, CursorPageOptions, MarkReadStateScopeInput, MarkReadStateScopeResult, MarkTaskRunStartedInput, MarkTaskRunStartedResult, MaterializeClarificationSuccessorInput, MaterializeTaskBoundRunSuccessorInput, MaterializeTaskBoundRunSuccessorResult, MessageAuthorInput, NextRunnableTaskResult, OpenNextTaskBoundClarificationRoundInput, OpenNextTaskBoundClarificationRoundResult, OpenNextTaskCompileClarificationRoundInput, OpenNextTaskCompileClarificationRoundResult, OpenTaskCompileClarificationRoundInput, OpenTaskCompileClarificationRoundResult, ReadMainChatMessagesAfterOptions, ReadMessageWindowOptions, ReadReadStateSummaryInput, RecoverStaleRunningTasksResult, RecoverableScheduledRun, RecordReadNotificationEventInput, RecordRecoverySummaryInput, RecordRecoverySummaryResult, RecordRunNeedsHumanResultInput, RecordRunNeedsHumanResultResult, RecordRunResultInput, RecordRunResultResult, RunResultPromotionProjection, ScratchpadSnapshotInput, SubmitScratchpadInput, SubmitScratchpadResult, SealedClarificationRoundWithoutSuccessor, SubmitClarificationRoundInput, TaskCompileProposalInput, TaskCompileProposalModuleInput, TaskCompileProposalTaskInput, } from "./types.js";
17
17
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,7 @@
1
1
  export { CollaborationStateCursorError } from "./serialization.js";
2
2
  export { createIdleExecutionStatusProjection } from "./execution-status.js";
3
3
  export { createMainChatAgentMessage, createMainChatMessage, createMainChatSystemMessage, hasMainChatWorklistFeedbackMessage, readCompletedWorklistFeedbackTaskIds, readMessageProjectionById, readMainChatMessageWindow, readMainChatMessagesAfter, readMainChatMessagesAfterCursor, readMainChatMessagesBeforeCursor, } from "./messages.js";
4
- export { clearScratchpadProjection, createEmptyScratchpadProjection, readScratchpadProjection, upsertScratchpadProjection, } from "./scratchpad.js";
4
+ export { createEmptyScratchpadProjection, 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";
7
7
  export { createClarificationRoundAgentMessage, createClarificationRoundMessage, readClarificationRoundMessagesWindow, } from "./clarification-messages.js";
@@ -1,8 +1,8 @@
1
1
  import type { ScratchpadProjection } from "@tutti/shared/schemas/api";
2
2
  import type { SqliteDatabase } from "../store/index.js";
3
- import type { ScratchpadSnapshotInput } from "./types.js";
3
+ import type { ScratchpadSnapshotInput, SubmitScratchpadInput } from "./types.js";
4
4
  export declare function createEmptyScratchpadProjection(): ScratchpadProjection;
5
5
  export declare function upsertScratchpadProjection(db: SqliteDatabase, input: ScratchpadSnapshotInput): ScratchpadProjection;
6
- export declare function clearScratchpadProjection(db: SqliteDatabase): ScratchpadProjection;
6
+ export declare function submitScratchpadProjection(db: SqliteDatabase, input: SubmitScratchpadInput): ScratchpadProjection;
7
7
  export declare function readScratchpadProjection(db: SqliteDatabase): ScratchpadProjection;
8
8
  //# sourceMappingURL=scratchpad.d.ts.map
@@ -1,4 +1,5 @@
1
1
  import { nowIso, parseJson, stringifyJson } from "./serialization.js";
2
+ const SCRATCHPAD_RECEIPT_LIMIT = 100;
2
3
  export function createEmptyScratchpadProjection() {
3
4
  return {
4
5
  id: "current",
@@ -8,8 +9,37 @@ export function createEmptyScratchpadProjection() {
8
9
  current_consensus: [],
9
10
  open_questions: [],
10
11
  task_changes: [],
12
+ receipts: [],
11
13
  };
12
14
  }
15
+ function readScratchpadReceipts(db) {
16
+ const rows = db
17
+ .prepare(`
18
+ SELECT
19
+ workflow_ref,
20
+ activity_ref,
21
+ topic,
22
+ background_summary,
23
+ current_consensus_json,
24
+ open_questions_json,
25
+ task_changes_json,
26
+ submitted_at
27
+ FROM scratchpad_receipts
28
+ ORDER BY submitted_at DESC, workflow_ref DESC
29
+ LIMIT ${SCRATCHPAD_RECEIPT_LIMIT}
30
+ `)
31
+ .all();
32
+ return rows.map((row) => ({
33
+ workflow_ref: row.workflow_ref,
34
+ activity_ref: row.activity_ref,
35
+ topic: row.topic,
36
+ background_summary: row.background_summary,
37
+ current_consensus: parseJson(row.current_consensus_json, []),
38
+ open_questions: parseJson(row.open_questions_json, []),
39
+ task_changes: parseJson(row.task_changes_json, []),
40
+ submitted_at: row.submitted_at,
41
+ }));
42
+ }
13
43
  export function upsertScratchpadProjection(db, input) {
14
44
  const updatedAt = nowIso(input.now);
15
45
  db.prepare(`
@@ -34,51 +64,108 @@ export function upsertScratchpadProjection(db, input) {
34
64
  ?
35
65
  )
36
66
  ON CONFLICT(id) DO UPDATE SET
67
+ state = 'ready',
37
68
  topic = excluded.topic,
38
69
  background_summary = excluded.background_summary,
39
70
  current_consensus_json = excluded.current_consensus_json,
40
71
  open_questions_json = excluded.open_questions_json,
41
72
  task_changes_json = excluded.task_changes_json,
42
73
  updated_by_activity_ref = excluded.updated_by_activity_ref,
43
- updated_at = excluded.updated_at
74
+ updated_at = excluded.updated_at,
75
+ submitted_by_activity_ref = NULL,
76
+ submitted_by_workflow_ref = NULL,
77
+ submitted_at = NULL
44
78
  `).run(input.topic, input.background_summary, stringifyJson(input.current_consensus), stringifyJson(input.open_questions), stringifyJson(input.task_changes), input.updated_by_activity_ref ?? null, updatedAt);
45
79
  return readScratchpadProjection(db);
46
80
  }
47
- export function clearScratchpadProjection(db) {
48
- db.prepare("DELETE FROM scratchpad WHERE id = 'current'").run();
49
- return createEmptyScratchpadProjection();
81
+ export function submitScratchpadProjection(db, input) {
82
+ const submittedAt = nowIso(input.now);
83
+ const scratchpad = readScratchpadProjection(db);
84
+ if (scratchpad.state === "empty") {
85
+ return scratchpad;
86
+ }
87
+ db.prepare(`
88
+ INSERT INTO scratchpad_receipts (
89
+ workflow_ref,
90
+ activity_ref,
91
+ topic,
92
+ background_summary,
93
+ current_consensus_json,
94
+ open_questions_json,
95
+ task_changes_json,
96
+ submitted_at
97
+ )
98
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
99
+ ON CONFLICT(workflow_ref) DO UPDATE SET
100
+ activity_ref = excluded.activity_ref,
101
+ topic = excluded.topic,
102
+ background_summary = excluded.background_summary,
103
+ current_consensus_json = excluded.current_consensus_json,
104
+ open_questions_json = excluded.open_questions_json,
105
+ task_changes_json = excluded.task_changes_json,
106
+ submitted_at = excluded.submitted_at
107
+ `).run(input.workflow_ref, input.activity_ref, scratchpad.topic, scratchpad.background_summary, stringifyJson(scratchpad.current_consensus), stringifyJson(scratchpad.open_questions), stringifyJson(scratchpad.task_changes), submittedAt);
108
+ db.prepare(`
109
+ UPDATE scratchpad
110
+ SET
111
+ state = 'submitted',
112
+ submitted_by_activity_ref = ?,
113
+ submitted_by_workflow_ref = ?,
114
+ submitted_at = ?,
115
+ updated_at = ?
116
+ WHERE id = 'current'
117
+ `).run(input.activity_ref, input.workflow_ref, submittedAt, submittedAt);
118
+ return readScratchpadProjection(db);
50
119
  }
51
120
  export function readScratchpadProjection(db) {
121
+ const receipts = readScratchpadReceipts(db);
52
122
  const row = db
53
123
  .prepare(`
54
124
  SELECT
125
+ state,
55
126
  topic,
56
127
  background_summary,
57
128
  current_consensus_json,
58
129
  open_questions_json,
59
130
  task_changes_json,
60
131
  updated_by_activity_ref,
61
- updated_at
132
+ updated_at,
133
+ submitted_by_activity_ref,
134
+ submitted_by_workflow_ref,
135
+ submitted_at
62
136
  FROM scratchpad
63
137
  WHERE id = 'current'
64
138
  `)
65
139
  .get();
66
140
  if (row === undefined) {
67
- return createEmptyScratchpadProjection();
141
+ return {
142
+ ...createEmptyScratchpadProjection(),
143
+ receipts,
144
+ };
68
145
  }
69
146
  const scratchpad = {
70
147
  id: "current",
71
- state: "ready",
148
+ state: row.state,
72
149
  topic: row.topic,
73
150
  background_summary: row.background_summary,
74
151
  current_consensus: parseJson(row.current_consensus_json, []),
75
152
  open_questions: parseJson(row.open_questions_json, []),
76
153
  task_changes: parseJson(row.task_changes_json, []),
77
154
  updated_at: row.updated_at,
155
+ receipts,
78
156
  };
79
157
  if (row.updated_by_activity_ref !== null) {
80
158
  scratchpad.updated_by_activity_ref = row.updated_by_activity_ref;
81
159
  }
160
+ if (row.submitted_by_activity_ref !== null) {
161
+ scratchpad.submitted_by_activity_ref = row.submitted_by_activity_ref;
162
+ }
163
+ if (row.submitted_by_workflow_ref !== null) {
164
+ scratchpad.submitted_by_workflow_ref = row.submitted_by_workflow_ref;
165
+ }
166
+ if (row.submitted_at !== null) {
167
+ scratchpad.submitted_at = row.submitted_at;
168
+ }
82
169
  return scratchpad;
83
170
  }
84
171
  //# sourceMappingURL=scratchpad.js.map
@@ -13,6 +13,7 @@ export type MessageRow = {
13
13
  created_at: string;
14
14
  };
15
15
  export type ScratchpadRow = {
16
+ state: "ready" | "submitted";
16
17
  topic: string;
17
18
  background_summary: string;
18
19
  current_consensus_json: string;
@@ -20,6 +21,19 @@ export type ScratchpadRow = {
20
21
  task_changes_json: string;
21
22
  updated_by_activity_ref: string | null;
22
23
  updated_at: string;
24
+ submitted_by_activity_ref: string | null;
25
+ submitted_by_workflow_ref: string | null;
26
+ submitted_at: string | null;
27
+ };
28
+ export type ScratchpadReceiptRow = {
29
+ workflow_ref: string;
30
+ activity_ref: string;
31
+ topic: string;
32
+ background_summary: string;
33
+ current_consensus_json: string;
34
+ open_questions_json: string;
35
+ task_changes_json: string;
36
+ submitted_at: string;
23
37
  };
24
38
  export type ScratchpadSourceStateRow = {
25
39
  main_chat_cursor_created_at: string | null;
@@ -1,7 +1,7 @@
1
1
  import type { RunLineage } from "@tutti/shared/domain";
2
2
  import type { AccountAvatarProjection } from "@tutti/shared/domain";
3
3
  import type { ActivityRef, ClarificationRoundRef, TaskId, TaskModuleId, WorkflowInvocationRef } from "@tutti/shared/ids";
4
- import type { ClarificationRequestPayload, ClarificationRoundProjection, ClarificationSuccessorRef, ClarificationThreadProjection, MessageProjection, ReadStateScope, ReadStateSummaryProjection, RecoverySummaryProjection, RunResultProjection, TaskContractProjection, TaskDetailProjection, TaskDetailResultProjection, TaskProjection, WorklistProjection } from "@tutti/shared/schemas/api";
4
+ import type { ClarificationRequestPayload, ClarificationRoundProjection, ClarificationSuccessorRef, ClarificationThreadProjection, MessageProjection, ReadStateScope, ReadStateSummaryProjection, RecoverySummaryProjection, RunResultProjection, ScratchpadProjection, TaskContractProjection, TaskDetailProjection, TaskDetailResultProjection, TaskProjection, WorklistProjection } from "@tutti/shared/schemas/api";
5
5
  export type MessageAuthorInput = {
6
6
  kind: "human" | "system" | "agent";
7
7
  ref?: string;
@@ -56,6 +56,14 @@ export type ScratchpadSnapshotInput = {
56
56
  updated_by_activity_ref?: ActivityRef;
57
57
  now?: () => Date;
58
58
  };
59
+ export type SubmitScratchpadInput = {
60
+ workflow_ref: WorkflowInvocationRef;
61
+ activity_ref: ActivityRef;
62
+ now?: () => Date;
63
+ };
64
+ export type SubmitScratchpadResult = {
65
+ scratchpad: ScratchpadProjection;
66
+ };
59
67
  export type TaskCompileProposalInput = {
60
68
  workflow_ref: WorkflowInvocationRef;
61
69
  proposal: {
@@ -1,4 +1,4 @@
1
- import { applyTaskCompileProposal, clearScratchpadProjection, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, readWorklistProjection, } from "../collaboration-state/index.js";
1
+ import { applyTaskCompileProposal, openNextTaskCompileClarificationRound, openTaskCompileClarificationRound, readWorklistProjection, submitScratchpadProjection, } from "../collaboration-state/index.js";
2
2
  import { clarificationInvalidates, taskCompileInvalidates, } from "./invalidations.js";
3
3
  import { normalizeTaskCompileProposal } from "./task-proposal.js";
4
4
  const EMPTY_INITIAL_WORKLIST_CLARIFICATION = {
@@ -86,7 +86,11 @@ export function applyTaskCompileProcedureOutput(input) {
86
86
  if (applied.changed_task_ids.length > 0) {
87
87
  input.onTasksAdded?.();
88
88
  }
89
- const clearedScratchpad = clearScratchpadProjection(input.store.db);
89
+ const submittedScratchpad = submitScratchpadProjection(input.store.db, {
90
+ workflow_ref: input.workflowRef,
91
+ activity_ref: input.activityRef,
92
+ now: input.now,
93
+ });
90
94
  input.events.publish({
91
95
  event_type: "worklist.changed",
92
96
  payload: {
@@ -101,7 +105,7 @@ export function applyTaskCompileProcedureOutput(input) {
101
105
  input.events.publish({
102
106
  event_type: "scratchpad.updated",
103
107
  payload: {
104
- scratchpad: clearedScratchpad,
108
+ scratchpad: submittedScratchpad,
105
109
  activity_ref: input.activityRef,
106
110
  },
107
111
  invalidates: taskCompileInvalidates(),
@@ -51,7 +51,7 @@ export function startControlPlaneTaskCompile(options) {
51
51
  sourceBatch,
52
52
  });
53
53
  }
54
- if (scratchpad.state === "empty") {
54
+ if (scratchpad.state !== "ready") {
55
55
  return { disposition: { kind: "scratchpad_empty" } };
56
56
  }
57
57
  if (shouldInitializeProjectContext) {
@@ -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),
@@ -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;