@crossworks/client-types 0.232.205 → 0.232.210

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crossworks/client-types",
3
- "version": "0.232.205",
3
+ "version": "0.232.210",
4
4
  "type": "module",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -197,7 +197,8 @@ export type AiWorkerKind =
197
197
  | 'search'
198
198
  | 'search_advanced'
199
199
  | 'narrator'
200
- | 'suggester';
200
+ | 'suggester'
201
+ | 'decider';
201
202
 
202
203
  /** An AI worker as returned by `GET /api/ai-workers`. `params` is jsonb (shape
203
204
  * varies by kind) — kept loose here; the form narrows per kind. */
package/src/dto/rows.ts CHANGED
@@ -130,6 +130,21 @@ export type PageRow = {
130
130
  updatedAt: string;
131
131
  };
132
132
 
133
+ /**
134
+ * A row of the /pages LIST response (`GET /api/pages`): a `PageRow` plus where
135
+ * it sits in the hierarchy. The list needs this per row because a search or tag
136
+ * filter returns only the HITS, so the client cannot derive it from the rows it
137
+ * holds: a hit's children and its parent are usually not among them.
138
+ */
139
+ export type PageListRow = PageRow & {
140
+ /** Direct sub-pages, counted across the whole hierarchy (not just this
141
+ * response), so a search hit can offer the way into its sub-pages. */
142
+ childCount: number;
143
+ /** The parent page's title, or null for a top-level page, so a hit can say
144
+ * where it lives. */
145
+ parentTitle: string | null;
146
+ };
147
+
133
148
  export type AppRow = {
134
149
  id: string;
135
150
  title: string;
@@ -675,6 +690,18 @@ export type ContextSnapshot = {
675
690
  };
676
691
  personaNotes: { count: number };
677
692
  corpusMap: { count: number; truncated: boolean };
693
+ /** The decider's `context_pruning` use, when it ran on this turn. In
694
+ * `shadow` the counts say what WOULD have been dropped; in `live` they were.
695
+ * Absent when the use is off or the call failed (nothing changed). */
696
+ pruning?: {
697
+ mode: 'shadow' | 'live';
698
+ threshold: number;
699
+ wouldDrop: { facts: number; contentHits: number; chunkHits: number };
700
+ /** Characters of item text under the threshold (≈ tokens × 4). */
701
+ charsSaved: number;
702
+ ms: number;
703
+ cached: boolean;
704
+ };
678
705
  };
679
706
 
680
707
  export type BackupFrequency = 'daily' | 'weekly';
@@ -2190,5 +2190,26 @@
2190
2190
  },
2191
2191
  "rating": 2,
2192
2192
  "note": "FREE, 128K context, real vision-language model (not just text); rate-limited, best for low-stakes/high-volume extraction"
2193
+ },
2194
+ {
2195
+ "pool": "decider",
2196
+ "position": 0,
2197
+ "name": "TypeSafe: Jev 1.13",
2198
+ "vendor": "TypeSafe",
2199
+ "routes": [
2200
+ {
2201
+ "provider": "openrouter",
2202
+ "model": "typesafe/jev-1.13"
2203
+ }
2204
+ ],
2205
+ "pricing": {
2206
+ "inputPerM": 0.042,
2207
+ "outputPerM": 0,
2208
+ "currency": "USD",
2209
+ "capturedAt": "2026-09-22",
2210
+ "source": "openrouter"
2211
+ },
2212
+ "rating": null,
2213
+ "note": "The only typed-decision model on OpenRouter (alpha decisions endpoint). Hand-added 2026-09-22 with the decider worker; re-curate at /models/pools when a second one ships."
2193
2214
  }
2194
2215
  ]
@@ -132,3 +132,26 @@ describe('the shipped curated template', () => {
132
132
  }
133
133
  });
134
134
  });
135
+
136
+ describe('poolModelIssue: the decider pool', () => {
137
+ it('accepts a decisions-out model and rejects a chat model', () => {
138
+ expect(poolModelIssue('decider', { input: ['text'], output: ['decisions'] })).toBeNull();
139
+ expect(poolModelIssue('decider', { input: ['text'], output: ['text'] })).toMatch(
140
+ /not decisions/,
141
+ );
142
+ });
143
+
144
+ it('keeps a decisions-out model out of every text pool', () => {
145
+ expect(poolModelIssue('summarizer', { input: ['text'], output: ['decisions'] })).toMatch(
146
+ /belongs in the Decider pool/,
147
+ );
148
+ expect(poolModelIssue('agents', { input: ['text'], output: ['decisions'] })).toMatch(
149
+ /belongs in the Decider pool/,
150
+ );
151
+ });
152
+
153
+ it('stays fail-open with no catalog evidence', () => {
154
+ expect(poolModelIssue('decider', null)).toBeNull();
155
+ expect(poolModelIssue('decider', { input: [], output: [] })).toBeNull();
156
+ });
157
+ });
@@ -43,7 +43,7 @@ export type PoolModality = {
43
43
  * a voice route was always "unknown"); since the fetch asks for
44
44
  * `output_modalities=all` they carry positive evidence like everything
45
45
  * else. */
46
- output: 'text' | 'image' | 'speech' | 'transcription';
46
+ output: 'text' | 'image' | 'speech' | 'transcription' | 'decisions';
47
47
  };
48
48
 
49
49
  const TEXT_OUT: PoolModality = { input: [], output: 'text' };
@@ -148,6 +148,14 @@ export const MODEL_POOLS: readonly ModelPoolDef[] = [
148
148
  group: 'workers',
149
149
  modality: TEXT_OUT,
150
150
  },
151
+ {
152
+ id: 'decider',
153
+ label: 'Decider (typed decisions)',
154
+ description:
155
+ 'Typed-decision models (TypeSafe Jev): choice / score / yes-no answers with probabilities, no prose. Behind the experimental per-use switches (passage scoring first). Output modality `decisions`, so no chat model fits here and it fits nowhere else.',
156
+ group: 'workers',
157
+ modality: { input: [], output: 'decisions' },
158
+ },
151
159
  ];
152
160
 
153
161
  export const MODEL_POOL_IDS = new Set(MODEL_POOLS.map((p) => p.id));
@@ -210,6 +218,18 @@ export function poolModelIssue(
210
218
  if (outputs.length === 0 && inputs.length === 0) return null;
211
219
 
212
220
  const makesImages = outputs.includes('image');
221
+ // The decider pool is the one place a `decisions`-out model belongs — and
222
+ // the one place a text-out chat model does not: a chat model answers a
223
+ // decisions request with nothing usable, and Jev answers a chat request
224
+ // with nothing at all.
225
+ if (want.output === 'decisions') {
226
+ return outputs.length > 0 && !outputs.includes('decisions')
227
+ ? `this model outputs ${outputs.join('+')}, not decisions — the ${pool.label} pool needs a typed-decision model such as typesafe/jev-1.13.`
228
+ : null;
229
+ }
230
+ if (want.output === 'text' && outputs.includes('decisions')) {
231
+ return `this model outputs decisions, not text — it belongs in the Decider pool, not ${pool.label}.`;
232
+ }
213
233
  if (want.output === 'text' && makesImages) {
214
234
  return (
215
235
  `this model OUTPUTS images (${outputs.join('+')}) — it is an image generator, ` +