cbrowser 18.78.0 → 18.80.0
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/analysis/accessibility-empathy.d.ts.map +1 -1
- package/dist/analysis/accessibility-empathy.js +395 -66
- package/dist/analysis/accessibility-empathy.js.map +1 -1
- package/dist/analysis/agent-ready-audit.d.ts.map +1 -1
- package/dist/analysis/agent-ready-audit.js +78 -0
- package/dist/analysis/agent-ready-audit.js.map +1 -1
- package/dist/analysis/page-understanding.d.ts +139 -1
- package/dist/analysis/page-understanding.d.ts.map +1 -1
- package/dist/analysis/page-understanding.js +179 -12
- package/dist/analysis/page-understanding.js.map +1 -1
- package/dist/artifact-store.d.ts +19 -5
- package/dist/artifact-store.d.ts.map +1 -1
- package/dist/artifact-store.js +41 -11
- package/dist/artifact-store.js.map +1 -1
- package/dist/browser.d.ts +9 -0
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +81 -5
- package/dist/browser.js.map +1 -1
- package/dist/cli.js +55 -10
- package/dist/cli.js.map +1 -1
- package/dist/cognitive/index.d.ts.map +1 -1
- package/dist/cognitive/index.js +3 -2
- package/dist/cognitive/index.js.map +1 -1
- package/dist/config.d.ts +57 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +234 -7
- package/dist/config.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server-remote.d.ts.map +1 -1
- package/dist/mcp-server-remote.js +108 -4
- package/dist/mcp-server-remote.js.map +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +62 -938
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools/account-personas.d.ts +41 -0
- package/dist/mcp-tools/account-personas.d.ts.map +1 -0
- package/dist/mcp-tools/account-personas.js +98 -0
- package/dist/mcp-tools/account-personas.js.map +1 -0
- package/dist/mcp-tools/base/analysis-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/analysis-tools.js +12 -2
- package/dist/mcp-tools/base/analysis-tools.js.map +1 -1
- package/dist/mcp-tools/base/artifact-tools.d.ts +3 -0
- package/dist/mcp-tools/base/artifact-tools.d.ts.map +1 -0
- package/dist/mcp-tools/base/artifact-tools.js +78 -0
- package/dist/mcp-tools/base/artifact-tools.js.map +1 -0
- package/dist/mcp-tools/base/audit-tools.d.ts +11 -0
- package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/audit-tools.js +595 -340
- package/dist/mcp-tools/base/audit-tools.js.map +1 -1
- package/dist/mcp-tools/base/browser-management-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/browser-management-tools.js +18 -0
- package/dist/mcp-tools/base/browser-management-tools.js.map +1 -1
- package/dist/mcp-tools/base/bug-analysis-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/bug-analysis-tools.js +59 -3
- package/dist/mcp-tools/base/bug-analysis-tools.js.map +1 -1
- package/dist/mcp-tools/base/capture-tools.d.ts +37 -0
- package/dist/mcp-tools/base/capture-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/capture-tools.js +267 -6
- package/dist/mcp-tools/base/capture-tools.js.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.d.ts +12 -0
- package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/cognitive-tools.js +63 -15
- package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
- package/dist/mcp-tools/base/extraction-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/extraction-tools.js +33 -2
- package/dist/mcp-tools/base/extraction-tools.js.map +1 -1
- package/dist/mcp-tools/base/index.d.ts +2 -0
- package/dist/mcp-tools/base/index.d.ts.map +1 -1
- package/dist/mcp-tools/base/index.js +9 -0
- package/dist/mcp-tools/base/index.js.map +1 -1
- package/dist/mcp-tools/base/marketing-tools.js +3 -3
- package/dist/mcp-tools/base/marketing-tools.js.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/persona-comparison-tools.js +529 -40
- package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
- package/dist/mcp-tools/base/site-knowledge-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/site-knowledge-tools.js +36 -3
- package/dist/mcp-tools/base/site-knowledge-tools.js.map +1 -1
- package/dist/mcp-tools/base/values-tools.d.ts +113 -0
- package/dist/mcp-tools/base/values-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/values-tools.js +868 -78
- package/dist/mcp-tools/base/values-tools.js.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
- package/dist/mcp-tools/base/visual-testing-tools.js +130 -10
- package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
- package/dist/mcp-tools/index.d.ts +3 -1
- package/dist/mcp-tools/index.d.ts.map +1 -1
- package/dist/mcp-tools/index.js +3 -28
- package/dist/mcp-tools/index.js.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.d.ts.map +1 -1
- package/dist/mcp-tools/persona-creation-tools.js +363 -20
- package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
- package/dist/mcp-tools/persona-lifecycle-tools.d.ts +3 -0
- package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -0
- package/dist/mcp-tools/persona-lifecycle-tools.js +406 -0
- package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -0
- package/dist/mcp-tools/screenshot-utils.d.ts.map +1 -1
- package/dist/mcp-tools/screenshot-utils.js +56 -10
- package/dist/mcp-tools/screenshot-utils.js.map +1 -1
- package/dist/mcp-tools/tier-gate.js +2 -2
- package/dist/mcp-tools/tier-gate.js.map +1 -1
- package/dist/mcp-tools/tool-categories.d.ts.map +1 -1
- package/dist/mcp-tools/tool-categories.js +4 -1
- package/dist/mcp-tools/tool-categories.js.map +1 -1
- package/dist/mcp-tools/ui-resources.d.ts +167 -0
- package/dist/mcp-tools/ui-resources.d.ts.map +1 -0
- package/dist/mcp-tools/ui-resources.js +707 -0
- package/dist/mcp-tools/ui-resources.js.map +1 -0
- package/dist/mcp-tools/widget-kit.d.ts +299 -0
- package/dist/mcp-tools/widget-kit.d.ts.map +1 -0
- package/dist/mcp-tools/widget-kit.js +2034 -0
- package/dist/mcp-tools/widget-kit.js.map +1 -0
- package/dist/mcp-ui-resources.d.ts.map +1 -1
- package/dist/mcp-ui-resources.js +14 -4
- package/dist/mcp-ui-resources.js.map +1 -1
- package/dist/persona-questionnaire.d.ts +69 -1
- package/dist/persona-questionnaire.d.ts.map +1 -1
- package/dist/persona-questionnaire.js +177 -76
- package/dist/persona-questionnaire.js.map +1 -1
- package/dist/persona-scope.d.ts +20 -0
- package/dist/persona-scope.d.ts.map +1 -0
- package/dist/persona-scope.js +59 -0
- package/dist/persona-scope.js.map +1 -0
- package/dist/personas.d.ts +51 -17
- package/dist/personas.d.ts.map +1 -1
- package/dist/personas.js +947 -286
- package/dist/personas.js.map +1 -1
- package/dist/recording/capture-player.d.ts +68 -0
- package/dist/recording/capture-player.d.ts.map +1 -0
- package/dist/recording/capture-player.js +298 -0
- package/dist/recording/capture-player.js.map +1 -0
- package/dist/recording/engine.d.ts +52 -0
- package/dist/recording/engine.d.ts.map +1 -1
- package/dist/recording/engine.js +241 -3
- package/dist/recording/engine.js.map +1 -1
- package/dist/recording/inline-player.d.ts +39 -0
- package/dist/recording/inline-player.d.ts.map +1 -0
- package/dist/recording/inline-player.js +272 -0
- package/dist/recording/inline-player.js.map +1 -0
- package/dist/recording/saliency-overlay.d.ts +133 -0
- package/dist/recording/saliency-overlay.d.ts.map +1 -0
- package/dist/recording/saliency-overlay.js +360 -0
- package/dist/recording/saliency-overlay.js.map +1 -0
- package/dist/recording/types.d.ts +32 -0
- package/dist/recording/types.d.ts.map +1 -1
- package/dist/recording/types.js +46 -0
- package/dist/recording/types.js.map +1 -1
- package/dist/site-model/manager.d.ts +20 -0
- package/dist/site-model/manager.d.ts.map +1 -1
- package/dist/site-model/manager.js +52 -0
- package/dist/site-model/manager.js.map +1 -1
- package/dist/trait-reference.d.ts +26 -2
- package/dist/trait-reference.d.ts.map +1 -1
- package/dist/trait-reference.js +129 -19
- package/dist/trait-reference.js.map +1 -1
- package/dist/types.d.ts +105 -18
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/url-resolution.d.ts +58 -0
- package/dist/url-resolution.d.ts.map +1 -0
- package/dist/url-resolution.js +143 -0
- package/dist/url-resolution.js.map +1 -0
- package/dist/values/big-five-inference.d.ts +121 -0
- package/dist/values/big-five-inference.d.ts.map +1 -0
- package/dist/values/big-five-inference.js +241 -0
- package/dist/values/big-five-inference.js.map +1 -0
- package/dist/values/big-five-values.d.ts +76 -0
- package/dist/values/big-five-values.d.ts.map +1 -0
- package/dist/values/big-five-values.js +152 -0
- package/dist/values/big-five-values.js.map +1 -0
- package/dist/values/index.d.ts +1 -1
- package/dist/values/index.d.ts.map +1 -1
- package/dist/values/index.js +1 -1
- package/dist/values/index.js.map +1 -1
- package/dist/values/persona-completeness.d.ts +91 -0
- package/dist/values/persona-completeness.d.ts.map +1 -0
- package/dist/values/persona-completeness.js +114 -0
- package/dist/values/persona-completeness.js.map +1 -0
- package/dist/values/persona-diff.d.ts +50 -0
- package/dist/values/persona-diff.d.ts.map +1 -0
- package/dist/values/persona-diff.js +126 -0
- package/dist/values/persona-diff.js.map +1 -0
- package/dist/values/persona-values.d.ts +16 -0
- package/dist/values/persona-values.d.ts.map +1 -1
- package/dist/values/persona-values.js +33 -0
- package/dist/values/persona-values.js.map +1 -1
- package/dist/values/schwartz-values.d.ts +9 -1
- package/dist/values/schwartz-values.d.ts.map +1 -1
- package/dist/values/schwartz-values.js +10 -2
- package/dist/values/schwartz-values.js.map +1 -1
- package/dist/values/value-mappings.d.ts +41 -2
- package/dist/values/value-mappings.d.ts.map +1 -1
- package/dist/values/value-mappings.js +65 -8
- package/dist/values/value-mappings.js.map +1 -1
- package/dist/visual/attention-quality.d.ts +27 -0
- package/dist/visual/attention-quality.d.ts.map +1 -1
- package/dist/visual/attention-quality.js +164 -2
- package/dist/visual/attention-quality.js.map +1 -1
- package/dist/visual/attention-transport.d.ts +41 -2
- package/dist/visual/attention-transport.d.ts.map +1 -1
- package/dist/visual/attention-transport.js +98 -26
- package/dist/visual/attention-transport.js.map +1 -1
- package/dist/visual/barrier-overlay-html.d.ts +10 -0
- package/dist/visual/barrier-overlay-html.d.ts.map +1 -1
- package/dist/visual/barrier-overlay-html.js.map +1 -1
- package/dist/visual/cognitive-models.d.ts +109 -13
- package/dist/visual/cognitive-models.d.ts.map +1 -1
- package/dist/visual/cognitive-models.js +166 -3
- package/dist/visual/cognitive-models.js.map +1 -1
- package/dist/visual/cognitive-transport-chain.d.ts +149 -5
- package/dist/visual/cognitive-transport-chain.d.ts.map +1 -1
- package/dist/visual/cognitive-transport-chain.js +558 -30
- package/dist/visual/cognitive-transport-chain.js.map +1 -1
- package/dist/visual/cognitive-transport.d.ts +45 -3
- package/dist/visual/cognitive-transport.d.ts.map +1 -1
- package/dist/visual/cognitive-transport.js +126 -12
- package/dist/visual/cognitive-transport.js.map +1 -1
- package/dist/visual/fixation-corpus.d.ts +52 -0
- package/dist/visual/fixation-corpus.d.ts.map +1 -1
- package/dist/visual/fixation-corpus.js +95 -0
- package/dist/visual/fixation-corpus.js.map +1 -1
- package/dist/visual/llm-relevance.d.ts +193 -0
- package/dist/visual/llm-relevance.d.ts.map +1 -0
- package/dist/visual/llm-relevance.js +480 -0
- package/dist/visual/llm-relevance.js.map +1 -0
- package/dist/visual/perceptual-transport.d.ts +60 -0
- package/dist/visual/perceptual-transport.d.ts.map +1 -1
- package/dist/visual/perceptual-transport.js +228 -19
- package/dist/visual/perceptual-transport.js.map +1 -1
- package/dist/visual/persona-context.d.ts +23 -0
- package/dist/visual/persona-context.d.ts.map +1 -0
- package/dist/visual/persona-context.js +44 -0
- package/dist/visual/persona-context.js.map +1 -0
- package/package.json +2 -1
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
* @license MIT
|
|
15
15
|
*/
|
|
16
16
|
import { z } from "zod";
|
|
17
|
-
import { generatePersonaQuestionnaire, buildTraitsFromAnswers, deriveValuesFromTraits, TRAIT_REFERENCE_MATRIX, } from "../persona-questionnaire.js";
|
|
17
|
+
import { generatePersonaQuestionnaire, buildTraitsFromAnswers, buildBigFiveFromAnswers, deriveValuesFromTraits, TRAIT_REFERENCE_MATRIX, } from "../persona-questionnaire.js";
|
|
18
|
+
import { bigFiveInferenceReference, validateInferredBigFive } from "../values/big-five-inference.js";
|
|
19
|
+
import { deriveValuesFromBigFive } from "../values/big-five-values.js";
|
|
20
|
+
import { TRAIT_INFERENCE_CRITERIA, ALL_TRAITS, assessTraitCompleteness } from "../values/persona-completeness.js";
|
|
21
|
+
import { summariseTraits } from "../values/persona-diff.js";
|
|
22
|
+
import { widgetUri } from "./widget-kit.js";
|
|
18
23
|
const VALUES_QUESTIONS = [
|
|
19
24
|
{
|
|
20
25
|
id: "security",
|
|
@@ -168,8 +173,7 @@ function getTraitHeader(trait) {
|
|
|
168
173
|
emotionalContagion: "Emotional",
|
|
169
174
|
fearOfMissingOut: "FOMO",
|
|
170
175
|
socialProofSensitivity: "Social Proof",
|
|
171
|
-
|
|
172
|
-
siteFamiliarity: "Site Memory",
|
|
176
|
+
mentalModelFlexibility: "Flexibility",
|
|
173
177
|
};
|
|
174
178
|
return headers[trait] || trait;
|
|
175
179
|
}
|
|
@@ -203,7 +207,7 @@ export function registerPersonaCreationTools(server) {
|
|
|
203
207
|
description: "Start creating a custom persona. Best practice: present the choice to the user via AskUserQuestion (questionnaire vs description mode) rather than showing raw JSON.",
|
|
204
208
|
inputSchema: {
|
|
205
209
|
persona_name: z.string().describe("Name for the new persona (e.g., 'tech-savvy-millennial')"),
|
|
206
|
-
comprehensive: z.boolean().optional().describe("Include all
|
|
210
|
+
comprehensive: z.boolean().optional().describe("Include all 26 traits (true) or just core 8 traits (false, default)"),
|
|
207
211
|
},
|
|
208
212
|
annotations: {
|
|
209
213
|
title: "Start Persona Creation",
|
|
@@ -259,7 +263,7 @@ IMPORTANT: Do NOT show this text to the user. USE AskUserQuestion to present the
|
|
|
259
263
|
description: "Start the questionnaire mode for persona creation. Each question is best presented via AskUserQuestion for interactive selection.",
|
|
260
264
|
inputSchema: {
|
|
261
265
|
persona_name: z.string().describe("Name for the new persona"),
|
|
262
|
-
comprehensive: z.boolean().optional().describe("Include all
|
|
266
|
+
comprehensive: z.boolean().optional().describe("Include all 26 traits (default: false, core 8 traits only)"),
|
|
263
267
|
},
|
|
264
268
|
annotations: {
|
|
265
269
|
title: "Start Persona Questionnaire",
|
|
@@ -296,7 +300,7 @@ IMPORTANT: Do NOT show this text to the user. USE AskUserQuestion to present the
|
|
|
296
300
|
Question 1 of ${totalQuestions} (Trait: ${firstQuestion.trait})
|
|
297
301
|
"${thirdPersonQuestion}"
|
|
298
302
|
|
|
299
|
-
Header: "${getTraitHeader(firstQuestion.trait)}"
|
|
303
|
+
Header: "${firstQuestion.trait ? getTraitHeader(firstQuestion.trait) : firstQuestion.id}"
|
|
300
304
|
|
|
301
305
|
Options:
|
|
302
306
|
${firstQuestion.options.map((o, i) => `${i + 1}. "${o.label}" - ${o.description}`).join("\n")}
|
|
@@ -338,8 +342,10 @@ IMPORTANT: Use AskUserQuestion - do NOT just display this text.`,
|
|
|
338
342
|
const now = Date.now();
|
|
339
343
|
if (session.phase === "traits") {
|
|
340
344
|
const currentQuestion = session.questions[session.currentIndex];
|
|
341
|
-
|
|
342
|
-
|
|
345
|
+
// Keyed by id so Big Five items, which carry no trait, land somewhere
|
|
346
|
+
// real. For a trait question id and trait are the same string.
|
|
347
|
+
session.answers[currentQuestion.id] = answer_value;
|
|
348
|
+
session.answerMetadata[currentQuestion.id] = {
|
|
343
349
|
value: answer_value,
|
|
344
350
|
answeredAt: now,
|
|
345
351
|
questionIndex: session.currentIndex,
|
|
@@ -366,6 +372,13 @@ IMPORTANT: Use AskUserQuestion - do NOT just display this text.`,
|
|
|
366
372
|
// Check if completely done
|
|
367
373
|
if (session.phase === "values" && session.currentIndex >= session.valueQuestions.length) {
|
|
368
374
|
const traits = buildTraitsFromAnswers(session.answers);
|
|
375
|
+
// Big Five answers, if the questionnaire included them. Kept on the
|
|
376
|
+
// persona so anything deriving values later has the layer the research
|
|
377
|
+
// actually relates to values, rather than re-deriving from cognitive
|
|
378
|
+
// traits and crossing a gap the literature does not cover. Without
|
|
379
|
+
// this the five items would have been asked and thrown away.
|
|
380
|
+
// (2026-08-01)
|
|
381
|
+
const bigFive = buildBigFiveFromAnswers(session.answers);
|
|
369
382
|
const derivedResult = deriveValuesFromTraits(traits);
|
|
370
383
|
const derivedValues = derivedResult.values;
|
|
371
384
|
const values = {
|
|
@@ -389,6 +402,7 @@ IMPORTANT: Use AskUserQuestion - do NOT just display this text.`,
|
|
|
389
402
|
text: JSON.stringify({
|
|
390
403
|
questionnaire_complete: true,
|
|
391
404
|
persona_name: personaName,
|
|
405
|
+
...(Object.keys(bigFive).length ? { bigFive } : {}),
|
|
392
406
|
duration_seconds: Math.round(duration / 1000),
|
|
393
407
|
traits,
|
|
394
408
|
values,
|
|
@@ -413,7 +427,7 @@ IMPORTANT: Use AskUserQuestion - do NOT just display this text.`,
|
|
|
413
427
|
Question ${answeredCount + 1} of ${totalQuestions} (Trait: ${nextQuestion.trait})
|
|
414
428
|
"${thirdPersonQuestion}"
|
|
415
429
|
|
|
416
|
-
Header: "${getTraitHeader(nextQuestion.trait)}"
|
|
430
|
+
Header: "${nextQuestion.trait ? getTraitHeader(nextQuestion.trait) : nextQuestion.id}"
|
|
417
431
|
|
|
418
432
|
Options:
|
|
419
433
|
${nextQuestion.options.map((o, i) => `${i + 1}. "${o.label}" - ${o.description}`).join("\n")}
|
|
@@ -486,8 +500,19 @@ Phase: VALUES (${session.currentIndex + 1} of ${session.valueQuestions.length})`
|
|
|
486
500
|
mode: "manual_inference",
|
|
487
501
|
persona_name,
|
|
488
502
|
user_description: description,
|
|
489
|
-
instruction: `Based on the user's description, infer appropriate trait values (0.0 to 1.0) for each cognitive trait. Use the trait reference matrix below to guide your inference. If the description mentions ANY
|
|
503
|
+
instruction: `Based on the user's description, infer appropriate trait values (0.0 to 1.0) for each cognitive trait. ALSO estimate the persona's Big Five profile from the same description and return it as bigFive with bigFiveEvidence — see big_five_inference below. This is not optional decoration: without it the persona's values are derived from cognitive traits, and that route cannot reach hedonism, power, universalism or the relatedness need, so those four stay at the 0.5 baseline. Use the trait reference matrix below to guide your inference. If the description mentions ANY disability or impairment — sensory (vision loss, colour blindness, hearing loss), physical (motor impairment, tremor), OR cognitive and attentional (ADHD, dyslexia, autism, brain fog, fatigue, low processing speed) — you MUST also include accessibilityTraits. Cognitive conditions map onto attentionSpan and processingSpeed in that same block; listing only physical and sensory examples led to ADHD personas getting no accessibility modeling at all. Without accessibilityTraits, the persona gets cognitive-only modeling and the perceptual transport layer will report near-perfect perception — which is wrong for someone with a disability. Return the traits via persona_create_submit_traits.`,
|
|
490
504
|
trait_reference: traitInfo,
|
|
505
|
+
big_five_inference: bigFiveInferenceReference(),
|
|
506
|
+
// What to look for, per trait, so the caller is not guessing at
|
|
507
|
+
// what counts as evidence. The submit tool REJECTS a persona with
|
|
508
|
+
// unaccounted traits, so this is the contract rather than advice.
|
|
509
|
+
trait_criteria: TRAIT_INFERENCE_CRITERIA,
|
|
510
|
+
completeness_contract: {
|
|
511
|
+
rule: `All ${ALL_TRAITS.length} traits must be accounted for. Give each one a value inferred from the description, OR list it in unsupportedTraits.`,
|
|
512
|
+
why: "An unsupplied trait is filled from a population baseline and the result is indistinguishable from a considered value — the persona looks complete either way. Naming what the description cannot support keeps that visible.",
|
|
513
|
+
unsupportedTraits: "string[] — traits this description genuinely does not speak to. A legitimate answer, not a failure.",
|
|
514
|
+
enforcement: "persona_create_submit_traits returns an error listing the unaccounted traits and writes nothing.",
|
|
515
|
+
},
|
|
491
516
|
...(includeTraitReference ? {} : { trait_reference_note: "Compact form: trait names and meanings. For per-level behaviours call persona_traits_list, or pass includeTraitReference:true." }),
|
|
492
517
|
accessibility_traits_reference: {
|
|
493
518
|
visionLevel: "0=blind, 0.3=legally blind, 0.5=low vision (needs magnification), 0.7=mild vision loss, 1.0=sighted",
|
|
@@ -540,7 +565,23 @@ Phase: VALUES (${session.currentIndex + 1} of ${session.valueQuestions.length})`
|
|
|
540
565
|
hearingLevel: z.number().min(0).max(1).optional().describe("Hearing level: 0=deaf, 1=full hearing"),
|
|
541
566
|
}).optional().describe("Sensory/motor/physical traits for disability modeling. Without these, the persona gets cognitive-only modeling (no vision/motor simulation in empathy_audit)."),
|
|
542
567
|
ageRange: z.string().optional().describe("Age range: '18-24', '25-45', '45-65', '65+'"),
|
|
568
|
+
discardBigFive: z.boolean().optional().describe("Deliberately drop an existing Big Five profile on this persona and derive values from cognitive traits instead. Without this, an existing profile is carried forward rather than silently downgraded — omitting bigFive on a rewrite used to demote the persona from thirteen value axes to nine and say nothing."),
|
|
569
|
+
traitProvenance: z.record(z.string(), z.enum(["supported", "asserted"])).optional().describe("Per trait: 'supported' if the description says it, 'asserted' if you decided it anyway. Both are legitimate. Supplying this is what makes the trait count mean something — '26 of 26 supplied' reads as quality whether the values were read out of the description or invented, and without this field nothing can tell those apart."),
|
|
570
|
+
unsupportedTraits: z.array(z.string()).optional().describe("Traits the description genuinely does not speak to. Required for any trait you are not supplying a value for — see completeness_contract. Naming them is a valid answer; omitting them is not."),
|
|
571
|
+
bigFive: z.object({
|
|
572
|
+
openness: z.number().min(0).max(1),
|
|
573
|
+
conscientiousness: z.number().min(0).max(1),
|
|
574
|
+
extraversion: z.number().min(0).max(1),
|
|
575
|
+
agreeableness: z.number().min(0).max(1),
|
|
576
|
+
neuroticism: z.number().min(0).max(1),
|
|
577
|
+
}).optional().describe("Big Five profile INFERRED from the description. Unlocks all 13 motivational values through published correlations instead of the 9 the cognitive-trait route reaches. Requires bigFiveEvidence."),
|
|
578
|
+
bigFiveEvidence: z.array(z.object({
|
|
579
|
+
factor: z.string(),
|
|
580
|
+
score: z.number().min(0).max(1),
|
|
581
|
+
quote: z.string().describe("The words in the description justifying this score"),
|
|
582
|
+
})).optional().describe("One entry per factor, quoting the description. Required whenever bigFive is supplied — an estimate nobody can check against the source is not evidence."),
|
|
543
583
|
},
|
|
584
|
+
_meta: { ui: { resourceUri: widgetUri("persona-created") } },
|
|
544
585
|
annotations: {
|
|
545
586
|
title: "Submit Persona Traits",
|
|
546
587
|
readOnlyHint: false,
|
|
@@ -548,9 +589,86 @@ Phase: VALUES (${session.currentIndex + 1} of ${session.valueQuestions.length})`
|
|
|
548
589
|
idempotentHint: false,
|
|
549
590
|
openWorldHint: false,
|
|
550
591
|
},
|
|
551
|
-
}, async ({ persona_name, traits, description, accessibilityTraits, ageRange }) => {
|
|
592
|
+
}, async ({ persona_name, traits, description, accessibilityTraits, ageRange, bigFive, bigFiveEvidence, unsupportedTraits, traitProvenance, discardBigFive }) => {
|
|
593
|
+
// Refused, not warned.
|
|
594
|
+
//
|
|
595
|
+
// A warning is read once by a model already moving to its next call, and
|
|
596
|
+
// the persona is written regardless -- the gap surfaces months later when
|
|
597
|
+
// someone audits. Five personas on this install are missing the same four
|
|
598
|
+
// traits because one session skipped them and nothing objected. Declining
|
|
599
|
+
// the write is the only version of this that changes what gets stored.
|
|
600
|
+
const completeness = assessTraitCompleteness(traits, unsupportedTraits, traitProvenance);
|
|
601
|
+
if (!completeness.ok) {
|
|
602
|
+
return {
|
|
603
|
+
content: [{ type: "text", text: JSON.stringify({
|
|
604
|
+
error: "incomplete_persona",
|
|
605
|
+
message: completeness.message,
|
|
606
|
+
unaccountedTraits: completeness.unaccounted,
|
|
607
|
+
traitCriteria: Object.fromEntries(completeness.unaccounted.map((t) => [t, TRAIT_INFERENCE_CRITERIA[t]])),
|
|
608
|
+
howToProceed: "Re-submit with a value for each trait the description supports, and the rest listed in unsupportedTraits. Nothing was written.",
|
|
609
|
+
written: false,
|
|
610
|
+
}, null, 2) }],
|
|
611
|
+
};
|
|
612
|
+
}
|
|
552
613
|
const builtTraits = buildTraitsFromAnswers(traits);
|
|
553
|
-
|
|
614
|
+
// Big Five, when the caller inferred one from the description.
|
|
615
|
+
//
|
|
616
|
+
// Preferred over the cognitive-trait derivation because it reaches all
|
|
617
|
+
// thirteen axes through published correlations instead of nine through
|
|
618
|
+
// our own bridge — but recorded as its OWN route, not merged into stated
|
|
619
|
+
// Big Five. A model reading prose and five numbers a human supplied are
|
|
620
|
+
// different evidence, and collapsing them would erase the distinction the
|
|
621
|
+
// route ordering exists to express. Rejected rather than degraded when it
|
|
622
|
+
// fails validation: a bad profile silently replacing a working
|
|
623
|
+
// trait-derivation is worse than no profile. (2026-08-01)
|
|
624
|
+
let bigFiveAccepted;
|
|
625
|
+
let bigFiveRejected;
|
|
626
|
+
let bigFiveUnrelated;
|
|
627
|
+
if (bigFive) {
|
|
628
|
+
const verdict = validateInferredBigFive(bigFive, bigFiveEvidence, description);
|
|
629
|
+
if (verdict.ok) {
|
|
630
|
+
bigFiveAccepted = bigFive;
|
|
631
|
+
bigFiveUnrelated = verdict.unrelatedQuotes;
|
|
632
|
+
}
|
|
633
|
+
else
|
|
634
|
+
bigFiveRejected = verdict.reason;
|
|
635
|
+
}
|
|
636
|
+
// A rewrite must not silently demote a persona's values route.
|
|
637
|
+
//
|
|
638
|
+
// Submitting without bigFive is the honest answer when the description
|
|
639
|
+
// does not support one -- and on an EXISTING persona that already had a
|
|
640
|
+
// Big Five profile, it quietly rewrote the record from `big_five` (or
|
|
641
|
+
// `bigfive_inferred`, thirteen axes) down to `cognitive_traits` (nine
|
|
642
|
+
// axes, four frozen at baseline). Following the instruction correctly
|
|
643
|
+
// made the persona worse, and nothing said so. Nobody would notice until
|
|
644
|
+
// they wondered why four values had gone flat.
|
|
645
|
+
//
|
|
646
|
+
// The existing profile is CARRIED FORWARD rather than dropped, because a
|
|
647
|
+
// downgrade nobody asked for is the worse error -- and it is reported
|
|
648
|
+
// loudly, with the traits it was inferred from now changed underneath it.
|
|
649
|
+
// `discardBigFive: true` is the way to actually mean the downgrade.
|
|
650
|
+
// (2026-08-01)
|
|
651
|
+
let carriedBigFive;
|
|
652
|
+
if (!bigFiveAccepted && !discardBigFive) {
|
|
653
|
+
try {
|
|
654
|
+
const { loadCustomPersonas } = await import("../personas.js");
|
|
655
|
+
const prior = loadCustomPersonas()[persona_name];
|
|
656
|
+
const priorDeriv = prior?.valuesDerivation;
|
|
657
|
+
const priorMethod = priorDeriv?.method;
|
|
658
|
+
const priorBigFive = prior?.bigFive;
|
|
659
|
+
if (priorBigFive && (priorMethod === "big_five" || priorMethod === "bigfive_inferred")) {
|
|
660
|
+
bigFiveAccepted = priorBigFive;
|
|
661
|
+
carriedBigFive = {
|
|
662
|
+
from: priorMethod,
|
|
663
|
+
note: `This persona already had a ${priorMethod} profile and this submission supplied none, which would have downgraded it to cognitive_traits — nine of thirteen value axes, with hedonism, power, universalism and the relatedness need frozen at 0.5. The existing profile was kept. It was inferred against the PREVIOUS description and traits, so if the persona has genuinely changed, re-supply bigFive with evidence from the new description. To downgrade on purpose, re-submit with discardBigFive:true.`,
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
catch { /* no prior persona; nothing to carry */ }
|
|
668
|
+
}
|
|
669
|
+
const derivedResult = bigFiveAccepted
|
|
670
|
+
? deriveValuesFromBigFive(bigFiveAccepted)
|
|
671
|
+
: deriveValuesFromTraits(builtTraits);
|
|
554
672
|
// buildTraitsFromAnswers fills every unsupplied trait from research
|
|
555
673
|
// baselines, so a caller who names 3 traits gets back a complete 26-trait
|
|
556
674
|
// persona with no way to tell which 23 were invented. Report both lists.
|
|
@@ -564,24 +682,57 @@ Phase: VALUES (${session.currentIndex + 1} of ${session.valueQuestions.length})`
|
|
|
564
682
|
const personaObj = createCognitivePersona(persona_name, description || persona_name, builtTraits, { ageRange: ageRange || undefined });
|
|
565
683
|
// Attach accessibility traits — either from explicit parameter or inferred from name/description
|
|
566
684
|
let resolvedAccessibilityTraits = accessibilityTraits;
|
|
685
|
+
// Which keywords fired, so an inferred number can be argued with rather
|
|
686
|
+
// than just noticed.
|
|
687
|
+
let inferredFrom = [];
|
|
567
688
|
if (!resolvedAccessibilityTraits || Object.keys(resolvedAccessibilityTraits).length === 0) {
|
|
568
689
|
// Infer disability traits from persona name and description
|
|
569
690
|
const text = `${persona_name} ${description || ''}`.toLowerCase();
|
|
570
691
|
const hasVision = /vision|blind|magnif|low.?vis|sight|visual.?impair/.test(text);
|
|
571
692
|
const hasMotor = /motor|tremor|parkinson|arthrit|dexterity|mobility/.test(text);
|
|
572
|
-
const hasCognitive = /adhd|dyslexic|dyslexia|cognitive|autism|intellectual/.test(text);
|
|
573
693
|
const hasHearing = /deaf|hearing|hard.?of.?hearing/.test(text);
|
|
574
694
|
const hasElderly = /elderly|senior|65|70|75|80|retired|aging|aged/.test(text);
|
|
695
|
+
// Cognitive conditions are NOT one bucket, and treating them as one
|
|
696
|
+
// produced a wrong number with a confident label. ADHD, dyslexia,
|
|
697
|
+
// autism and intellectual disability all matched a single regex and all
|
|
698
|
+
// came out as processingSpeed 0.4 / attentionSpan 0.3 -- so an ADHD
|
|
699
|
+
// persona whose previous record said processingSpeed 0.9 was rewritten
|
|
700
|
+
// to 0.4 by a keyword. ADHD is an attention-regulation condition; it is
|
|
701
|
+
// not a claim about how fast someone processes information, and plenty
|
|
702
|
+
// of people with ADHD process quickly. Autism is not a speed claim
|
|
703
|
+
// either. Dyslexia slows READING specifically. Only the conditions that
|
|
704
|
+
// genuinely bear on speed set speed. (2026-08-01)
|
|
705
|
+
const hasAdhd = /adhd|add\b|attention deficit|executive function/.test(text);
|
|
706
|
+
const hasDyslexia = /dyslexi/.test(text);
|
|
707
|
+
const hasAutism = /autis|asperger|neurodiver/.test(text);
|
|
708
|
+
const hasSlowProcessing = /intellectual disab|brain fog|processing (speed )?(disorder|difficult|impair)|slow (to )?process|cognitive impair|dementia|traumatic brain/.test(text);
|
|
709
|
+
const hasCognitive = hasAdhd || hasDyslexia || hasAutism || hasSlowProcessing;
|
|
575
710
|
if (hasVision || hasMotor || hasCognitive || hasHearing || hasElderly) {
|
|
711
|
+
// Left undefined where the description supports no claim, so the
|
|
712
|
+
// block below falls back to the persona's own traits rather than to a
|
|
713
|
+
// number this inference invented.
|
|
714
|
+
const speed = hasSlowProcessing ? 0.35
|
|
715
|
+
: hasDyslexia ? 0.6
|
|
716
|
+
: hasElderly ? 0.5
|
|
717
|
+
: undefined;
|
|
718
|
+
const attention = hasAdhd ? 0.3
|
|
719
|
+
: hasAutism ? undefined
|
|
720
|
+
: undefined;
|
|
576
721
|
resolvedAccessibilityTraits = {
|
|
577
722
|
visionLevel: hasVision ? 0.4 : hasElderly ? 0.6 : 1.0,
|
|
578
723
|
contrastSensitivity: hasVision ? 0.3 : hasElderly ? 0.5 : 1.0,
|
|
579
724
|
motorControl: hasMotor ? 0.3 : hasElderly ? 0.6 : 1.0,
|
|
580
725
|
tremor: hasMotor,
|
|
581
|
-
|
|
582
|
-
|
|
726
|
+
...(speed !== undefined ? { processingSpeed: speed } : {}),
|
|
727
|
+
...(attention !== undefined ? { attentionSpan: attention } : {}),
|
|
583
728
|
hearingLevel: hasHearing ? 0.2 : 1.0,
|
|
584
729
|
};
|
|
730
|
+
inferredFrom = [
|
|
731
|
+
hasVision && "vision", hasMotor && "motor", hasHearing && "hearing", hasElderly && "age",
|
|
732
|
+
hasAdhd && "ADHD (attention only — not a processing-speed claim)",
|
|
733
|
+
hasDyslexia && "dyslexia (reading speed)", hasAutism && "autism (no speed or attention claim inferred)",
|
|
734
|
+
hasSlowProcessing && "processing-speed condition",
|
|
735
|
+
].filter(Boolean);
|
|
585
736
|
}
|
|
586
737
|
}
|
|
587
738
|
if (resolvedAccessibilityTraits && Object.keys(resolvedAccessibilityTraits).length > 0) {
|
|
@@ -597,9 +748,53 @@ Phase: VALUES (${session.currentIndex + 1} of ${session.valueQuestions.length})`
|
|
|
597
748
|
fatigueSusceptibility: (resolvedAccessibilityTraits.visionLevel ?? 1.0) < 0.7 || (resolvedAccessibilityTraits.motorControl ?? 1.0) < 0.7 ? 0.7 : 0.3,
|
|
598
749
|
};
|
|
599
750
|
}
|
|
751
|
+
// The route that produced the values, recorded ON the persona so every
|
|
752
|
+
// reader gets the same answer. bigFive is stored too, so the derivation
|
|
753
|
+
// can be re-run and checked rather than taken on trust.
|
|
754
|
+
personaObj.valuesDerivation = bigFiveAccepted
|
|
755
|
+
? { method: "bigfive_inferred", squash: "tanh", precision: 3,
|
|
756
|
+
inferredFrom: "description", evidence: bigFiveEvidence ?? [] }
|
|
757
|
+
: { method: "cognitive_traits", squash: "tanh", precision: 3 };
|
|
758
|
+
if (bigFiveAccepted) {
|
|
759
|
+
personaObj.bigFive = bigFiveAccepted;
|
|
760
|
+
}
|
|
761
|
+
personaObj.schwartzValues = derivedResult.values;
|
|
762
|
+
// Kept ON the persona: which traits are baseline-by-declaration rather
|
|
763
|
+
// than by omission. Without it the distinction is lost the moment the
|
|
764
|
+
// response is discarded.
|
|
765
|
+
if (unsupportedTraits?.length) {
|
|
766
|
+
personaObj.unsupportedTraits = unsupportedTraits;
|
|
767
|
+
}
|
|
768
|
+
// The attention pattern createCognitivePersona stamps from its template is
|
|
769
|
+
// NOT a declaration.
|
|
770
|
+
//
|
|
771
|
+
// A recreate flipped a stored persona from "thorough" to "skim" while still
|
|
772
|
+
// reporting attentionPatternSource: "declared" -- nobody declared either
|
|
773
|
+
// value. The template's default arrived through humanBehavior, which is
|
|
774
|
+
// exactly where a real declaration lives, so the profile could not tell
|
|
775
|
+
// them apart. Dropping it lets the derivation run and report itself as
|
|
776
|
+
// derived; a caller who wants a declaration sets it through
|
|
777
|
+
// persona_update, which is the surface that takes one. (2026-08-01)
|
|
778
|
+
const hb = personaObj.humanBehavior;
|
|
779
|
+
if (hb?.attention && "pattern" in hb.attention)
|
|
780
|
+
delete hb.attention.pattern;
|
|
600
781
|
registerPersonas([personaObj]);
|
|
782
|
+
// Persist to disk. registerPersonas writes to an in-memory map that dies
|
|
783
|
+
// with the process, so a creation whose CMS write also failed left the
|
|
784
|
+
// persona existing NOWHERE -- while the response said registered: true.
|
|
785
|
+
// "Registered" described RAM and read as "saved". (2026-08-01)
|
|
786
|
+
let savedToFile;
|
|
787
|
+
let fileSaveError;
|
|
788
|
+
try {
|
|
789
|
+
const { saveCustomPersona } = await import("../personas.js");
|
|
790
|
+
savedToFile = saveCustomPersona(personaObj);
|
|
791
|
+
}
|
|
792
|
+
catch (e) {
|
|
793
|
+
fileSaveError = e?.message ?? String(e);
|
|
794
|
+
}
|
|
601
795
|
// Also save to CMS if an API key is configured (makes it persistent across sessions)
|
|
602
796
|
let savedToCms = false;
|
|
797
|
+
let cmsError;
|
|
603
798
|
try {
|
|
604
799
|
const { getSessionApiKey } = await import("./base/cognitive-tools.js");
|
|
605
800
|
const apiKey = getSessionApiKey();
|
|
@@ -618,6 +813,13 @@ Phase: VALUES (${session.currentIndex + 1} of ${session.valueQuestions.length})`
|
|
|
618
813
|
accessibility_traits: resolvedAccessibilityTraits || null,
|
|
619
814
|
age_range: ageRange || personaObj.demographics?.age_range || null,
|
|
620
815
|
source: "mcp",
|
|
816
|
+
// Sent so the row is not re-labelled by guesswork later. A stored
|
|
817
|
+
// block with no provenance reads as `stated`, which would claim a
|
|
818
|
+
// human wrote numbers this route inferred.
|
|
819
|
+
values_derivation: bigFiveAccepted
|
|
820
|
+
? { method: "bigfive_inferred", squash: "tanh", precision: 3,
|
|
821
|
+
inferredFrom: "description", evidence: bigFiveEvidence ?? [] }
|
|
822
|
+
: { method: "cognitive_traits", squash: "tanh", precision: 3 },
|
|
621
823
|
}),
|
|
622
824
|
});
|
|
623
825
|
if (res.ok) {
|
|
@@ -626,10 +828,20 @@ Phase: VALUES (${session.currentIndex + 1} of ${session.valueQuestions.length})`
|
|
|
626
828
|
}
|
|
627
829
|
else {
|
|
628
830
|
const errBody = await res.text().catch(() => "");
|
|
831
|
+
// The REASON travels to the caller. "savedToCms: false" alone does
|
|
832
|
+
// not distinguish an unreachable CMS from a credential the CMS
|
|
833
|
+
// will never accept -- and the second is the actual situation on
|
|
834
|
+
// an OAuth connector, where the bearer forwarded here is an OAuth
|
|
835
|
+
// token and /api/personas only accepts cbk_ account keys. Those
|
|
836
|
+
// need different responses from whoever reads this.
|
|
837
|
+
cmsError = res.status === 401 || res.status === 403
|
|
838
|
+
? `CMS rejected the credential (${res.status}). The session bearer is forwarded as-is, so an OAuth connector token reaches an endpoint that only accepts cbk_ account keys. Set an account key with set_api_key to make CMS writes succeed.`
|
|
839
|
+
: `CMS returned ${res.status}: ${errBody.slice(0, 120)}`;
|
|
629
840
|
console.warn(`[persona] CMS save failed (${res.status}): ${errBody.slice(0, 100)}`);
|
|
630
841
|
}
|
|
631
842
|
}
|
|
632
843
|
else {
|
|
844
|
+
cmsError = "no API key in this session — nothing was sent to the CMS";
|
|
633
845
|
console.warn(`[persona] No API key in session — cannot save to CMS`);
|
|
634
846
|
}
|
|
635
847
|
}
|
|
@@ -640,13 +852,42 @@ Phase: VALUES (${session.currentIndex + 1} of ${session.valueQuestions.length})`
|
|
|
640
852
|
content: [{
|
|
641
853
|
type: "text",
|
|
642
854
|
text: JSON.stringify({
|
|
643
|
-
success
|
|
855
|
+
// success is computed below from what actually persisted.
|
|
644
856
|
persona_name,
|
|
645
857
|
description,
|
|
646
858
|
traits: builtTraits,
|
|
647
859
|
providedTraits,
|
|
648
860
|
defaultedTraits,
|
|
649
|
-
|
|
861
|
+
// The old line was `${n} of ${n} supplied; 0 filled from research
|
|
862
|
+
// baselines`, and it read as a quality signal no matter how the
|
|
863
|
+
// numbers were arrived at -- a caller who invented six of them got
|
|
864
|
+
// the same clean sentence as one who read all 26 out of the text.
|
|
865
|
+
// The count measures ACCOUNTING. Whether the description supports a
|
|
866
|
+
// value is a different question, and the sentence has to stop
|
|
867
|
+
// answering it by implication.
|
|
868
|
+
traitCompleteness: (() => {
|
|
869
|
+
const base = `${providedTraits.length} of ${allTraitNames.length} supplied; ${defaultedTraits.length} filled from research baselines`;
|
|
870
|
+
const a = completeness.asserted?.length ?? 0;
|
|
871
|
+
const u = completeness.unlabelled?.length ?? 0;
|
|
872
|
+
if (a || u) {
|
|
873
|
+
const parts = [
|
|
874
|
+
a ? `${a} asserted by the caller rather than read from the description` : "",
|
|
875
|
+
u ? `${u} with no provenance stated` : "",
|
|
876
|
+
].filter(Boolean);
|
|
877
|
+
return `${base}. ${parts.join("; ")}. This counts accounting, not support — a supplied value can still be an invention.`;
|
|
878
|
+
}
|
|
879
|
+
return `${base}; all supplied values marked as supported by the description`;
|
|
880
|
+
})(),
|
|
881
|
+
...(completeness.asserted?.length ? { assertedTraits: completeness.asserted } : {}),
|
|
882
|
+
...(completeness.unlabelled?.length ? {
|
|
883
|
+
unlabelledTraits: completeness.unlabelled,
|
|
884
|
+
provenanceNote: "These were supplied with no traitProvenance entry, so whether the description supports them is unknown — not assumed. Pass traitProvenance to say which values you read out of the text and which you decided.",
|
|
885
|
+
} : {}),
|
|
886
|
+
...(carriedBigFive ? { bigFiveCarriedForward: carriedBigFive } : {}),
|
|
887
|
+
...(bigFiveUnrelated?.length ? {
|
|
888
|
+
bigFiveQuotesUnrelated: bigFiveUnrelated,
|
|
889
|
+
bigFiveQuoteNote: "These quotes are in the description but contain no word bearing on the factor they were cited for. The profile was accepted — the check is a heuristic — but a quote that is not about the factor is not evidence for it.",
|
|
890
|
+
} : {}),
|
|
650
891
|
// A misspelled trait name was previously accepted in silence and had
|
|
651
892
|
// no effect, since buildTraitsFromAnswers only applies keys already
|
|
652
893
|
// present in the trait table.
|
|
@@ -655,15 +896,117 @@ Phase: VALUES (${session.currentIndex + 1} of ${session.valueQuestions.length})`
|
|
|
655
896
|
warning: `Ignored ${unknownTraits.length} unrecognized trait name(s): ${unknownTraits.join(", ")}. These had no effect.`,
|
|
656
897
|
} : {}),
|
|
657
898
|
values: derivedResult.values,
|
|
658
|
-
|
|
899
|
+
// The two routes report their working differently -- the trait
|
|
900
|
+
// derivation lists `derivations`, the Big Five one `contributions`.
|
|
901
|
+
// Both are surfaced under their own name so a caller can tell which
|
|
902
|
+
// route ran from the response alone, without inferring it from
|
|
903
|
+
// which key happens to be present.
|
|
904
|
+
...("derivations" in derivedResult
|
|
905
|
+
? { derivations: derivedResult.derivations }
|
|
906
|
+
: {}),
|
|
907
|
+
...("contributions" in derivedResult
|
|
908
|
+
? { bigFiveContributions: derivedResult.contributions,
|
|
909
|
+
hypothesisAxes: derivedResult.hypothesisAxes }
|
|
910
|
+
: {}),
|
|
911
|
+
valuesRoute: bigFiveAccepted ? "bigfive_inferred" : "cognitive_traits",
|
|
912
|
+
traitSummary: summariseTraits(builtTraits),
|
|
913
|
+
// Named at creation, which is the only moment the distinction is
|
|
914
|
+
// free: a declared-unsupported trait sits at a population baseline
|
|
915
|
+
// and is indistinguishable from a considered value in every later
|
|
916
|
+
// view of this persona.
|
|
917
|
+
...(unsupportedTraits?.length ? {
|
|
918
|
+
unsupportedTraits,
|
|
919
|
+
unsupportedDetail: unsupportedTraits.map((t) => ({
|
|
920
|
+
trait: t, value: builtTraits[t],
|
|
921
|
+
source: "population baseline — the description did not speak to this",
|
|
922
|
+
})),
|
|
923
|
+
} : {}),
|
|
924
|
+
...(bigFiveAccepted ? { bigFive: bigFiveAccepted, bigFiveEvidence: bigFiveEvidence ?? [] } : {}),
|
|
925
|
+
// A rejected profile is REPORTED, never swallowed. Silently falling
|
|
926
|
+
// back would leave the caller believing the Big Five route ran.
|
|
927
|
+
...(bigFiveRejected ? {
|
|
928
|
+
bigFiveRejected,
|
|
929
|
+
bigFiveRejectedNote: "The inferred profile was not stored and values were derived from cognitive traits instead, which cannot reach hedonism, power, universalism or the relatedness need. Fix the profile and resubmit to reach all thirteen.",
|
|
930
|
+
} : {}),
|
|
659
931
|
accessibilityTraits: resolvedAccessibilityTraits || null,
|
|
660
|
-
|
|
932
|
+
// Presence of the block is not impairment. A persona with vision,
|
|
933
|
+
// motor and hearing all at 1.0 has accessibility traits and no
|
|
934
|
+
// disability, and flagging it flipped an instruction claiming the
|
|
935
|
+
// perceptual layer "will simulate sensory impairment" -- a false
|
|
936
|
+
// statement about what the run does. ADHD is cognitive; supplying
|
|
937
|
+
// ceiling sensory values must not read as a sensory disability.
|
|
938
|
+
hasDisabilityModeling: (() => {
|
|
939
|
+
const a = resolvedAccessibilityTraits;
|
|
940
|
+
if (!a)
|
|
941
|
+
return false;
|
|
942
|
+
const below = (k, t = 0.95) => typeof a[k] === "number" && a[k] < t;
|
|
943
|
+
return below("visionLevel") || below("motorControl") || below("hearingLevel")
|
|
944
|
+
|| below("contrastSensitivity") || below("processingSpeed", 0.6)
|
|
945
|
+
|| below("attentionSpan", 0.6) || a.tremor === true || !!a.colorBlindness;
|
|
946
|
+
})(),
|
|
661
947
|
disabilityTraitsSource: accessibilityTraits ? 'explicit' : resolvedAccessibilityTraits ? 'inferred_from_description' : 'none',
|
|
948
|
+
...(inferredFrom.length ? {
|
|
949
|
+
disabilityInferredFrom: inferredFrom,
|
|
950
|
+
disabilityInferenceNote: "Keyword inference, not a diagnosis. It sets a trait only where the condition bears on it — a condition named in the description does not license a number for every accessibility trait. Pass accessibilityTraits explicitly to override any of it.",
|
|
951
|
+
} : {}),
|
|
662
952
|
ageRange: ageRange || personaObj.demographics?.age_range || null,
|
|
663
953
|
registered: true,
|
|
954
|
+
// Both stores named, and whether the persona SURVIVES stated
|
|
955
|
+
// outright. savedToCms alone read as a minor caveat when combined
|
|
956
|
+
// with registered:true it meant the persona was in RAM and nowhere
|
|
957
|
+
// else.
|
|
664
958
|
savedToCms,
|
|
959
|
+
savedToFile: savedToFile ? true : false,
|
|
960
|
+
...(savedToFile ? { filePath: savedToFile } : {}),
|
|
961
|
+
...(fileSaveError ? { fileSaveError } : {}),
|
|
962
|
+
// Agentic callers branch on success. A partial write that reports
|
|
963
|
+
// success is a write nobody retries, which is how the two stores
|
|
964
|
+
// drift apart in the first place -- so a half-completed write is
|
|
965
|
+
// reported as a failure with the half that landed named, rather
|
|
966
|
+
// than as a success with a caveat.
|
|
967
|
+
success: !!savedToFile && savedToCms,
|
|
968
|
+
...(savedToFile && !savedToCms ? { partialWrite: true } : {}),
|
|
969
|
+
writeStatus: { fileStore: savedToFile ? "ok" : "failed", cms: savedToCms ? "ok" : "failed" },
|
|
970
|
+
...(cmsError ? { cmsError } : {}),
|
|
971
|
+
persisted: !!savedToFile || savedToCms,
|
|
972
|
+
persistenceNote: savedToFile && savedToCms
|
|
973
|
+
? "Written to the file store and the CMS."
|
|
974
|
+
: savedToFile
|
|
975
|
+
? "Written to the file store. The CMS copy was NOT written, so the two stores disagree — the persona survives restarts but will not appear for other clients of the CMS."
|
|
976
|
+
: savedToCms
|
|
977
|
+
? "Written to the CMS only. The file store did NOT take it, so tools reading the file store will not find this persona."
|
|
978
|
+
: "NOT PERSISTED. The persona exists only in this server process's memory and will be gone when it restarts. `registered: true` above means RAM, not storage.",
|
|
665
979
|
instruction: resolvedAccessibilityTraits
|
|
666
|
-
|
|
980
|
+
// Names the modalities actually impaired instead of claiming
|
|
981
|
+
// "sensory impairment" wholesale. An ADHD persona with vision,
|
|
982
|
+
// motor and hearing at 1.0 and a short attention span has
|
|
983
|
+
// something real to model -- but nothing SENSORY, and the old
|
|
984
|
+
// text promised a simulation that would not happen. A claim about
|
|
985
|
+
// what a run will do has to match what the numbers support.
|
|
986
|
+
? (() => {
|
|
987
|
+
const a = resolvedAccessibilityTraits;
|
|
988
|
+
const impaired = [];
|
|
989
|
+
const lo = (k, label, t = 0.95) => {
|
|
990
|
+
if (typeof a[k] === "number" && a[k] < t)
|
|
991
|
+
impaired.push(`${label} ${a[k]}`);
|
|
992
|
+
};
|
|
993
|
+
lo("visionLevel", "vision");
|
|
994
|
+
lo("contrastSensitivity", "contrast");
|
|
995
|
+
lo("motorControl", "motor");
|
|
996
|
+
lo("hearingLevel", "hearing");
|
|
997
|
+
lo("processingSpeed", "processing speed", 0.6);
|
|
998
|
+
lo("attentionSpan", "attention span", 0.6);
|
|
999
|
+
if (a.tremor === true)
|
|
1000
|
+
impaired.push("tremor");
|
|
1001
|
+
if (a.colorBlindness)
|
|
1002
|
+
impaired.push(`colour vision (${String(a.colorBlindness)})`);
|
|
1003
|
+
const sensory = impaired.some((x) => /^(vision|contrast|hearing|colour)/.test(x));
|
|
1004
|
+
return `Persona "${persona_name}" created with accessibility modeling on: ${impaired.join(", ")}. `
|
|
1005
|
+
+ (sensory
|
|
1006
|
+
? "The perceptual transport layer will simulate the sensory limits above. "
|
|
1007
|
+
: "No SENSORY channel is impaired, so perception is simulated as intact; the limits above are cognitive and motor. ")
|
|
1008
|
+
+ "Use with empathy_audit for disability scoring.`".slice(0, -1);
|
|
1009
|
+
})()
|
|
667
1010
|
: `Persona "${persona_name}" created with cognitive-only modeling. For disability-specific scoring (vision impairment, motor difficulty, etc.), recreate with accessibilityTraits parameter.`,
|
|
668
1011
|
usage_example: {
|
|
669
1012
|
tool: "empathy_audit",
|