@crouton-kit/crouter 0.3.37 → 0.3.39
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/dist/build-root.js +1 -0
- package/dist/{builtin-personas/runtime-base.md → builtin-memory/00-runtime-base.md} +7 -0
- package/dist/{builtin-personas/spine/has-manager.md → builtin-memory/01-spine/00-has-manager.md} +8 -0
- package/dist/builtin-memory/01-spine/01-no-manager.md +10 -0
- package/dist/{builtin-personas/lifecycle/terminal.md → builtin-memory/02-lifecycle/00-terminal.md} +8 -0
- package/dist/{builtin-personas/lifecycle/resident.md → builtin-memory/02-lifecycle/01-resident.md} +8 -0
- package/dist/{builtin-personas/waiting.md → builtin-memory/03-waiting.md} +7 -0
- package/dist/{builtin-personas/orchestration-kernel.md → builtin-memory/04-orchestration-kernel.md} +8 -0
- package/dist/{builtin-personas/advisor/PERSONA.md → builtin-memory/05-kinds/advisor/00-base.md} +5 -2
- package/dist/{builtin-personas/design/PERSONA.md → builtin-memory/05-kinds/design/00-base.md} +5 -2
- package/dist/{builtin-personas/design/orchestrator.md → builtin-memory/05-kinds/design/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/developer/PERSONA.md → builtin-memory/05-kinds/developer/00-base.md} +5 -2
- package/dist/{builtin-personas/developer/orchestrator.md → builtin-memory/05-kinds/developer/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/explore/PERSONA.md → builtin-memory/05-kinds/explore/00-base.md} +5 -2
- package/dist/{builtin-personas/explore/orchestrator.md → builtin-memory/05-kinds/explore/01-orchestrator.md} +5 -3
- package/dist/{builtin-personas/general/PERSONA.md → builtin-memory/05-kinds/general/00-base.md} +5 -2
- package/dist/{builtin-personas/general/orchestrator.md → builtin-memory/05-kinds/general/01-orchestrator.md} +5 -3
- package/dist/{builtin-personas/plan/PERSONA.md → builtin-memory/05-kinds/plan/00-base.md} +5 -2
- package/dist/{builtin-personas/plan/orchestrator.md → builtin-memory/05-kinds/plan/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/plan/reviewers/architecture-fit/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/architecture-fit.md} +5 -2
- package/dist/{builtin-personas/plan/reviewers/code-smells/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/code-smells.md} +5 -2
- package/dist/{builtin-personas/plan/reviewers/pattern-consistency/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/pattern-consistency.md} +5 -2
- package/dist/{builtin-personas/plan/reviewers/requirements-coverage/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/requirements-coverage.md} +5 -2
- package/dist/{builtin-personas/plan/reviewers/security/PERSONA.md → builtin-memory/05-kinds/plan/reviewers/security.md} +5 -2
- package/dist/{builtin-personas/product/PERSONA.md → builtin-memory/05-kinds/product/00-base.md} +5 -2
- package/dist/{builtin-personas/product/orchestrator.md → builtin-memory/05-kinds/product/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/product/teardown/PERSONA.md → builtin-memory/05-kinds/product/teardown.md} +5 -3
- package/dist/{builtin-personas/review/PERSONA.md → builtin-memory/05-kinds/review/00-base.md} +5 -2
- package/dist/{builtin-personas/review/orchestrator.md → builtin-memory/05-kinds/review/01-orchestrator.md} +5 -3
- package/dist/{builtin-personas/spec/PERSONA.md → builtin-memory/05-kinds/spec/00-base.md} +5 -2
- package/dist/{builtin-personas/spec/orchestrator.md → builtin-memory/05-kinds/spec/01-orchestrator.md} +5 -4
- package/dist/{builtin-personas/spec/requirements/PERSONA.md → builtin-memory/05-kinds/spec/requirements.md} +5 -2
- package/dist/builtin-memory/internal/INDEX.md +1 -1
- package/dist/builtin-memory/internal/examples/imessage-assistant.md +2 -2
- package/dist/builtin-views/prompt-review/core.mjs +6 -134
- package/dist/builtin-views/settings/core.mjs +26 -94
- package/dist/builtin-views/settings/text.mjs +0 -5
- package/dist/builtin-views/settings/tui.mjs +1 -7
- package/dist/builtin-views/settings/web.jsx +3 -49
- package/dist/clients/attach/attach-cmd.js +416 -416
- package/dist/commands/memory/read.js +3 -3
- package/dist/commands/memory/shared.d.ts +13 -8
- package/dist/commands/memory/shared.js +22 -8
- package/dist/commands/memory/write.js +5 -3
- package/dist/commands/node.js +40 -11
- package/dist/commands/profile/add-project.d.ts +1 -0
- package/dist/commands/profile/add-project.js +42 -0
- package/dist/commands/profile/delete.d.ts +1 -0
- package/dist/commands/profile/delete.js +39 -0
- package/dist/commands/profile/list.d.ts +1 -0
- package/dist/commands/profile/list.js +35 -0
- package/dist/commands/profile/new.d.ts +1 -0
- package/dist/commands/profile/new.js +50 -0
- package/dist/commands/profile/remove-project.d.ts +1 -0
- package/dist/commands/profile/remove-project.js +42 -0
- package/dist/commands/profile/rename.d.ts +1 -0
- package/dist/commands/profile/rename.js +42 -0
- package/dist/commands/profile/show.d.ts +1 -0
- package/dist/commands/profile/show.js +50 -0
- package/dist/commands/profile.d.ts +2 -0
- package/dist/commands/profile.js +33 -0
- package/dist/commands/sys/config.js +6 -21
- package/dist/commands/sys/prompt-review.js +90 -17
- package/dist/core/canvas/canvas.js +10 -9
- package/dist/core/canvas/db.js +11 -0
- package/dist/core/canvas/history.js +1 -0
- package/dist/core/canvas/types.d.ts +10 -0
- package/dist/core/config.d.ts +44 -1
- package/dist/core/config.js +155 -12
- package/dist/core/hearth/provider.d.ts +8 -0
- package/dist/core/hearth/providers/__tests__/sweep-and-release.test.d.ts +1 -0
- package/dist/core/hearth/providers/__tests__/sweep-and-release.test.js +254 -0
- package/dist/core/hearth/providers/blaxel-home.d.ts +39 -0
- package/dist/core/hearth/providers/blaxel-home.js +271 -21
- package/dist/core/hearth/providers/blaxel.d.ts +5 -0
- package/dist/core/hearth/providers/blaxel.js +86 -4
- package/dist/core/hearth/providers/types.d.ts +16 -0
- package/dist/core/memory-resolver.d.ts +26 -15
- package/dist/core/memory-resolver.js +158 -40
- package/dist/core/profiles/manifest.d.ts +33 -0
- package/dist/core/profiles/manifest.js +332 -0
- package/dist/core/profiles/select.d.ts +11 -0
- package/dist/core/profiles/select.js +115 -0
- package/dist/core/runtime/bearings.d.ts +9 -0
- package/dist/core/runtime/bearings.js +31 -0
- package/dist/core/runtime/front-door.js +9 -2
- package/dist/core/runtime/launch.d.ts +25 -7
- package/dist/core/runtime/launch.js +42 -55
- package/dist/core/runtime/nodes.d.ts +7 -0
- package/dist/core/runtime/nodes.js +6 -0
- package/dist/core/runtime/persona.js +18 -32
- package/dist/core/runtime/spawn.d.ts +13 -0
- package/dist/core/runtime/spawn.js +25 -0
- package/dist/core/scope.d.ts +14 -7
- package/dist/core/scope.js +76 -20
- package/dist/core/substrate/index.d.ts +3 -3
- package/dist/core/substrate/index.js +5 -3
- package/dist/core/substrate/on-read.js +3 -2
- package/dist/core/substrate/render.d.ts +16 -11
- package/dist/core/substrate/render.js +249 -62
- package/dist/core/substrate/schema.d.ts +11 -3
- package/dist/core/substrate/schema.js +25 -0
- package/dist/core/substrate/subject.d.ts +15 -2
- package/dist/core/substrate/subject.js +24 -2
- package/dist/hearth/control-plane/__tests__/error-serialization.test.d.ts +1 -0
- package/dist/hearth/control-plane/__tests__/error-serialization.test.js +29 -0
- package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.d.ts +1 -0
- package/dist/hearth/control-plane/__tests__/oauth-serving-marker.test.js +44 -0
- package/dist/hearth/control-plane/__tests__/wake-roll.test.d.ts +1 -0
- package/dist/hearth/control-plane/__tests__/wake-roll.test.js +230 -0
- package/dist/hearth/control-plane/db.js +27 -0
- package/dist/hearth/control-plane/hearth-target.d.ts +23 -0
- package/dist/hearth/control-plane/hearth-target.js +68 -0
- package/dist/hearth/control-plane/registry.d.ts +6 -1
- package/dist/hearth/control-plane/registry.js +7 -0
- package/dist/hearth/control-plane/relay.d.ts +4 -0
- package/dist/hearth/control-plane/relay.js +72 -3
- package/dist/hearth/control-plane/secrets.d.ts +14 -0
- package/dist/hearth/control-plane/secrets.js +21 -0
- package/dist/hearth/control-plane/server.js +140 -4
- package/dist/hearth/control-plane/serving.d.ts +15 -0
- package/dist/hearth/control-plane/serving.js +106 -0
- package/dist/hearth/control-plane/types.d.ts +20 -0
- package/dist/hearth/control-plane/wake.d.ts +8 -2
- package/dist/hearth/control-plane/wake.js +241 -3
- package/dist/pi-extensions/canvas-doc-substrate.js +2 -3
- package/dist/types.d.ts +57 -1
- package/dist/types.js +83 -1
- package/dist/web-client/assets/{index-MSKSWwV0.js → index-CbO8L0mN.js} +4 -4
- package/dist/web-client/assets/index-DwO46Cs5.css +2 -0
- package/dist/web-client/index.html +2 -2
- package/package.json +2 -2
- package/dist/builtin-memory/crouter-development/personas/base-prompt.md +0 -56
- package/dist/builtin-memory/crouter-development/personas/orchestrator-prompt.md +0 -57
- package/dist/builtin-memory/crouter-development/personas.md +0 -112
- package/dist/builtin-personas/spine/no-manager.md +0 -2
- package/dist/core/personas/index.d.ts +0 -13
- package/dist/core/personas/index.js +0 -11
- package/dist/core/personas/loader.d.ts +0 -159
- package/dist/core/personas/loader.js +0 -327
- package/dist/core/personas/resolve.d.ts +0 -59
- package/dist/core/personas/resolve.js +0 -376
- package/dist/web-client/assets/index-DUThOUzU.css +0 -2
|
@@ -85,6 +85,8 @@ function execRequest(bin, args, stdin) {
|
|
|
85
85
|
|
|
86
86
|
/** @typedef {{kind:string, whenToUse:string}} KindInfo */
|
|
87
87
|
|
|
88
|
+
/** @typedef {{config:PromptReviewConfig, layers:PromptLayer[], assembled:string, total:{tokens:number, chars:number}, sources:PromptSource[]}} ReviewPayload */
|
|
89
|
+
|
|
88
90
|
/**
|
|
89
91
|
* @typedef {Object} PromptReviewList
|
|
90
92
|
* @property {KindInfo[]} kinds
|
|
@@ -155,19 +157,6 @@ function normalizeConfig(config) {
|
|
|
155
157
|
};
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
/** @param {PromptLayer[]} layers @returns {PromptSource[]} */
|
|
159
|
-
function dedupeSources(layers) {
|
|
160
|
-
/** @type {PromptSource[]} */
|
|
161
|
-
const out = [];
|
|
162
|
-
const seen = new Set();
|
|
163
|
-
for (const layer of layers) {
|
|
164
|
-
if (!layer.sourcePath || seen.has(layer.sourcePath)) continue;
|
|
165
|
-
seen.add(layer.sourcePath);
|
|
166
|
-
out.push({ id: layer.id, label: layer.label, path: layer.sourcePath, scope: layer.scope });
|
|
167
|
-
}
|
|
168
|
-
return out;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
160
|
/** @param {string} s @returns {string} */
|
|
172
161
|
function normalizeText(s) {
|
|
173
162
|
return str(s).replace(/\r\n/g, '\n');
|
|
@@ -184,12 +173,6 @@ function chars(text) { return normalizeText(text).length; }
|
|
|
184
173
|
/** @param {string} text @returns {number} */
|
|
185
174
|
function tokens(text) { return approxTokens(chars(text)); }
|
|
186
175
|
|
|
187
|
-
/** @param {string} id @param {string} label @param {LayerGroup} group @param {string} source @param {string|null} sourcePath @param {LayerScope} scope @param {boolean} included @param {string|null} condition @param {string} text @returns {PromptLayer} */
|
|
188
|
-
function makeLayer(id, label, group, source, sourcePath, scope, included, condition, text) {
|
|
189
|
-
const norm = normalizeText(text);
|
|
190
|
-
return { id, label, group, source, sourcePath, scope, included, condition, text: norm, tokens: tokens(norm), chars: chars(norm) };
|
|
191
|
-
}
|
|
192
|
-
|
|
193
176
|
/** @param {string} kind @returns {string} */
|
|
194
177
|
function kindLabel(kind) { return kind || DEFAULT_KIND; }
|
|
195
178
|
|
|
@@ -217,112 +200,9 @@ function layerLabel(id) {
|
|
|
217
200
|
return map[id] || id.replace(/[-_]/g, ' ');
|
|
218
201
|
}
|
|
219
202
|
|
|
220
|
-
/** @param {PromptReviewConfig} config @returns {PromptLayer[]} */
|
|
221
|
-
function buildReviewLayers(config) {
|
|
222
|
-
const c = normalizeConfig(config);
|
|
223
|
-
const base = 'System prompt composition and agent runtime rules.';
|
|
224
|
-
const spineHas = c.hasManager;
|
|
225
|
-
const spineNo = !c.hasManager;
|
|
226
|
-
const lifecycleTerminal = c.lifecycle === 'terminal';
|
|
227
|
-
const lifecycleResident = !lifecycleTerminal;
|
|
228
|
-
const baseText = joinPieces([
|
|
229
|
-
'# Runtime base',
|
|
230
|
-
base,
|
|
231
|
-
]);
|
|
232
|
-
const spineHasText = joinPieces([
|
|
233
|
-
'# Spine with manager',
|
|
234
|
-
'You have a manager. Report upward and keep the spine active.',
|
|
235
|
-
]);
|
|
236
|
-
const spineNoText = joinPieces([
|
|
237
|
-
'# Spine without manager',
|
|
238
|
-
'You have no manager. Work independently and do not assume an upstream reviewer.',
|
|
239
|
-
]);
|
|
240
|
-
const lifecycleTerminalText = joinPieces([
|
|
241
|
-
'# Terminal lifecycle',
|
|
242
|
-
'This node must finish with a final report when its work is done.',
|
|
243
|
-
]);
|
|
244
|
-
const lifecycleResidentText = joinPieces([
|
|
245
|
-
'# Resident lifecycle',
|
|
246
|
-
'This node stays resident and waits for inbound events instead of always owing a final.',
|
|
247
|
-
]);
|
|
248
|
-
const waitingText = joinPieces([
|
|
249
|
-
'# Waiting',
|
|
250
|
-
'When you are blocked on a future event, stop and wait instead of busy-looping.',
|
|
251
|
-
]);
|
|
252
|
-
const personaBody = joinPieces([
|
|
253
|
-
`# ${kindLabel(c.kind)} persona`,
|
|
254
|
-
`Kind-specific prompt body for ${c.kind}.`,
|
|
255
|
-
]);
|
|
256
|
-
const orchestrationKernel = joinPieces([
|
|
257
|
-
'# Orchestration kernel',
|
|
258
|
-
'Kernel guidance for orchestrator mode.',
|
|
259
|
-
]);
|
|
260
|
-
|
|
261
|
-
/** @type {PromptLayer[]} */
|
|
262
|
-
const layers = [
|
|
263
|
-
makeLayer('runtime-base', 'Runtime base', 'protocol', 'personas/runtime-base.md', '/abs/src/builtin-personas/runtime-base.md', 'builtin', true, null, baseText),
|
|
264
|
-
makeLayer('spine-has-manager', 'Spine (has manager)', 'protocol', 'personas/spine/has-manager.md', '/abs/src/builtin-personas/spine/has-manager.md', 'builtin', spineHas, spineHas ? null : 'hasManager == false', spineHasText),
|
|
265
|
-
makeLayer('spine-no-manager', 'Spine (no manager)', 'protocol', 'personas/spine/no-manager.md', '/abs/src/builtin-personas/spine/no-manager.md', 'builtin', spineNo, spineNo ? null : 'hasManager == true', spineNoText),
|
|
266
|
-
makeLayer('lifecycle-terminal', 'Lifecycle (terminal)', 'protocol', 'personas/lifecycle/terminal.md', '/abs/src/builtin-personas/lifecycle/terminal.md', 'builtin', lifecycleTerminal, lifecycleTerminal ? null : 'lifecycle == resident', lifecycleTerminalText),
|
|
267
|
-
makeLayer('lifecycle-resident', 'Lifecycle (resident)', 'protocol', 'personas/lifecycle/resident.md', '/abs/src/builtin-personas/lifecycle/resident.md', 'builtin', lifecycleResident, lifecycleResident ? null : 'lifecycle == terminal', lifecycleResidentText),
|
|
268
|
-
makeLayer('waiting', 'Waiting', 'protocol', 'personas/waiting.md', '/abs/src/builtin-personas/waiting.md', 'builtin', true, null, waitingText),
|
|
269
|
-
makeLayer('persona-body', 'Persona body', 'persona', c.mode === 'base' ? `personas/${c.kind}/PERSONA.md` : `personas/${c.kind}/orchestrator.md`, c.mode === 'base' ? `/abs/src/builtin-personas/${c.kind}/PERSONA.md` : `/abs/src/builtin-personas/${c.kind}/orchestrator.md`, 'builtin', true, null, c.mode === 'base' ? personaBody : joinPieces([personaBody, orchestrationKernel])),
|
|
270
|
-
makeLayer('subpersona-menu', 'Sub-persona menu', 'generated', `generated:${c.kind}:sub-personas`, null, 'generated', true, null, joinPieces([
|
|
271
|
-
`Generated sub-persona menu for ${c.kind}.`,
|
|
272
|
-
'This layer is synthesized from the available sub-personas and their source files.',
|
|
273
|
-
])),
|
|
274
|
-
];
|
|
275
|
-
|
|
276
|
-
if (c.node) {
|
|
277
|
-
layers.push(
|
|
278
|
-
makeLayer('substrate-preferences', 'Preferences substrate', 'substrate', 'runtime:preferences', null, 'runtime', true, null, joinPieces([
|
|
279
|
-
'# Preferences substrate',
|
|
280
|
-
'Node-scoped preferences are spliced into the system prompt here.',
|
|
281
|
-
])),
|
|
282
|
-
makeLayer('substrate-memory-guidance', 'Memory guidance substrate', 'substrate', 'runtime:memory-guidance', null, 'runtime', true, null, joinPieces([
|
|
283
|
-
'# Memory guidance',
|
|
284
|
-
'Runtime-spliced memory guidance shown here because a node was supplied.',
|
|
285
|
-
])),
|
|
286
|
-
makeLayer('substrate-knowledge', 'Context message — not system prompt', 'substrate', 'runtime:knowledge', null, 'runtime', true, null, joinPieces([
|
|
287
|
-
'# Context message',
|
|
288
|
-
'This node-specific knowledge block is not part of the system prompt.',
|
|
289
|
-
])),
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return layers;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/** @param {PromptReviewConfig} config @returns {string} */
|
|
297
|
-
function composeAssembled(config) {
|
|
298
|
-
const c = normalizeConfig(config);
|
|
299
|
-
const layers = buildReviewLayers(c);
|
|
300
|
-
const protocol = layers.filter((layer) => layer.group !== 'substrate');
|
|
301
|
-
const substrate = layers.filter((layer) => layer.group === 'substrate');
|
|
302
|
-
const protocolText = joinPieces(protocol.filter((layer) => layer.included).map((layer) => layer.text));
|
|
303
|
-
if (c.node && substrate.length) {
|
|
304
|
-
return joinPieces([protocolText, ...substrate.map((layer) => layer.text)]);
|
|
305
|
-
}
|
|
306
|
-
return protocolText;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/** @param {PromptReviewConfig} config @param {PromptLayer[]} layers @returns {PromptLayer[]} */
|
|
310
|
-
function maybeAddNodeLayers(config, layers) {
|
|
311
|
-
if (!config.node) return layers;
|
|
312
|
-
return layers.concat(buildReviewLayers(config).filter((layer) => layer.group === 'substrate'));
|
|
313
|
-
}
|
|
314
|
-
|
|
315
203
|
/** @param {PromptLayer} layer @returns {string} */
|
|
316
204
|
function sourceForLayer(layer) { return layer.sourcePath || layer.source; }
|
|
317
205
|
|
|
318
|
-
/** @param {PromptReviewConfig} config @param {PromptLayer[]} layers @returns {{config:PromptReviewConfig, layers:PromptLayer[], assembled:string, total:{tokens:number, chars:number}, sources:PromptSource[]}} */
|
|
319
|
-
function reviewPayload(config, layers) {
|
|
320
|
-
const assembled = composeAssembled(config);
|
|
321
|
-
const totalChars = chars(assembled);
|
|
322
|
-
const totalTokens = tokens(assembled);
|
|
323
|
-
return { config: normalizeConfig(config), layers, assembled, total: { tokens: totalTokens, chars: totalChars }, sources: dedupeSources(layers) };
|
|
324
|
-
}
|
|
325
|
-
|
|
326
206
|
/** @param {unknown} value @returns {value is PromptReviewConfig} */
|
|
327
207
|
function looksLikeConfig(value) {
|
|
328
208
|
return !!value && typeof value === 'object' && 'kind' in /** @type {any} */ (value) && 'mode' in /** @type {any} */ (value) && 'lifecycle' in /** @type {any} */ (value);
|
|
@@ -388,11 +268,6 @@ function diffReviews(left, right) {
|
|
|
388
268
|
return out;
|
|
389
269
|
}
|
|
390
270
|
|
|
391
|
-
/** @param {PromptReviewConfig} config @param {PromptLayer[]} layers @returns {{config:PromptReviewConfig, layers:PromptLayer[], assembled:string, total:{tokens:number, chars:number}, sources:PromptSource[]}} */
|
|
392
|
-
function makeReview(config, layers) {
|
|
393
|
-
return reviewPayload(config, layers);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
271
|
/** @type {import('../../core/view/contract.js').Source<PromptReviewList>} */
|
|
397
272
|
export const listSource = {
|
|
398
273
|
id: 'prompt-review-list',
|
|
@@ -414,7 +289,7 @@ export const listSource = {
|
|
|
414
289
|
},
|
|
415
290
|
};
|
|
416
291
|
|
|
417
|
-
/** @type {import('../../core/view/contract.js').Source<
|
|
292
|
+
/** @type {import('../../core/view/contract.js').Source<ReviewPayload, PromptReviewConfig>} */
|
|
418
293
|
export const reviewSource = {
|
|
419
294
|
id: 'prompt-review-review',
|
|
420
295
|
request: (config) => {
|
|
@@ -464,7 +339,7 @@ export const reviewSource = {
|
|
|
464
339
|
tokens: typeof data.total.tokens === 'number' ? data.total.tokens : approxTokens(chars(assembled)),
|
|
465
340
|
chars: typeof data.total.chars === 'number' ? data.total.chars : chars(assembled),
|
|
466
341
|
};
|
|
467
|
-
return ok(/** @type {
|
|
342
|
+
return ok(/** @type {ReviewPayload} */ ({ config, layers, assembled, total, sources }));
|
|
468
343
|
},
|
|
469
344
|
};
|
|
470
345
|
|
|
@@ -507,7 +382,7 @@ export const exportCommand = {
|
|
|
507
382
|
},
|
|
508
383
|
};
|
|
509
384
|
|
|
510
|
-
/** @param {PromptReviewConfig} config @param {PromptReviewConfig|null} compareConfig @returns {Promise<{review:
|
|
385
|
+
/** @param {PromptReviewConfig} config @param {PromptReviewConfig|null} compareConfig @returns {Promise<{review: ReviewPayload, compareReview: ReviewPayload | null, list: PromptReviewList | null, liveNodes: Array<{node_id:string, name:string, kind:string, mode:string, lifecycle:string, status:string, cwd:string, parent:string|null, created:string, enterable:boolean}> | null}>} */
|
|
511
386
|
async function loadAll(ctx, config, compareConfig) {
|
|
512
387
|
const tasks = [ctx.resolve(listSource), ctx.resolve(reviewSource, config), ctx.resolve(nodeListSource)];
|
|
513
388
|
if (compareConfig) tasks.push(ctx.resolve(reviewSource, compareConfig));
|
|
@@ -546,7 +421,7 @@ function cursorFor(review, id) {
|
|
|
546
421
|
return idx >= 0 ? idx : Math.max(0, review.findIndex((layer) => layer.included) >= 0 ? review.findIndex((layer) => layer.included) : 0);
|
|
547
422
|
}
|
|
548
423
|
|
|
549
|
-
/** @param {ReviewState} state @param {PromptReviewConfig} config @param {PromptReviewConfig|null} compareConfig @param {
|
|
424
|
+
/** @param {ReviewState} state @param {PromptReviewConfig} config @param {PromptReviewConfig|null} compareConfig @param {ReviewPayload|null} reviewPayloadObj @param {ReviewPayload|null} comparePayloadObj @returns {ReviewState} */
|
|
550
425
|
function materialize(state, config, compareConfig, reviewPayloadObj, comparePayloadObj) {
|
|
551
426
|
const review = reviewPayloadObj ? reviewPayloadObj.layers : null;
|
|
552
427
|
const compareReview = comparePayloadObj ? comparePayloadObj.layers : null;
|
|
@@ -852,11 +727,8 @@ export {
|
|
|
852
727
|
diffReviews,
|
|
853
728
|
layerLabel,
|
|
854
729
|
makeDefaultCompare,
|
|
855
|
-
reviewPayload,
|
|
856
730
|
selectedLayer,
|
|
857
731
|
compareSelectedLayer,
|
|
858
|
-
composeAssembled,
|
|
859
|
-
buildReviewLayers,
|
|
860
732
|
kindLabel,
|
|
861
733
|
};
|
|
862
734
|
|
|
@@ -83,21 +83,19 @@ function makeConfigSetCommand(key, value) {
|
|
|
83
83
|
};
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
/** @typedef {{ kind:'defaultProvider' } | { kind:'matrix'; strength:ModelStrength }
|
|
86
|
+
/** @typedef {{ kind:'defaultProvider' } | { kind:'matrix'; strength:ModelStrength }} Row */
|
|
87
87
|
|
|
88
88
|
/** @typedef {Object} EditState
|
|
89
|
-
* @property {'defaultProvider'|'matrixCell'
|
|
89
|
+
* @property {'defaultProvider'|'matrixCell'} kind
|
|
90
90
|
* @property {string} draft
|
|
91
91
|
* @property {string} value
|
|
92
92
|
* @property {string} label
|
|
93
93
|
* @property {ModelStrength} [strength]
|
|
94
94
|
* @property {ModelProvider} [provider]
|
|
95
|
-
* @property {string} [persona]
|
|
96
95
|
*/
|
|
97
96
|
|
|
98
97
|
/** @typedef {Object} SettingsState
|
|
99
98
|
* @property {ModelLaddersConfig|null} modelLadders
|
|
100
|
-
* @property {Record<string, ModelStrength>} personaStrengths
|
|
101
99
|
* @property {Record<ModelStrength, ModelProvider>} matrixSelection
|
|
102
100
|
* @property {number} cursor
|
|
103
101
|
* @property {number} scroll
|
|
@@ -109,10 +107,8 @@ function makeConfigSetCommand(key, value) {
|
|
|
109
107
|
/** @param {SettingsState} state @returns {Row[]} */
|
|
110
108
|
export function buildRows(state) {
|
|
111
109
|
if (!state.modelLadders) return [];
|
|
112
|
-
const rows = [{ kind: 'defaultProvider' }];
|
|
113
|
-
for (const strength of STRENGTHS) rows.push({ kind: 'matrix', strength });
|
|
114
|
-
for (const persona of Object.keys(state.personaStrengths).sort((a, b) => a.localeCompare(b))) rows.push({ kind: 'persona', persona });
|
|
115
|
-
rows.push({ kind: 'addPersona' });
|
|
110
|
+
const rows = [{ kind: /** @type {'defaultProvider'} */ ('defaultProvider') }];
|
|
111
|
+
for (const strength of STRENGTHS) rows.push({ kind: /** @type {'matrix'} */ ('matrix'), strength });
|
|
116
112
|
return rows;
|
|
117
113
|
}
|
|
118
114
|
|
|
@@ -131,67 +127,30 @@ function currentRow(state) {
|
|
|
131
127
|
export function settingsRowLabel(state, row) {
|
|
132
128
|
if (!row) return '';
|
|
133
129
|
if (row.kind === 'defaultProvider') return 'Default provider';
|
|
134
|
-
|
|
135
|
-
if (row.kind === 'persona') return row.persona;
|
|
136
|
-
return 'Add persona strength';
|
|
130
|
+
return `${row.strength} strength`;
|
|
137
131
|
}
|
|
138
132
|
|
|
139
|
-
/** @param {
|
|
140
|
-
function parseStrength(draft) {
|
|
141
|
-
const s = draft.trim();
|
|
142
|
-
return s === 'ultra' || s === 'strong' || s === 'medium' || s === 'light' ? s : null;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/** @param {string} draft @returns {{persona:string, strength:ModelStrength}|null} */
|
|
146
|
-
function parsePersonaPair(draft) {
|
|
147
|
-
const s = draft.trim();
|
|
148
|
-
if (!s) return null;
|
|
149
|
-
const eq = s.indexOf('=');
|
|
150
|
-
if (eq > 0) {
|
|
151
|
-
const persona = s.slice(0, eq).trim();
|
|
152
|
-
const strength = parseStrength(s.slice(eq + 1));
|
|
153
|
-
return persona && strength ? { persona, strength } : null;
|
|
154
|
-
}
|
|
155
|
-
const parts = s.split(/\s+/).filter(Boolean);
|
|
156
|
-
if (parts.length >= 2) {
|
|
157
|
-
const strength = parseStrength(parts[parts.length - 1] || '');
|
|
158
|
-
const persona = parts.slice(0, -1).join(' ').trim();
|
|
159
|
-
return persona && strength ? { persona, strength } : null;
|
|
160
|
-
}
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/** @param {SettingsState} state @param {Row} row @returns {{ kind:'defaultProvider'|'matrixCell'|'personaStrength'|'personaNew', draft:string, value:string, label:string, strength?:ModelStrength, provider?:ModelProvider, persona?:string }} */
|
|
133
|
+
/** @param {SettingsState} state @param {Row} row @returns {{ kind:'defaultProvider'|'matrixCell', draft:string, value:string, label:string, strength?:ModelStrength, provider?:ModelProvider }} */
|
|
165
134
|
export function settingsTargetForRow(state, row) {
|
|
166
135
|
if (row.kind === 'defaultProvider') {
|
|
167
136
|
return { kind: 'defaultProvider', draft: '', value: state.modelLadders?.defaultProvider ?? 'anthropic', label: 'default provider' };
|
|
168
137
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
if (row.kind === 'persona') {
|
|
181
|
-
return { kind: 'personaStrength', persona: row.persona, draft: '', value: state.personaStrengths[row.persona] ?? 'strong', label: row.persona };
|
|
182
|
-
}
|
|
183
|
-
return { kind: 'personaNew', draft: '', value: '', label: 'new persona strength' };
|
|
138
|
+
const provider = state.matrixSelection[row.strength] ?? DEFAULT_MATRIX_SELECTION[row.strength];
|
|
139
|
+
return {
|
|
140
|
+
kind: 'matrixCell',
|
|
141
|
+
strength: row.strength,
|
|
142
|
+
provider,
|
|
143
|
+
draft: '',
|
|
144
|
+
value: state.modelLadders?.[provider]?.[row.strength] ?? '',
|
|
145
|
+
label: `${provider} / ${row.strength}`,
|
|
146
|
+
};
|
|
184
147
|
}
|
|
185
148
|
|
|
186
|
-
/** @param {Ctx} ctx @returns {Promise<{ok:true, data:{ladders: ModelLaddersConfig
|
|
149
|
+
/** @param {Ctx} ctx @returns {Promise<{ok:true, data:{ladders: ModelLaddersConfig}} | {ok:false, error:SourceError}>} */
|
|
187
150
|
async function loadConfig(ctx) {
|
|
188
|
-
const
|
|
189
|
-
ctx.resolve(makeConfigGetSource('modelLadders')),
|
|
190
|
-
ctx.resolve(makeConfigGetSource('personaStrengths')),
|
|
191
|
-
]);
|
|
151
|
+
const ladders = await ctx.resolve(makeConfigGetSource('modelLadders'));
|
|
192
152
|
if (!ladders.ok) return ladders;
|
|
193
|
-
|
|
194
|
-
return ok({ ladders: /** @type {ModelLaddersConfig} */ (ladders.data), personaStrengths: /** @type {Record<string, ModelStrength>} */ (personaStrengths.data) });
|
|
153
|
+
return ok({ ladders: /** @type {ModelLaddersConfig} */ (ladders.data) });
|
|
195
154
|
}
|
|
196
155
|
|
|
197
156
|
/** @param {Ctx} ctx @param {string} msg @param {SourceError['display']['level']} [level='error'] */
|
|
@@ -200,7 +159,7 @@ function setBanner(ctx, msg, level = 'error') {
|
|
|
200
159
|
else ctx.signal.setBanner(msg, level);
|
|
201
160
|
}
|
|
202
161
|
|
|
203
|
-
/** @typedef {{ kind:'defaultProvider'|'matrixCell'
|
|
162
|
+
/** @typedef {{ kind:'defaultProvider'|'matrixCell', value:string, strength?:ModelStrength, provider?:ModelProvider }} PersistTarget */
|
|
204
163
|
|
|
205
164
|
/** Validate a target, write it via `crtr sys config set`, fold the result into
|
|
206
165
|
* local state, and re-pull. Shared by submitEdit (text fields) and applyValue
|
|
@@ -212,24 +171,13 @@ async function persist(ctx, target) {
|
|
|
212
171
|
if (value !== 'anthropic' && value !== 'openai') { setBanner(ctx, 'default provider must be anthropic or openai', 'action'); return false; }
|
|
213
172
|
} else if (target.kind === 'matrixCell') {
|
|
214
173
|
if (!value) { setBanner(ctx, 'model id cannot be empty', 'action'); return false; }
|
|
215
|
-
} else if (target.kind === 'personaStrength') {
|
|
216
|
-
if (!parseStrength(value)) { setBanner(ctx, 'persona strength must be ultra, strong, medium, or light', 'action'); return false; }
|
|
217
|
-
} else if (target.kind === 'personaNew') {
|
|
218
|
-
if (!parsePersonaPair(value)) { setBanner(ctx, 'use "kind strength" or "kind=strength"', 'action'); return false; }
|
|
219
174
|
}
|
|
220
175
|
|
|
221
176
|
ctx.signal.setStatus('Saving…');
|
|
222
177
|
let key = '';
|
|
223
|
-
|
|
224
|
-
if (target.kind === 'defaultProvider') { key = 'modelLadders.defaultProvider';
|
|
225
|
-
else
|
|
226
|
-
else if (target.kind === 'personaStrength') { key = `personaStrengths.${target.persona}`; writeValue = value; }
|
|
227
|
-
else {
|
|
228
|
-
const pair = parsePersonaPair(value);
|
|
229
|
-
if (!pair) return false;
|
|
230
|
-
key = `personaStrengths.${pair.persona}`;
|
|
231
|
-
writeValue = pair.strength;
|
|
232
|
-
}
|
|
178
|
+
const writeValue = value;
|
|
179
|
+
if (target.kind === 'defaultProvider') { key = 'modelLadders.defaultProvider'; }
|
|
180
|
+
else { key = `modelLadders.${target.provider}.${target.strength}`; }
|
|
233
181
|
|
|
234
182
|
const r = await ctx.execute(makeConfigSetCommand(key, writeValue));
|
|
235
183
|
if (!r.ok) {
|
|
@@ -241,11 +189,6 @@ async function persist(ctx, target) {
|
|
|
241
189
|
const next = { ...s, edit: null };
|
|
242
190
|
if (target.kind === 'defaultProvider' && next.modelLadders) next.modelLadders = { ...next.modelLadders, defaultProvider: value };
|
|
243
191
|
if (target.kind === 'matrixCell' && next.modelLadders) next.modelLadders = { ...next.modelLadders, [target.provider]: { ...next.modelLadders[target.provider], [target.strength]: value } };
|
|
244
|
-
if (target.kind === 'personaStrength') next.personaStrengths = { ...next.personaStrengths, [target.persona]: /** @type {ModelStrength} */ (value) };
|
|
245
|
-
if (target.kind === 'personaNew') {
|
|
246
|
-
const pair = parsePersonaPair(value);
|
|
247
|
-
if (pair) next.personaStrengths = { ...next.personaStrengths, [pair.persona]: pair.strength };
|
|
248
|
-
}
|
|
249
192
|
return next;
|
|
250
193
|
});
|
|
251
194
|
ctx.signal.setStatus('Saved');
|
|
@@ -258,14 +201,13 @@ const core = {
|
|
|
258
201
|
manifest: {
|
|
259
202
|
id: 'settings',
|
|
260
203
|
title: 'Model Ladders',
|
|
261
|
-
subtitle: 'provider × strength matrix
|
|
262
|
-
description: 'edit model ladders
|
|
204
|
+
subtitle: 'provider × strength matrix',
|
|
205
|
+
description: 'edit model ladders through crtr commands',
|
|
263
206
|
},
|
|
264
207
|
|
|
265
208
|
init() {
|
|
266
209
|
return {
|
|
267
210
|
modelLadders: null,
|
|
268
|
-
personaStrengths: {},
|
|
269
211
|
matrixSelection: { ...DEFAULT_MATRIX_SELECTION },
|
|
270
212
|
cursor: 0,
|
|
271
213
|
scroll: 0,
|
|
@@ -277,7 +219,6 @@ const core = {
|
|
|
277
219
|
|
|
278
220
|
sources: {
|
|
279
221
|
modelLadders: makeConfigGetSource('modelLadders'),
|
|
280
|
-
personaStrengths: makeConfigGetSource('personaStrengths'),
|
|
281
222
|
},
|
|
282
223
|
|
|
283
224
|
intents: {
|
|
@@ -297,7 +238,6 @@ const core = {
|
|
|
297
238
|
const next = {
|
|
298
239
|
...s,
|
|
299
240
|
modelLadders: r.data.ladders,
|
|
300
|
-
personaStrengths: r.data.personaStrengths,
|
|
301
241
|
sourceError: null,
|
|
302
242
|
lastFetch: Date.now(),
|
|
303
243
|
};
|
|
@@ -343,11 +283,7 @@ const core = {
|
|
|
343
283
|
const cur = s.modelLadders.defaultProvider ?? 'anthropic';
|
|
344
284
|
return { ...s, edit: { kind: 'defaultProvider', draft: cur, value: cur, label: 'default provider' } };
|
|
345
285
|
}
|
|
346
|
-
|
|
347
|
-
const cur = s.personaStrengths[p.persona] ?? 'strong';
|
|
348
|
-
return { ...s, edit: { kind: 'personaStrength', persona: p.persona, draft: cur, value: cur, label: p.persona } };
|
|
349
|
-
}
|
|
350
|
-
return { ...s, edit: { kind: 'personaNew', draft: '', value: '', label: 'new persona strength' } };
|
|
286
|
+
return s;
|
|
351
287
|
}),
|
|
352
288
|
|
|
353
289
|
setDraft: (ctx, draft) => ctx.set((s) => (s.edit ? { ...s, edit: { ...s.edit, draft: typeof draft === 'string' ? draft : '' } } : s)),
|
|
@@ -357,7 +293,7 @@ const core = {
|
|
|
357
293
|
async submitEdit(ctx) {
|
|
358
294
|
const edit = ctx.state.edit;
|
|
359
295
|
if (!edit) return;
|
|
360
|
-
await persist(ctx, { kind: edit.kind, value: edit.draft, strength: edit.strength, provider: edit.provider
|
|
296
|
+
await persist(ctx, { kind: edit.kind, value: edit.draft, strength: edit.strength, provider: edit.provider });
|
|
361
297
|
},
|
|
362
298
|
|
|
363
299
|
// One-click save for enum fields (web chips): no editor round-trip. Payload
|
|
@@ -368,16 +304,12 @@ const core = {
|
|
|
368
304
|
await persist(ctx, p);
|
|
369
305
|
},
|
|
370
306
|
|
|
371
|
-
jumpAddPersona: (ctx) => ctx.set((s) => ({ ...s, cursor: Math.max(0, buildRows(s).length - 1) })),
|
|
372
|
-
|
|
373
307
|
selectRow: (ctx, payload) => ctx.set((s) => {
|
|
374
308
|
const p = payload && typeof payload === 'object' ? /** @type {Record<string, unknown>} */ (payload) : {};
|
|
375
309
|
const rows = buildRows(s);
|
|
376
310
|
const target = rows.findIndex((row) => {
|
|
377
311
|
if (row.kind === 'defaultProvider' && p.kind === 'defaultProvider') return true;
|
|
378
312
|
if (row.kind === 'matrix' && p.kind === 'matrix' && p.strength === row.strength) return true;
|
|
379
|
-
if (row.kind === 'persona' && p.kind === 'persona' && p.persona === row.persona) return true;
|
|
380
|
-
if (row.kind === 'addPersona' && p.kind === 'addPersona') return true;
|
|
381
313
|
return false;
|
|
382
314
|
});
|
|
383
315
|
if (target < 0) return s;
|
|
@@ -30,11 +30,6 @@ export function dump(state, ctx) {
|
|
|
30
30
|
lines.push(`${row.strength}: anthropic=${state.modelLadders.anthropic?.[row.strength] ?? '—'} | openai=${state.modelLadders.openai?.[row.strength] ?? '—'}${provider ? ` [selected ${provider}]` : ''}`);
|
|
31
31
|
continue;
|
|
32
32
|
}
|
|
33
|
-
if (row.kind === 'persona') {
|
|
34
|
-
lines.push(`${row.persona}: ${state.personaStrengths[row.persona] ?? 'strong'}`);
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
lines.push('+ add persona strength');
|
|
38
33
|
}
|
|
39
34
|
return lines.join('\n');
|
|
40
35
|
}
|
|
@@ -37,11 +37,6 @@ function rows(state) {
|
|
|
37
37
|
});
|
|
38
38
|
continue;
|
|
39
39
|
}
|
|
40
|
-
if (row.kind === 'persona') {
|
|
41
|
-
out.push({ spans: [{ text: row.persona, style: { bold: true } }, { text: ' → ' }, { text: state.personaStrengths[row.persona] ?? 'strong', style: { fg: '33' } }] });
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
out.push({ spans: [{ text: '+ add persona strength', style: { fg: '32', bold: true } }] });
|
|
45
40
|
}
|
|
46
41
|
return out;
|
|
47
42
|
}
|
|
@@ -67,7 +62,7 @@ export function render(state, draw, content) {
|
|
|
67
62
|
|
|
68
63
|
const header = state.edit
|
|
69
64
|
? `Replacing ${state.edit.label} — Enter saves, Esc cancels`
|
|
70
|
-
: 'j/k move · h/l switch matrix cell · enter edit ·
|
|
65
|
+
: 'j/k move · h/l switch matrix cell · enter edit · g refresh';
|
|
71
66
|
draw.text(content.row, content.col, 'Model ladders', { bold: true });
|
|
72
67
|
draw.text(content.row + 1, content.col, header, { dim: true });
|
|
73
68
|
if (state.edit) {
|
|
@@ -85,7 +80,6 @@ export const keymap = [
|
|
|
85
80
|
{ keys: ['k', 'up'], intent: 'cursorUp', when: (s) => !s.edit },
|
|
86
81
|
{ keys: ['h', 'left'], intent: 'matrixLeft', when: (s) => !s.edit, hint: { keys: 'h/l', label: 'switch cell' } },
|
|
87
82
|
{ keys: ['l', 'right'], intent: 'matrixRight', when: (s) => !s.edit },
|
|
88
|
-
{ keys: ['n'], intent: 'jumpAddPersona', when: (s) => !s.edit, hint: { keys: 'n', label: 'add persona' } },
|
|
89
83
|
{ keys: ['g'], intent: 'refresh', when: (s) => !s.edit, hint: { keys: 'g', label: 'refresh' } },
|
|
90
84
|
{ keys: ['q'], intent: 'quit', hint: { keys: 'q', label: 'quit' } },
|
|
91
85
|
{ capture: 'setDraft', when: (s) => !!s.edit },
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Model ladders settings — web presenter for the `settings` view.
|
|
4
4
|
*
|
|
5
|
-
* Mouse-first: enum
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Mouse-first: the enum field (default provider) is a click-to-pick chip that
|
|
6
|
+
* saves immediately; free-text fields (matrix model ids) open an inline editor
|
|
7
|
+
* prefilled with the current value, with Save/Cancel.
|
|
8
8
|
*
|
|
9
9
|
* @module settings/web
|
|
10
10
|
*/
|
|
@@ -16,33 +16,6 @@ import { Loading, NotReady, ErrorState } from '@crouton-kit/crouter/web';
|
|
|
16
16
|
const STRENGTHS = ['ultra', 'strong', 'medium', 'light'];
|
|
17
17
|
const PROVIDERS = ['anthropic', 'openai'];
|
|
18
18
|
|
|
19
|
-
/** Inline editor for a free-text field (matrix model id, new persona pair). */
|
|
20
|
-
/** @param {{ state: SettingsState, dispatch: (intent:string, payload?:unknown)=>void }} props */
|
|
21
|
-
function Editor({ state, dispatch }) {
|
|
22
|
-
if (!state.edit) return null;
|
|
23
|
-
return (
|
|
24
|
-
<div className="rounded border border-cyan-300 bg-cyan-50 p-3">
|
|
25
|
-
<div className="mb-2 text-sm text-slate-600">Editing <span className="font-semibold text-slate-900">{state.edit.label}</span></div>
|
|
26
|
-
<div className="flex items-center gap-2">
|
|
27
|
-
<input
|
|
28
|
-
autoFocus
|
|
29
|
-
value={state.edit.draft}
|
|
30
|
-
placeholder={state.edit.kind === 'personaNew' ? 'kind strength' : 'model id'}
|
|
31
|
-
onChange={(e) => dispatch('setDraft', e.target.value)}
|
|
32
|
-
onKeyDown={(e) => {
|
|
33
|
-
if (e.key === 'Enter') { e.preventDefault(); dispatch('submitEdit'); }
|
|
34
|
-
else if (e.key === 'Escape') { e.preventDefault(); dispatch('cancelEdit'); }
|
|
35
|
-
}}
|
|
36
|
-
className="flex-1 rounded border border-slate-300 bg-white px-3 py-2 font-mono text-sm outline-none focus:border-cyan-500"
|
|
37
|
-
/>
|
|
38
|
-
<button type="button" onClick={() => dispatch('submitEdit')} className="rounded bg-cyan-600 px-3 py-2 text-sm font-semibold text-white hover:bg-cyan-700">Save</button>
|
|
39
|
-
<button type="button" onClick={() => dispatch('cancelEdit')} className="rounded border border-slate-300 bg-white px-3 py-2 text-sm text-slate-600 hover:bg-slate-100">Cancel</button>
|
|
40
|
-
</div>
|
|
41
|
-
{state.edit.kind === 'personaNew' ? <div className="mt-2 text-xs text-slate-500">Format: <code>kind strength</code> or <code>kind=strength</code> (strength = ultra·strong·medium·light).</div> : null}
|
|
42
|
-
</div>
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
19
|
/** A small segmented control of fixed choices that saves on click. */
|
|
47
20
|
/** @param {{ choices: string[], value: string, onPick: (c:string)=>void }} props */
|
|
48
21
|
function Segmented({ choices, value, onPick }) {
|
|
@@ -80,8 +53,6 @@ export default function Settings({ state, dispatch }) {
|
|
|
80
53
|
}
|
|
81
54
|
|
|
82
55
|
const ladders = state.modelLadders;
|
|
83
|
-
const personas = Object.keys(state.personaStrengths).sort((a, b) => a.localeCompare(b));
|
|
84
|
-
const editingPersonaNew = !!state.edit && state.edit.kind === 'personaNew';
|
|
85
56
|
|
|
86
57
|
return (
|
|
87
58
|
<div className="h-full overflow-auto text-sm">
|
|
@@ -144,23 +115,6 @@ export default function Settings({ state, dispatch }) {
|
|
|
144
115
|
</div>
|
|
145
116
|
))}
|
|
146
117
|
</div>
|
|
147
|
-
|
|
148
|
-
{/* Persona strengths — enum per persona, click to pick. */}
|
|
149
|
-
<div className="space-y-2">
|
|
150
|
-
<div className="font-semibold">Persona strengths</div>
|
|
151
|
-
{personas.length === 0 ? <div className="text-xs text-slate-400">None set yet.</div> : null}
|
|
152
|
-
{personas.map((persona) => (
|
|
153
|
-
<div key={persona} className="flex items-center gap-3">
|
|
154
|
-
<span className="w-40 truncate font-mono text-slate-800">{persona}</span>
|
|
155
|
-
<Segmented choices={STRENGTHS} value={state.personaStrengths[persona] ?? 'strong'} onPick={(s) => dispatch('applyValue', { kind: 'personaStrength', persona, value: s })} />
|
|
156
|
-
</div>
|
|
157
|
-
))}
|
|
158
|
-
{editingPersonaNew ? (
|
|
159
|
-
<Editor state={state} dispatch={dispatch} />
|
|
160
|
-
) : (
|
|
161
|
-
<button type="button" onClick={() => dispatch('beginEditFor', { kind: 'addPersona' })} className="rounded border border-dashed border-emerald-400 px-3 py-1.5 text-sm font-semibold text-emerald-700 hover:bg-emerald-50">+ add persona strength</button>
|
|
162
|
-
)}
|
|
163
|
-
</div>
|
|
164
118
|
</div>
|
|
165
119
|
</div>
|
|
166
120
|
);
|