@open-mercato/cezar 0.10.0 → 0.10.1-pr963.1433

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 (114) hide show
  1. package/README.md +19 -15
  2. package/dist/contract/github.d.ts +42 -0
  3. package/dist/contract/index.js +92 -10
  4. package/dist/contract/runs.d.ts +57 -3
  5. package/dist/contract/skills.d.ts +2 -0
  6. package/dist/contract/workspace.d.ts +11 -9
  7. package/dist/core/ask.d.ts +15 -4
  8. package/dist/core/ask.js +80 -10
  9. package/dist/core/ask.js.map +1 -1
  10. package/dist/core/claude-cli-runner.d.ts +9 -0
  11. package/dist/core/claude-cli-runner.js +16 -5
  12. package/dist/core/claude-cli-runner.js.map +1 -1
  13. package/dist/core/claude-model-catalog.d.ts +24 -0
  14. package/dist/core/claude-model-catalog.js +208 -0
  15. package/dist/core/claude-model-catalog.js.map +1 -0
  16. package/dist/core/model-presets.d.ts +3 -3
  17. package/dist/core/model-presets.js +26 -16
  18. package/dist/core/model-presets.js.map +1 -1
  19. package/dist/handoff.d.ts +2 -2
  20. package/dist/handoff.js +2 -2
  21. package/dist/handoff.js.map +1 -1
  22. package/dist/index.js +4 -0
  23. package/dist/index.js.map +1 -1
  24. package/dist/runs/arm-repo-handle.d.ts +17 -0
  25. package/dist/runs/arm-repo-handle.js +24 -0
  26. package/dist/runs/arm-repo-handle.js.map +1 -0
  27. package/dist/runs/store.d.ts +48 -0
  28. package/dist/runs/store.js +278 -9
  29. package/dist/runs/store.js.map +1 -1
  30. package/dist/server/forge/github.d.ts +77 -7
  31. package/dist/server/forge/github.js +408 -41
  32. package/dist/server/forge/github.js.map +1 -1
  33. package/dist/server/forge/types.d.ts +25 -0
  34. package/dist/server/github.d.ts +2 -2
  35. package/dist/server/github.js +1 -1
  36. package/dist/server/github.js.map +1 -1
  37. package/dist/server/project-context.js +10 -0
  38. package/dist/server/project-context.js.map +1 -1
  39. package/dist/server/server.d.ts +503 -11
  40. package/dist/server/server.js +126 -53
  41. package/dist/server/server.js.map +1 -1
  42. package/dist/workflows/run.d.ts +69 -21
  43. package/dist/workflows/run.js +221 -87
  44. package/dist/workflows/run.js.map +1 -1
  45. package/package.json +3 -3
  46. package/scripts/mock-claude.mjs +5 -0
  47. package/web/dist/assets/{alert-dialog-Byp42_IG.js → alert-dialog-CbgdYRdG.js} +1 -1
  48. package/web/dist/assets/arrow-down-CFJjodj1.js +1 -0
  49. package/web/dist/assets/arrow-left-Bj4yR0oD.js +1 -0
  50. package/web/dist/assets/{centered-state-LfAW-igm.js → centered-state-CK5xL1tY.js} +12 -12
  51. package/web/dist/assets/chevron-right-CHh6yN_N.js +1 -0
  52. package/web/dist/assets/{collapsible-CBiiqDJM.js → collapsible-V60GFXUS.js} +1 -1
  53. package/web/dist/assets/{commit-list-6PPve7PE.js → commit-list-BY41QHij.js} +1 -1
  54. package/web/dist/assets/{compare-variants-BDnp6mXJ.js → compare-variants-Bv0vPXKi.js} +1 -1
  55. package/web/dist/assets/{diff-_jK0OWpF.js → diff-CosyTYcF.js} +2 -2
  56. package/web/dist/assets/{diff-stat-CIrTu4Ym.js → diff-stat-qrWVL1In.js} +1 -1
  57. package/web/dist/assets/{diff-view-COA_5IyY.js → diff-view-CSVwuKbE.js} +1 -1
  58. package/web/dist/assets/{dropdown-menu-BofYpFFw.js → dropdown-menu-Cv0mNt85.js} +1 -1
  59. package/web/dist/assets/editable-title-CtYyf86e.js +1 -0
  60. package/web/dist/assets/{ellipsis-vertical-BeMR-l62.js → ellipsis-vertical-C0YOByny.js} +1 -1
  61. package/web/dist/assets/{file-DY996Pee.js → file-DhJLFvn5.js} +1 -1
  62. package/web/dist/assets/{folder-NO3Ec-TY.js → folder-aV5sVscd.js} +1 -1
  63. package/web/dist/assets/{git-toolbar-BqJeUWcD.js → git-toolbar-CQjlSF_A.js} +1 -1
  64. package/web/dist/assets/github-Dtvc7Yyj.js +1 -0
  65. package/web/dist/assets/{image-preview-D9cCDfKQ.js → image-preview-BYFdHjH6.js} +1 -1
  66. package/web/dist/assets/index-BcI5hug3.css +2 -0
  67. package/web/dist/assets/index-LjS6Nfpe.js +7 -0
  68. package/web/dist/assets/{markdown-DDzk11zj.js → markdown-fzA2VAWi.js} +1 -1
  69. package/web/dist/assets/new-task-form-CcAEDoGO.js +1 -0
  70. package/web/dist/assets/{pill-CVM1EmV4.js → pill-ClWA3AGC.js} +1 -1
  71. package/web/dist/assets/{project-router-LRCtP48W.js → project-router-jzfV7lfR.js} +1 -1
  72. package/web/dist/assets/prompt-templates-C2TH2RrH.js +15 -0
  73. package/web/dist/assets/{refresh-cw-D1ys6QY2.js → refresh-cw-BFD5E65I.js} +1 -1
  74. package/web/dist/assets/repo-git-CzyNdjqa.js +1 -0
  75. package/web/dist/assets/{run-diff-BlnqwfSS.js → run-diff-DDSiSJtK.js} +2 -2
  76. package/web/dist/assets/run-header-CoDkY_on.js +1 -0
  77. package/web/dist/assets/{search-x-D6FSpxId.js → search-x-Jn3T9zuY.js} +1 -1
  78. package/web/dist/assets/{skill-empty-hint-iwpCcqgI.js → skill-empty-hint-BA9t56v9.js} +1 -1
  79. package/web/dist/assets/{skills-BiWfHmCR.js → skills-9oR_oHse.js} +1 -1
  80. package/web/dist/assets/skills-ClDzh6n_.js +1 -0
  81. package/web/dist/assets/{sparkles-MrQrgCI9.js → sparkles-DaWOvpR5.js} +1 -1
  82. package/web/dist/assets/{square-terminal-TTH64lO7.js → square-terminal-CAegR74E.js} +1 -1
  83. package/web/dist/assets/{tab-link-FYksz037.js → tab-link-DdoSI2JF.js} +1 -1
  84. package/web/dist/assets/task-changes-B2UxeYGn.js +1 -0
  85. package/web/dist/assets/task-commits-BRA4xrdb.js +1 -0
  86. package/web/dist/assets/{task-files-uSCWyD0-.js → task-files-BNjMuHcm.js} +2 -2
  87. package/web/dist/assets/task-thread-eoHsX04i.js +9 -0
  88. package/web/dist/assets/{textarea-B9Ahj4E4.js → textarea-CPTHF1ed.js} +1 -1
  89. package/web/dist/assets/thread-loading-BcY3rX-U.js +1 -0
  90. package/web/dist/assets/{trash-2-D8S3V3fu.js → trash-2-CwSOizKZ.js} +1 -1
  91. package/web/dist/assets/{triangle-alert-bB7p9n5E.js → triangle-alert-CNM6JjgT.js} +1 -1
  92. package/web/dist/assets/{upload-Bd5oRLPV.js → upload-BxoUx0oB.js} +1 -1
  93. package/web/dist/assets/{use-desktop-BNvbRBeZ.js → use-desktop-CppmcXDr.js} +1 -1
  94. package/web/dist/assets/{use-submit-shortcut-B-xhu4Bf.js → use-submit-shortcut-DqdtK3wv.js} +1 -1
  95. package/web/dist/assets/{utils-BhpbVMwN.js → utils-Ff4ujxHD.js} +1 -1
  96. package/web/dist/assets/{workflows-RmfbM9Vy.js → workflows-BKm8iRRS.js} +2 -2
  97. package/web/dist/assets/{zoomable-image-BCW6ZBV0.js → zoomable-image-BDwzD6UK.js} +1 -1
  98. package/web/dist/index.html +24 -24
  99. package/web/dist/assets/arrow-down-CGDbDYKD.js +0 -1
  100. package/web/dist/assets/arrow-left-ESCjr1Hq.js +0 -1
  101. package/web/dist/assets/chevron-right-Du-P1IUd.js +0 -1
  102. package/web/dist/assets/editable-title-_J0zFDl-.js +0 -1
  103. package/web/dist/assets/github-C96HPV9E.js +0 -1
  104. package/web/dist/assets/index-Bx-grqa9.css +0 -2
  105. package/web/dist/assets/index-DoVvGDfW.js +0 -7
  106. package/web/dist/assets/new-task-form-BNLLgC6P.js +0 -1
  107. package/web/dist/assets/prompt-templates-DFOvYgtW.js +0 -15
  108. package/web/dist/assets/repo-git-CzUnsgWY.js +0 -1
  109. package/web/dist/assets/run-header-Bn7cN0Z1.js +0 -1
  110. package/web/dist/assets/skills-CezbOaol.js +0 -1
  111. package/web/dist/assets/task-changes-ClWlnzBL.js +0 -1
  112. package/web/dist/assets/task-commits-Crbrl86b.js +0 -1
  113. package/web/dist/assets/task-thread-DPmamBA1.js +0 -9
  114. package/web/dist/assets/thread-loading-BH_ypQoy.js +0 -1
package/README.md CHANGED
@@ -259,9 +259,10 @@ Three words, no jargon — **task**, **skill**, **chain**:
259
259
 
260
260
  - 📋 **Tasks** are the unit of work. Every task is a **run**: `queued → running →
261
261
  review / done / failed / cancelled`, with a live event log, per-step token and
262
- cost usage, cancel/delete, and — for anything with a diff — a review gate. Paste
263
- screenshots into the task, or send follow-up messages into the live session
264
- while it works.
262
+ cost usage, cancel/delete, and — for anything with a diff — a review gate. Attach
263
+ screenshots, PDFs, `.txt` or `.md` files to the task (paperclip, ⌘V or drag-drop;
264
+ the agent gets each one as a real file on disk), or send follow-up messages into
265
+ the live session while it works.
265
266
  - 📖 **Skills** are Markdown playbooks. Drop them in `.ai/skills/` or
266
267
  `.ai/cezar/skills/`, or pull them from a shared **team skills repo** (a bare
267
268
  git clone cached globally in `~/.cache/cez/`). A workflow step references one by
@@ -314,7 +315,7 @@ Eight views, one browser window, all live over Server-Sent Events (seven until y
314
315
 
315
316
  | View | What's in it |
316
317
  |---|---|
317
- | **Tasks** | Every task with its status, live event stream (agent text · tool calls · tool results · pasted/generated screenshots), tokens and cost. Continue, cancel, open in terminal (`claude --resume`), review the diff, or push a draft PR. |
318
+ | **Tasks** | Every task with its status, live event stream (agent text · tool calls · tool results · pasted/generated screenshots and file attachments), tokens and cost. Continue, cancel, open in terminal (`claude --resume`), review the diff, or push a draft PR. |
318
319
  | **All tasks** | Every *registered project's* tasks in one table, filtered and grouped by tag, project, status or workflow — see [Grouping connected repositories](#grouping-connected-repositories-tags-and-the-all-tasks-page). Appears once a second project is registered. |
319
320
  | **Inbox** | **Opt-in** (`CEZ_FOLLOWUPS=1`; hidden by default). Follow-ups an agent left behind (`todos.json`) — one click turns a suggestion into the next task, pre-wired to its suggested skill. Off, agents are never asked to leave follow-ups; each task's own **Notes** handoff journal is unaffected. |
320
321
  | **Git** | Branch, working-tree status, diff vs HEAD, recent commits (click one for its inline patch + GitHub link), and the configurable base branch that worktrees fork from and PRs target. |
@@ -595,6 +596,20 @@ cezar is not married to one vendor. Every agent step runs through a single
595
596
  On startup cezar probes which CLIs are installed and the cockpit only offers
596
597
  the backends it found — install any one of the four and you're operational.
597
598
 
599
+ **Models come from your own machine.** The model picker does not ship a list of
600
+ vendor releases that goes stale between cezar versions. For Claude, Codex and
601
+ OpenCode cezar asks the CLI on your host what *it* currently offers (Claude
602
+ Code's `list_models` control request; the Codex app-server's `model/list`;
603
+ `opencode models`) and shows exactly that, in that order — so a model your
604
+ account gained yesterday is selectable today with no cezar release, and one your
605
+ provider retired stops being offered. Discovery is read-only, costs no tokens,
606
+ and is cached briefly in memory. If the CLI is missing, logged out, too old or
607
+ slow, the picker quietly falls back to that runner's built-in entries (`auto`
608
+ plus Claude's tier aliases) and says so in a status row; `pi`, which has no
609
+ host-local catalog yet, always shows its built-in entries. `auto` — send no
610
+ model at all and let the CLI decide — is always available, and a model you
611
+ pinned by hand stays selectable even when it is no longer advertised.
612
+
598
613
  **Pick a backend at three levels** (most specific wins):
599
614
 
600
615
  1. **Config default** — `"defaultRunner": "codex"` in `.ai/cezar/config.json`.
@@ -624,17 +639,6 @@ steps:
624
639
  Parallel variants (×2/×3) of one task share that task's backend — mixing
625
640
  happens per task and per step, not inside a variant group.
626
641
 
627
- **Models come from your own machine.** For Codex and OpenCode, the model picker
628
- is not a list cezar ships — it asks the installed CLI what it can actually run
629
- (`codex app-server`'s `model/list`, and `opencode models`), caches the answer in
630
- memory for five minutes, and shows it. A model your provider rolled out
631
- yesterday is selectable without a cezar release, and one it retired stops being
632
- offered. Claude Code has no equivalent local catalog, so it keeps a short list
633
- of tier aliases and pinned versions. `auto` (let the agent decide) is always
634
- available, including when the CLI is missing, logged out, or slow — discovery
635
- never blocks the cockpit, and a model you pinned yourself stays selectable even
636
- if it is absent from the discovered list.
637
-
638
642
  The seam is deliberately small: a backend is one class implementing the
639
643
  `AgentRunner` interface (`packages/cezar/src/core/agent-runner.ts`) that turns a prompt into
640
644
  a stream of normalized events. Other CLIs — pi, aider, whatever ships next —
@@ -101,6 +101,47 @@ export declare const githubChecksDataSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
101
101
  reason: z.ZodString;
102
102
  }, z.core.$strip>], "available">;
103
103
  export type GithubChecksData = z.infer<typeof githubChecksDataSchema>;
104
+ /**
105
+ * `GET /api/v1/github/search?kind=…&q=…&limit=…` (#730) — issues/PRs in ANY state.
106
+ *
107
+ * `GET /api/v1/github` lists the OPEN set only, and the tab's search is an in-memory filter over
108
+ * exactly that payload, so a closed or merged item is not "outside the fetched window" — it was
109
+ * never fetched, and nothing the user types can reach it. This is the cross-state lookup the tab
110
+ * falls back to.
111
+ *
112
+ * Not a discriminated union, like `githubDataSchema` and unlike `githubChecksDataSchema`: the
113
+ * driver always answers the full record and merely flips `available`, so an unavailable payload
114
+ * still carries `items: []`.
115
+ */
116
+ export declare const githubSearchDataSchema: z.ZodObject<{
117
+ available: z.ZodBoolean;
118
+ reason: z.ZodOptional<z.ZodString>;
119
+ items: z.ZodArray<z.ZodObject<{
120
+ kind: z.ZodEnum<{
121
+ issue: "issue";
122
+ pr: "pr";
123
+ }>;
124
+ number: z.ZodNumber;
125
+ title: z.ZodString;
126
+ author: z.ZodString;
127
+ createdAt: z.ZodString;
128
+ labels: z.ZodArray<z.ZodString>;
129
+ body: z.ZodString;
130
+ url: z.ZodString;
131
+ comments: z.ZodNumber;
132
+ isDraft: z.ZodOptional<z.ZodBoolean>;
133
+ additions: z.ZodOptional<z.ZodNumber>;
134
+ deletions: z.ZodOptional<z.ZodNumber>;
135
+ checks: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
136
+ failing: "failing";
137
+ passing: "passing";
138
+ pending: "pending";
139
+ }>>>;
140
+ }, z.core.$strip>>;
141
+ truncated: z.ZodOptional<z.ZodBoolean>;
142
+ labelColors: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
143
+ }, z.core.$strip>;
144
+ export type GithubSearchData = z.infer<typeof githubSearchDataSchema>;
104
145
  /**
105
146
  * Where a referenced PR or issue STANDS — the vocabulary a task's tracker chip paints.
106
147
  *
@@ -182,6 +223,7 @@ export declare const githubRefStatusDataSchema: z.ZodDiscriminatedUnion<[z.ZodOb
182
223
  ready: "ready";
183
224
  "review-required": "review-required";
184
225
  }>>;
226
+ conflicts: z.ZodOptional<z.ZodArray<z.ZodNumber>>;
185
227
  recheckAfterMs: z.ZodNullable<z.ZodNumber>;
186
228
  }, z.core.$strip>, z.ZodObject<{
187
229
  available: z.ZodLiteral<false>;
@@ -162,6 +162,19 @@ var githubChecksDataSchema = z3.discriminatedUnion("available", [
162
162
  reason: z3.string()
163
163
  })
164
164
  ]);
165
+ var githubSearchDataSchema = z3.object({
166
+ available: z3.boolean(),
167
+ /** Why it could not search (`gh` missing, no remote, rate-limited…). A hint, never an error. */
168
+ reason: z3.string().optional(),
169
+ /** The same rows the list ships. `checks` is always `null` here — hydrated lazily through
170
+ * `/github/checks`, exactly as list rows have been since #664. */
171
+ items: z3.array(githubItemSchema),
172
+ /** The hit list filled the server's cap; there may be more matches on the forge. */
173
+ truncated: z3.boolean().optional(),
174
+ /** Label name → 6-hex color for the labels these hits carry. A closed item often wears labels
175
+ * no open one does, which the repo-wide map from the list call would not cover. Additive. */
176
+ labelColors: z3.record(z3.string(), z3.string()).optional()
177
+ });
165
178
  var referenceStatusSchema = z3.enum([
166
179
  "draft",
167
180
  "review-required",
@@ -182,6 +195,26 @@ var githubRefStatusDataSchema = z3.discriminatedUnion("available", [
182
195
  available: z3.literal(true),
183
196
  prs: z3.record(z3.number(), referenceStatusSchema),
184
197
  issues: z3.record(z3.number(), referenceStatusSchema),
198
+ /**
199
+ * The pull request numbers that do NOT merge cleanly into their base right now.
200
+ *
201
+ * A second list rather than a twelfth status, because a conflict is a different AXIS from the
202
+ * one `referenceStatusSchema` ranks. That enum answers *whose move is it*, and it answers with
203
+ * one word; mergeability is an independent fact that can be true alongside any of them — a PR
204
+ * can be green, approved and conflicting at the same time, and folding the two together would
205
+ * force the cockpit to pick which of two true things to say. Kept apart, it paints a second
206
+ * chip next to the first and says both.
207
+ *
208
+ * It rides the same GraphQL node the statuses come from (`mergeable`), so unlike
209
+ * `githubPrMergeStateResponseSchema` — the full per-PR probe, blockers and all — it costs no
210
+ * extra request for the batch.
211
+ *
212
+ * Optional on the wire, and its absence means *nothing is known about mergeability*, never
213
+ * *no conflicts*: a server from before this field simply omits it. Only OPEN pull requests are
214
+ * ever named — GitHub reports `UNKNOWN` for merged and closed ones, and `UNKNOWN` (which is
215
+ * also what it answers while it is still computing) is never listed.
216
+ */
217
+ conflicts: z3.array(z3.number()).optional(),
185
218
  recheckAfterMs: recheckAfterMsSchema
186
219
  }),
187
220
  z3.object({
@@ -444,7 +477,8 @@ var diffStatSchema = z5.object({
444
477
  var queuedMessageSchema = z5.object({
445
478
  id: z5.string(),
446
479
  text: z5.string(),
447
- /** `/api/v1/runs/:id/images/…` URLs — attachments are persisted, never inlined. */
480
+ /** `/api/v1/runs/:id/images/…` URLs — attachments are persisted, never inlined. Images and
481
+ * files share this one list; read `isImageAttachmentName` on the file name to tell them apart. */
448
482
  images: z5.array(z5.string()).optional(),
449
483
  createdAt: z5.string()
450
484
  });
@@ -467,7 +501,8 @@ var runRecordSchema = z5.object({
467
501
  /** Prompt messages stacked onto the run while it waited for a free agent slot (#472). Folded
468
502
  * into the prompt at dequeue — never delivered as their own turns. Absent on pre-#472 runs. */
469
503
  queuedMessages: z5.array(queuedMessageSchema).optional(),
470
- /** URLs of images attached to the initial task prompt (#image-display). */
504
+ /** URLs of the attachments on the initial task prompt (#image-display) — images and, since
505
+ * #950, files. One list, one numbering space; branch on `isImageAttachmentName`. */
471
506
  taskImages: z5.array(z5.string()).optional(),
472
507
  model: z5.string().optional(),
473
508
  /** Normalized provider/model identity used for attribution and reproducible replay. */
@@ -560,6 +595,17 @@ var runRecordSchema = z5.object({
560
595
  peakProcCount: z5.number().optional(),
561
596
  archived: z5.boolean(),
562
597
  archivedAt: z5.string().optional(),
598
+ /** Pinned to the top of this project's task list (#935) — the `Pinned` group above
599
+ * `Needs you`. Optional, unlike `archived`: absent IS "not pinned", which is what every
600
+ * record written before this carries, and the store never writes `false`. Archiving
601
+ * clears it, because archiving is how a user resigns from a task. */
602
+ pinned: z5.boolean().optional(),
603
+ /** When the pin was set. Deliberately WRITE-ONLY today, like the `archivedAt` above it:
604
+ * ordering inside `Pinned` uses the ordinary status/recency rules, so nothing reads this
605
+ * yet. It is here because a field on a protected surface is far cheaper to add now than to
606
+ * add later — "pinned oldest first" and "unpin what you pinned a month ago" both need it,
607
+ * and neither can be reconstructed after the fact. */
608
+ pinnedAt: z5.string().optional(),
563
609
  /** Read receipt (#unread-done-items): ISO time the cockpit last opened this run's
564
610
  * thread. A finished (`done`/`failed`) run reads as *unread* until seen since it
565
611
  * finished — see `isUnread()` in the cockpit's `lib/read-state.ts`. Absent on old
@@ -742,10 +788,33 @@ var groupResponseSchema = z5.object({
742
788
  var pickVariantResponseSchema = z5.object({
743
789
  winner: runRecordSchema.optional()
744
790
  });
745
- var imageInputSchema = z5.object({
746
- mediaType: z5.string().regex(/^image\//),
791
+ var FILE_ATTACHMENT_MEDIA_TYPES = [
792
+ "text/plain",
793
+ "text/markdown",
794
+ "text/x-markdown",
795
+ "application/pdf"
796
+ ];
797
+ function isImageMediaType(mediaType) {
798
+ return /^image\//.test(mediaType);
799
+ }
800
+ function isAttachmentMediaType(mediaType) {
801
+ return isImageMediaType(mediaType) || FILE_ATTACHMENT_MEDIA_TYPES.includes(mediaType);
802
+ }
803
+ function attachmentExtension(mediaType) {
804
+ return /png/.test(mediaType) ? "png" : /jpe?g/.test(mediaType) ? "jpg" : /webp/.test(mediaType) ? "webp" : /gif/.test(mediaType) ? "gif" : mediaType === "application/pdf" ? "pdf" : mediaType === "text/plain" ? "txt" : mediaType === "text/markdown" || mediaType === "text/x-markdown" ? "md" : "img";
805
+ }
806
+ var IMAGE_ATTACHMENT_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "webp", "gif", "img"]);
807
+ function isImageAttachmentName(name) {
808
+ const ext = name.split(".").pop()?.toLowerCase();
809
+ return ext !== void 0 && IMAGE_ATTACHMENT_EXTENSIONS.has(ext);
810
+ }
811
+ var attachmentInputSchema = z5.object({
812
+ mediaType: z5.string().refine(isAttachmentMediaType, {
813
+ message: "unsupported attachment type \u2014 images, plain text, markdown and PDF only"
814
+ }),
747
815
  data: z5.string().min(1).max(7e6)
748
816
  });
817
+ var imageInputSchema = attachmentInputSchema;
749
818
  var createRunInputBaseSchema = z5.object({
750
819
  workflow: z5.string().min(1).optional(),
751
820
  /** An inline chain (spec 008 — an approved plan runs as an ad-hoc workflow, never written to
@@ -770,8 +839,10 @@ var createRunInputBaseSchema = z5.object({
770
839
  /** Per-run system-prompt override (R2 2.3) — programmatic callers only. Wins over the
771
840
  * config.json default; whitespace-only degrades to absent. */
772
841
  systemPrompt: z5.string().trim().max(2e4, "must be at most 20000 characters").optional().transform((s) => s ? s : void 0),
773
- /** Screenshots pasted into the new-task form; delivered with the first agent step. */
774
- images: z5.array(imageInputSchema).max(4).optional(),
842
+ /** Attachments pasted into the new-task form screenshots, and since #950 PDF/TXT/MD files
843
+ * too; delivered with the first agent step. The key keeps its name: the wire contract widened
844
+ * in place rather than growing a second list. */
845
+ images: z5.array(attachmentInputSchema).max(4).optional(),
775
846
  /** The inbox entry this task came from (#374). Best-effort bookkeeping: an unknown or
776
847
  * already-started id never fails the run. For ×2/×3 the FIRST variant is recorded. */
777
848
  todoId: z5.string().min(1).max(200, "must be at most 200 characters").optional()
@@ -782,9 +853,9 @@ var createRunInputSchema = createRunInputBaseSchema.refine(
782
853
  );
783
854
  var messageInputSchema = z5.object({
784
855
  text: z5.string().max(1e5).default(""),
785
- images: z5.array(imageInputSchema).max(4).default([])
856
+ images: z5.array(attachmentInputSchema).max(4).default([])
786
857
  }).refine((m) => m.text.trim().length > 0 || m.images.length > 0, {
787
- message: "message needs text or at least one image"
858
+ message: "message needs text or at least one attachment"
788
859
  });
789
860
  var patchRunInputSchema = z5.object({
790
861
  title: z5.string().trim().min(1).max(300).optional(),
@@ -1082,7 +1153,11 @@ var taskTableUiStateSchema = z8.looseObject({
1082
1153
  expandedColumns: z8.record(z8.string(), z8.boolean()).optional()
1083
1154
  });
1084
1155
  var uiStateSchema = z8.looseObject({
1085
- lastTask: z8.object({ source: z8.enum(["workflow", "skill"]), ref: z8.string() }).optional(),
1156
+ /** What the last started run used. `null` is a VALUE, not an absence: it records a run that
1157
+ * chose neither a skill nor a workflow (the plain built-in `quick-task`), which the composer
1158
+ * can now express since the source picker grew an empty state. Absent still means "no
1159
+ * run has been recorded here" — a cockpit reading either one selects nothing. */
1160
+ lastTask: z8.object({ source: z8.enum(["workflow", "skill"]), ref: z8.string() }).nullable().optional(),
1086
1161
  /** Most-recently-run sources, newest first (deduped, capped). Feeds the composer picker's
1087
1162
  * recency sort. */
1088
1163
  recentSources: z8.array(z8.object({ source: z8.enum(["workflow", "skill"]), ref: z8.string() })).optional(),
@@ -1284,7 +1359,7 @@ var providerConnectResponseSchema = z8.discriminatedUnion("opened", [
1284
1359
  z8.object({ opened: z8.literal(true), command: z8.string() }),
1285
1360
  z8.object({ opened: z8.literal(false), connected: z8.literal(true), command: z8.string() })
1286
1361
  ]);
1287
- var modelDiscoveryRunnerSchema = z8.enum(["codex", "opencode"]);
1362
+ var modelDiscoveryRunnerSchema = z8.enum(["claude", "codex", "opencode"]);
1288
1363
  var MODEL_DISCOVERY_RUNNERS = modelDiscoveryRunnerSchema.options;
1289
1364
  function runnerDiscoversModels(runner) {
1290
1365
  return MODEL_DISCOVERY_RUNNERS.includes(runner);
@@ -1688,6 +1763,7 @@ var automationCheckInputSchema = z12.object({
1688
1763
  });
1689
1764
  export {
1690
1765
  DEFAULT_AGENT_ACCOUNT_ID,
1766
+ FILE_ATTACHMENT_MEDIA_TYPES,
1691
1767
  MODEL_DISCOVERY_RUNNERS,
1692
1768
  PROJECT_TAGS_MAX,
1693
1769
  PROJECT_TAG_MAX_LENGTH,
@@ -1711,6 +1787,8 @@ export {
1711
1787
  agentProfilesResponseSchema,
1712
1788
  apiRunSchema,
1713
1789
  archiveFinishedResponseSchema,
1790
+ attachmentExtension,
1791
+ attachmentInputSchema,
1714
1792
  automationCheckInputSchema,
1715
1793
  automationCheckQueuedResponseSchema,
1716
1794
  automationCheckSchema,
@@ -1768,6 +1846,7 @@ export {
1768
1846
  githubPrMergeStateResponseSchema,
1769
1847
  githubPrMergeStateSchema,
1770
1848
  githubRefStatusDataSchema,
1849
+ githubSearchDataSchema,
1771
1850
  githubTimelineEventKindSchema,
1772
1851
  githubTimelineEventSchema,
1773
1852
  groupResponseSchema,
@@ -1775,6 +1854,9 @@ export {
1775
1854
  healthResponseSchema,
1776
1855
  imageInputSchema,
1777
1856
  importableSkillSchema,
1857
+ isAttachmentMediaType,
1858
+ isImageAttachmentName,
1859
+ isImageMediaType,
1778
1860
  launchKeyResponseSchema,
1779
1861
  logEntrySchema,
1780
1862
  markAllReadResponseSchema,
@@ -207,6 +207,8 @@ export declare const runRecordSchema: z.ZodObject<{
207
207
  peakProcCount: z.ZodOptional<z.ZodNumber>;
208
208
  archived: z.ZodBoolean;
209
209
  archivedAt: z.ZodOptional<z.ZodString>;
210
+ pinned: z.ZodOptional<z.ZodBoolean>;
211
+ pinnedAt: z.ZodOptional<z.ZodString>;
210
212
  seenAt: z.ZodOptional<z.ZodString>;
211
213
  currentStepId: z.ZodOptional<z.ZodString>;
212
214
  error: z.ZodOptional<z.ZodString>;
@@ -374,6 +376,8 @@ export declare const apiRunSchema: z.ZodObject<{
374
376
  peakProcCount: z.ZodOptional<z.ZodNumber>;
375
377
  archived: z.ZodBoolean;
376
378
  archivedAt: z.ZodOptional<z.ZodString>;
379
+ pinned: z.ZodOptional<z.ZodBoolean>;
380
+ pinnedAt: z.ZodOptional<z.ZodString>;
377
381
  seenAt: z.ZodOptional<z.ZodString>;
378
382
  currentStepId: z.ZodOptional<z.ZodString>;
379
383
  error: z.ZodOptional<z.ZodString>;
@@ -739,6 +743,8 @@ export declare const createRunResponseSchema: z.ZodUnion<readonly [z.ZodObject<{
739
743
  peakProcCount: z.ZodOptional<z.ZodNumber>;
740
744
  archived: z.ZodBoolean;
741
745
  archivedAt: z.ZodOptional<z.ZodString>;
746
+ pinned: z.ZodOptional<z.ZodBoolean>;
747
+ pinnedAt: z.ZodOptional<z.ZodString>;
742
748
  seenAt: z.ZodOptional<z.ZodString>;
743
749
  currentStepId: z.ZodOptional<z.ZodString>;
744
750
  error: z.ZodOptional<z.ZodString>;
@@ -900,6 +906,8 @@ export declare const createRunResponseSchema: z.ZodUnion<readonly [z.ZodObject<{
900
906
  peakProcCount: z.ZodOptional<z.ZodNumber>;
901
907
  archived: z.ZodBoolean;
902
908
  archivedAt: z.ZodOptional<z.ZodString>;
909
+ pinned: z.ZodOptional<z.ZodBoolean>;
910
+ pinnedAt: z.ZodOptional<z.ZodString>;
903
911
  seenAt: z.ZodOptional<z.ZodString>;
904
912
  currentStepId: z.ZodOptional<z.ZodString>;
905
913
  error: z.ZodOptional<z.ZodString>;
@@ -1268,6 +1276,8 @@ export declare const pickVariantResponseSchema: z.ZodObject<{
1268
1276
  peakProcCount: z.ZodOptional<z.ZodNumber>;
1269
1277
  archived: z.ZodBoolean;
1270
1278
  archivedAt: z.ZodOptional<z.ZodString>;
1279
+ pinned: z.ZodOptional<z.ZodBoolean>;
1280
+ pinnedAt: z.ZodOptional<z.ZodString>;
1271
1281
  seenAt: z.ZodOptional<z.ZodString>;
1272
1282
  currentStepId: z.ZodOptional<z.ZodString>;
1273
1283
  error: z.ZodOptional<z.ZodString>;
@@ -1342,12 +1352,56 @@ export declare const pickVariantResponseSchema: z.ZodObject<{
1342
1352
  }, z.core.$strip>>;
1343
1353
  }, z.core.$strip>;
1344
1354
  export type PickVariantResponse = z.infer<typeof pickVariantResponseSchema>;
1345
- /** An inline image, base64 — ≤4 per request, ~5 MB each once decoded. */
1355
+ /**
1356
+ * Non-image attachment types the composer may send (#950). Deliberately an allowlist and
1357
+ * deliberately short: cezar serves these files back from the cockpit's own origin, so every entry
1358
+ * here is one more thing that must be safe to hand a browser. `image/*` stays a regex — narrowing
1359
+ * what the route has always accepted would be the breaking half of this change.
1360
+ *
1361
+ * `text/x-markdown` is the spelling some browsers still report for a `.md`.
1362
+ */
1363
+ export declare const FILE_ATTACHMENT_MEDIA_TYPES: readonly ['text/plain', 'text/markdown', 'text/x-markdown', 'application/pdf'];
1364
+ /** Does this media type travel as an inline image block the model can look at? */
1365
+ export declare function isImageMediaType(mediaType: string): boolean;
1366
+ /** Is this something the composer may attach at all — an image, or one of the file types above? */
1367
+ export declare function isAttachmentMediaType(mediaType: string): boolean;
1368
+ /**
1369
+ * The on-disk extension for an attachment, derived from its media type ALONE — the user's own
1370
+ * filename never reaches the wire, and therefore never reaches a path. `img` is the pre-existing
1371
+ * catch-all for an image type cezar does not name (an SVG, a BMP), kept so those files land where
1372
+ * they always did.
1373
+ */
1374
+ export declare function attachmentExtension(mediaType: string): string;
1375
+ /**
1376
+ * Does a persisted attachment name (`pasted-3.pdf`, `screenshot-1.png`) refer to an image?
1377
+ *
1378
+ * The one predicate every reader branches on — the engine deciding whether to re-encode a file as
1379
+ * an image block, and the cockpit deciding between a thumbnail and a named chip. Branch on the
1380
+ * NAME, never on the list an entry came from: images and files share one list and one numbering
1381
+ * space on disk, and that is what keeps the orphan sweep, the restart re-read and the per-stack
1382
+ * cap on a single code path.
1383
+ */
1384
+ export declare function isImageAttachmentName(name: string): boolean;
1385
+ /**
1386
+ * One inline attachment, base64 — ≤4 per request, ~5 MB each once decoded.
1387
+ *
1388
+ * An image rides along as a block the model can view; a file (#950) is written to the run's
1389
+ * attachment folder and reaches the agent as a PATH only, which is the form its file tools want,
1390
+ * the only form that survives the codex/opencode backends (they drop image blocks before the
1391
+ * model sees them), and the one that keeps a multi-megabyte PDF out of the prompt bounds.
1392
+ */
1393
+ export declare const attachmentInputSchema: z.ZodObject<{
1394
+ mediaType: z.ZodString;
1395
+ data: z.ZodString;
1396
+ }, z.core.$strip>;
1397
+ export type AttachmentInput = z.input<typeof attachmentInputSchema>;
1398
+ /** @deprecated Attachments are no longer images only — use `attachmentInputSchema`. Kept because
1399
+ * the `images` key it validates is the wire contract and did not change name. */
1346
1400
  export declare const imageInputSchema: z.ZodObject<{
1347
1401
  mediaType: z.ZodString;
1348
1402
  data: z.ZodString;
1349
1403
  }, z.core.$strip>;
1350
- export type ImageInput = z.input<typeof imageInputSchema>;
1404
+ export type ImageInput = AttachmentInput;
1351
1405
  /**
1352
1406
  * The KEYS of `POST /runs`' body, before the XOR refinement that `createRunInputSchema` adds.
1353
1407
  *
@@ -1448,7 +1502,7 @@ export declare const createRunInputSchema: z.ZodObject<{
1448
1502
  }, z.core.$strip>;
1449
1503
  export type CreateRunInput = z.input<typeof createRunInputSchema>;
1450
1504
  /**
1451
- * `POST /runs/:id/messages` — text and/or pasted screenshots for a live session. Both keys have
1505
+ * `POST /runs/:id/messages` — text and/or pasted attachments for a live session. Both keys have
1452
1506
  * server-side defaults, so an omitted `text` is `''` and an omitted `images` is `[]`; the refine
1453
1507
  * is what rejects a message that is empty in both.
1454
1508
  */
@@ -156,6 +156,8 @@ export declare const startTodoResponseSchema: z.ZodObject<{
156
156
  peakProcCount: z.ZodOptional<z.ZodNumber>;
157
157
  archived: z.ZodBoolean;
158
158
  archivedAt: z.ZodOptional<z.ZodString>;
159
+ pinned: z.ZodOptional<z.ZodBoolean>;
160
+ pinnedAt: z.ZodOptional<z.ZodString>;
159
161
  seenAt: z.ZodOptional<z.ZodString>;
160
162
  currentStepId: z.ZodOptional<z.ZodString>;
161
163
  error: z.ZodOptional<z.ZodString>;
@@ -110,13 +110,13 @@ export type SetWorkspaceConfigInput = z.infer<typeof setWorkspaceConfigInputSche
110
110
  * listed it, which made it wider than the route.
111
111
  */
112
112
  export declare const uiStateSchema: z.ZodObject<{
113
- lastTask: z.ZodOptional<z.ZodObject<{
113
+ lastTask: z.ZodOptional<z.ZodNullable<z.ZodObject<{
114
114
  source: z.ZodEnum<{
115
115
  skill: "skill";
116
116
  workflow: "workflow";
117
117
  }>;
118
118
  ref: z.ZodString;
119
- }, z.core.$strip>>;
119
+ }, z.core.$strip>>>;
120
120
  recentSources: z.ZodOptional<z.ZodArray<z.ZodObject<{
121
121
  source: z.ZodEnum<{
122
122
  skill: "skill";
@@ -498,12 +498,14 @@ export declare const providerConnectResponseSchema: z.ZodDiscriminatedUnion<[z.Z
498
498
  }, z.core.$strip>], "opened">;
499
499
  export type ProviderConnectResponse = z.infer<typeof providerConnectResponseSchema>;
500
500
  /**
501
- * The runners whose model list is discovered from the host rather than hard-coded: Codex
502
- * through its app-server protocol, OpenCode through its own `models` listing (#794). Claude has
503
- * no equivalent local source, so its picker keeps static presets and `GET /api/v1/models`
504
- * rejects it. One definition, used by the route's query validator and by the cockpit's picker.
501
+ * The runners whose model list is discovered from the host rather than hard-coded: Codex through
502
+ * its app-server protocol, OpenCode through its own `models` listing (#794), Claude through the
503
+ * CLI's `list_models` control request (#784). A runner absent here has no discovery path and
504
+ * 400s, so the client compiles against exactly what the route accepts. One definition, used by
505
+ * the route's query validator and by the cockpit's picker.
505
506
  */
506
507
  export declare const modelDiscoveryRunnerSchema: z.ZodEnum<{
508
+ claude: "claude";
507
509
  codex: "codex";
508
510
  opencode: "opencode";
509
511
  }>;
@@ -517,9 +519,9 @@ export declare const runnerModelOptionSchema: z.ZodObject<{
517
519
  description: z.ZodString;
518
520
  }, z.core.$strip>;
519
521
  export type RunnerModelOption = z.infer<typeof runnerModelOptionSchema>;
520
- /** `GET /api/v1/models?runner=codex|opencode` — the models discovered from that runner's own
521
- * host installation, plus how fresh the answer is. Never an error: an unavailable CLI degrades
522
- * to `source: 'unavailable'` with a `reason`. Claude has no host-local catalog and is rejected. */
522
+ /** `GET /api/v1/models?runner=claude|codex|opencode` — the models discovered from that runner's
523
+ * own host installation, plus how fresh the answer is. Never an error: an unavailable CLI
524
+ * degrades to `source: 'unavailable'` with a `reason`. */
523
525
  export declare const runnerModelCatalogResponseSchema: z.ZodObject<{
524
526
  runner: z.ZodEnum<{
525
527
  claude: "claude";
@@ -49,7 +49,9 @@ export interface AskParseIssue {
49
49
  message: string;
50
50
  }
51
51
  /** The diagnostic parse result used at turn-end. The compatibility wrapper
52
- * below deliberately keeps returning `AskRequest | null`. */
52
+ * below deliberately keeps returning `AskRequest | null`. `repaired` marks a
53
+ * card that only exists because the payload's missing closers were appended
54
+ * (#936) — callers surface it as a note rather than claiming a clean parse. */
53
55
  export type AskMarkerParseResult = {
54
56
  kind: 'none';
55
57
  } | {
@@ -62,6 +64,7 @@ export type AskMarkerParseResult = {
62
64
  kind: 'valid';
63
65
  request: AskRequest;
64
66
  normalized: boolean;
67
+ repaired?: boolean;
65
68
  };
66
69
  /**
67
70
  * Parse a value into a validated `AskRequest`, or `null` when it does not match
@@ -79,9 +82,12 @@ export declare function parseAskRequest(value: unknown): AskRequest | null;
79
82
  */
80
83
  export declare const ASK_MARKER_RE: RegExp;
81
84
  /**
82
- * Parse a trailing marker with an actionable result for diagnostics. A
83
- * parseable near-valid request gets one bounded normalization pass; structural
84
- * violations remain rejected so the raw fallback stays readable.
85
+ * Parse a trailing marker with an actionable result for diagnostics. Two
86
+ * bounded forgiveness layers sit under the schema, and neither loosens it: a
87
+ * payload that fails `JSON.parse` only for missing closers is repaired
88
+ * (`closeUnbalancedJson`, #936), then a parseable near-valid request gets one
89
+ * normalization pass (`normalizeAskRequest`). Structural violations remain
90
+ * rejected so the raw fallback stays readable.
85
91
  */
86
92
  export declare function parseAskMarkerResult(turnText: string): AskMarkerParseResult;
87
93
  /**
@@ -101,5 +107,10 @@ export declare function parseAskMarker(turnText: string): AskRequest | null;
101
107
  * the marker across events — then it stays visible; detection on the assembled
102
108
  * turn text is unaffected (same best-effort caveat as the `CEZ:DONE` /
103
109
  * `CEZ:MONITORING` strippers).
110
+ *
111
+ * The strip runs from the `CEZ:ASK` keyword to end-of-text rather than to a
112
+ * trailing `}`: a repaired payload (#936) may end on `]`, or on the last
113
+ * character before the closers this module appended, and half a marker left
114
+ * under a rendered card is protocol noise the card already replaced.
104
115
  */
105
116
  export declare function stripAskMarker(text: string): string;