@ai-matrx/agents 0.13.16 → 0.14.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/CHANGELOG.md +20 -0
- package/dist/content-transfer/index.cjs.map +1 -1
- package/dist/content-transfer/index.js.map +1 -1
- package/dist/content-transfer/react/index.cjs.map +1 -1
- package/dist/content-transfer/react/index.js.map +1 -1
- package/dist/mandates/index.cjs +2 -2
- package/dist/mandates/index.cjs.map +1 -1
- package/dist/mandates/index.d.cts +4 -4
- package/dist/mandates/index.d.ts +4 -4
- package/dist/mandates/index.js +2 -2
- package/dist/mandates/index.js.map +1 -1
- package/dist/presentation/decision-answers.cjs +195 -0
- package/dist/presentation/decision-answers.cjs.map +1 -0
- package/dist/presentation/decision-answers.d.cts +113 -0
- package/dist/presentation/decision-answers.d.ts +113 -0
- package/dist/presentation/decision-answers.js +174 -0
- package/dist/presentation/decision-answers.js.map +1 -0
- package/mandates/snapshots/keys.0.14.0.json +619 -0
- package/package.json +13 -3
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a `decision_answers` payload — THE ONE reader, shared by every
|
|
3
|
+
* surface that shows a decision (matrx-frontend's answers card, review queue,
|
|
4
|
+
* comparison table and text export; Workflow Studio's decision block).
|
|
5
|
+
*
|
|
6
|
+
* Contract: `common-docs/systems/agents/typed-messages/FEATURE.md`
|
|
7
|
+
* ("Kind `decision_answers`"). The reader is DEFENSIVE by design — a
|
|
8
|
+
* malformed answer is shown as unreadable, never coerced into a number that
|
|
9
|
+
* a person would then act on. A probability is a claim about the world; a
|
|
10
|
+
* silently-defaulted one is a lie with a bar chart next to it.
|
|
11
|
+
*
|
|
12
|
+
* Pure and framework-free: no React, no DOM, no host imports.
|
|
13
|
+
*/
|
|
14
|
+
declare const DECISION_ANSWERS_KIND: "decision_answers";
|
|
15
|
+
/** How the probability was obtained. Never inferred — always stated. */
|
|
16
|
+
type DecisionMethod = "native" | "verbalized" | "verbalized_calibrated";
|
|
17
|
+
interface DecisionAnswerView {
|
|
18
|
+
name: string;
|
|
19
|
+
type: "noul" | "choice" | "score" | "unknown";
|
|
20
|
+
/** The answer verbatim: boolean, option name, or score. */
|
|
21
|
+
answer: boolean | string | number | null;
|
|
22
|
+
/**
|
|
23
|
+
* Yes/No only — probability that the answer is TRUE, verbatim from the
|
|
24
|
+
* payload. This is NOT the probability of the answer given: a `false`
|
|
25
|
+
* answer at `probability: 0.29` is a 71% No. Use `answerProbability` for
|
|
26
|
+
* the number that belongs beside the answer; this one is for the
|
|
27
|
+
* distribution and the author's threshold, both of which are stated on the
|
|
28
|
+
* true-probability scale.
|
|
29
|
+
*/
|
|
30
|
+
probability: number | null;
|
|
31
|
+
/**
|
|
32
|
+
* The full distribution, option/level → probability. For Yes/No this is
|
|
33
|
+
* SYNTHESISED from `probability` (Yes p, No 1−p) so every type has one
|
|
34
|
+
* distribution shape and no surface has to derive the complement itself.
|
|
35
|
+
*/
|
|
36
|
+
probabilities: Array<{
|
|
37
|
+
key: string;
|
|
38
|
+
label: string;
|
|
39
|
+
value: number;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* The distribution key the ANSWER points at: the option for a choice, the
|
|
43
|
+
* MOST LIKELY level (the distribution's peak) for a score, `"true"`/`"false"`
|
|
44
|
+
* for a Yes/No. `null` when the answer is unreadable or no distribution was
|
|
45
|
+
* returned. A score's weighted number is shown as the number; its label and
|
|
46
|
+
* percentage come from the peak, so the headline and the bold bar below it
|
|
47
|
+
* are the same level (see `answerKeyFor`).
|
|
48
|
+
*/
|
|
49
|
+
answerKey: string | null;
|
|
50
|
+
confidence: number | null;
|
|
51
|
+
/** Score only — level number → what that level means. */
|
|
52
|
+
legend: Record<string, string>;
|
|
53
|
+
/** The author's suggested cut, when the surface knows it. */
|
|
54
|
+
suggestedThreshold: number | null;
|
|
55
|
+
/** The question as asked, when the surface can supply it. */
|
|
56
|
+
instruction: string | null;
|
|
57
|
+
}
|
|
58
|
+
interface DecisionAnswersView {
|
|
59
|
+
model: string | null;
|
|
60
|
+
method: DecisionMethod | null;
|
|
61
|
+
answers: DecisionAnswerView[];
|
|
62
|
+
/** Questions the holder refused, with the reason it gave. */
|
|
63
|
+
refusals: Array<{
|
|
64
|
+
name: string;
|
|
65
|
+
reason: string;
|
|
66
|
+
}>;
|
|
67
|
+
inputTokens: number | null;
|
|
68
|
+
outputTokens: number | null;
|
|
69
|
+
costUsd: number | null;
|
|
70
|
+
}
|
|
71
|
+
/** `null` when the value is not a decision_answers payload at all. */
|
|
72
|
+
declare function readDecisionAnswers(value: unknown): DecisionAnswersView | null;
|
|
73
|
+
/** How the answer reads on screen. Never invented — `null` stays unreadable. */
|
|
74
|
+
declare function formatDecisionAnswer(answer: DecisionAnswerView): string;
|
|
75
|
+
/**
|
|
76
|
+
* The answer in words, for a glance. A score reads as its peak level
|
|
77
|
+
* ("4 · down for someone"), never "2.3 — down for someone", which put the
|
|
78
|
+
* probability-weighted average and the peak level's name in one phrase. The
|
|
79
|
+
* average stays available on the view (`answer.answer`) for surfaces that show
|
|
80
|
+
* it as a secondary figure; the text export keeps `formatDecisionAnswer`.
|
|
81
|
+
*/
|
|
82
|
+
declare function formatAnswerHeadline(answer: DecisionAnswerView): string;
|
|
83
|
+
/**
|
|
84
|
+
* The one number that belongs beside the answer: the probability OF THE
|
|
85
|
+
* ANSWER GIVEN, never the biggest number in the distribution and never the
|
|
86
|
+
* raw `probability` field.
|
|
87
|
+
*
|
|
88
|
+
* Two ways this used to lie, both live: a `noul` answered `false` at
|
|
89
|
+
* `probability: 0.3` printed "No 30%" when the model was 70% sure of No; and
|
|
90
|
+
* a score's label and percentage came from different levels. For a score the
|
|
91
|
+
* label is the peak level, so this is the peak's own probability — the label
|
|
92
|
+
* and the number always describe the same level.
|
|
93
|
+
*/
|
|
94
|
+
declare function answerProbability(answer: DecisionAnswerView): number | null;
|
|
95
|
+
/** P(true) for a Yes/No — the scale the author's threshold is stated on. */
|
|
96
|
+
declare function probabilityOfTrue(answer: DecisionAnswerView): number | null;
|
|
97
|
+
declare const METHOD_LABELS: Record<DecisionMethod, string>;
|
|
98
|
+
declare const METHOD_EXPLANATIONS: Record<DecisionMethod, string>;
|
|
99
|
+
/**
|
|
100
|
+
* A decision turn as READABLE TEXT — for every surface that shows a run's
|
|
101
|
+
* result as a string instead of drawing the `DecisionAnswers` card: the toast
|
|
102
|
+
* preview, agent apps (their `response` string, public `/p/<slug>` included),
|
|
103
|
+
* a shortcut's `responseText` handed to its caller. A decision turn carries no
|
|
104
|
+
* text at all, so every one of those surfaces used to show nothing — or wait
|
|
105
|
+
* forever — over a paid, finished verdict.
|
|
106
|
+
*
|
|
107
|
+
* One line per answer, with the probability OF THE ANSWER GIVEN (never the raw
|
|
108
|
+
* P(true) a `false` answer carries), then the refusals with their reasons.
|
|
109
|
+
* `null` when the value is not a decision payload — never a guess.
|
|
110
|
+
*/
|
|
111
|
+
declare function decisionAnswersText(payload: unknown): string | null;
|
|
112
|
+
|
|
113
|
+
export { DECISION_ANSWERS_KIND, type DecisionAnswerView, type DecisionAnswersView, type DecisionMethod, METHOD_EXPLANATIONS, METHOD_LABELS, answerProbability, decisionAnswersText, formatAnswerHeadline, formatDecisionAnswer, probabilityOfTrue, readDecisionAnswers };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a `decision_answers` payload — THE ONE reader, shared by every
|
|
3
|
+
* surface that shows a decision (matrx-frontend's answers card, review queue,
|
|
4
|
+
* comparison table and text export; Workflow Studio's decision block).
|
|
5
|
+
*
|
|
6
|
+
* Contract: `common-docs/systems/agents/typed-messages/FEATURE.md`
|
|
7
|
+
* ("Kind `decision_answers`"). The reader is DEFENSIVE by design — a
|
|
8
|
+
* malformed answer is shown as unreadable, never coerced into a number that
|
|
9
|
+
* a person would then act on. A probability is a claim about the world; a
|
|
10
|
+
* silently-defaulted one is a lie with a bar chart next to it.
|
|
11
|
+
*
|
|
12
|
+
* Pure and framework-free: no React, no DOM, no host imports.
|
|
13
|
+
*/
|
|
14
|
+
declare const DECISION_ANSWERS_KIND: "decision_answers";
|
|
15
|
+
/** How the probability was obtained. Never inferred — always stated. */
|
|
16
|
+
type DecisionMethod = "native" | "verbalized" | "verbalized_calibrated";
|
|
17
|
+
interface DecisionAnswerView {
|
|
18
|
+
name: string;
|
|
19
|
+
type: "noul" | "choice" | "score" | "unknown";
|
|
20
|
+
/** The answer verbatim: boolean, option name, or score. */
|
|
21
|
+
answer: boolean | string | number | null;
|
|
22
|
+
/**
|
|
23
|
+
* Yes/No only — probability that the answer is TRUE, verbatim from the
|
|
24
|
+
* payload. This is NOT the probability of the answer given: a `false`
|
|
25
|
+
* answer at `probability: 0.29` is a 71% No. Use `answerProbability` for
|
|
26
|
+
* the number that belongs beside the answer; this one is for the
|
|
27
|
+
* distribution and the author's threshold, both of which are stated on the
|
|
28
|
+
* true-probability scale.
|
|
29
|
+
*/
|
|
30
|
+
probability: number | null;
|
|
31
|
+
/**
|
|
32
|
+
* The full distribution, option/level → probability. For Yes/No this is
|
|
33
|
+
* SYNTHESISED from `probability` (Yes p, No 1−p) so every type has one
|
|
34
|
+
* distribution shape and no surface has to derive the complement itself.
|
|
35
|
+
*/
|
|
36
|
+
probabilities: Array<{
|
|
37
|
+
key: string;
|
|
38
|
+
label: string;
|
|
39
|
+
value: number;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* The distribution key the ANSWER points at: the option for a choice, the
|
|
43
|
+
* MOST LIKELY level (the distribution's peak) for a score, `"true"`/`"false"`
|
|
44
|
+
* for a Yes/No. `null` when the answer is unreadable or no distribution was
|
|
45
|
+
* returned. A score's weighted number is shown as the number; its label and
|
|
46
|
+
* percentage come from the peak, so the headline and the bold bar below it
|
|
47
|
+
* are the same level (see `answerKeyFor`).
|
|
48
|
+
*/
|
|
49
|
+
answerKey: string | null;
|
|
50
|
+
confidence: number | null;
|
|
51
|
+
/** Score only — level number → what that level means. */
|
|
52
|
+
legend: Record<string, string>;
|
|
53
|
+
/** The author's suggested cut, when the surface knows it. */
|
|
54
|
+
suggestedThreshold: number | null;
|
|
55
|
+
/** The question as asked, when the surface can supply it. */
|
|
56
|
+
instruction: string | null;
|
|
57
|
+
}
|
|
58
|
+
interface DecisionAnswersView {
|
|
59
|
+
model: string | null;
|
|
60
|
+
method: DecisionMethod | null;
|
|
61
|
+
answers: DecisionAnswerView[];
|
|
62
|
+
/** Questions the holder refused, with the reason it gave. */
|
|
63
|
+
refusals: Array<{
|
|
64
|
+
name: string;
|
|
65
|
+
reason: string;
|
|
66
|
+
}>;
|
|
67
|
+
inputTokens: number | null;
|
|
68
|
+
outputTokens: number | null;
|
|
69
|
+
costUsd: number | null;
|
|
70
|
+
}
|
|
71
|
+
/** `null` when the value is not a decision_answers payload at all. */
|
|
72
|
+
declare function readDecisionAnswers(value: unknown): DecisionAnswersView | null;
|
|
73
|
+
/** How the answer reads on screen. Never invented — `null` stays unreadable. */
|
|
74
|
+
declare function formatDecisionAnswer(answer: DecisionAnswerView): string;
|
|
75
|
+
/**
|
|
76
|
+
* The answer in words, for a glance. A score reads as its peak level
|
|
77
|
+
* ("4 · down for someone"), never "2.3 — down for someone", which put the
|
|
78
|
+
* probability-weighted average and the peak level's name in one phrase. The
|
|
79
|
+
* average stays available on the view (`answer.answer`) for surfaces that show
|
|
80
|
+
* it as a secondary figure; the text export keeps `formatDecisionAnswer`.
|
|
81
|
+
*/
|
|
82
|
+
declare function formatAnswerHeadline(answer: DecisionAnswerView): string;
|
|
83
|
+
/**
|
|
84
|
+
* The one number that belongs beside the answer: the probability OF THE
|
|
85
|
+
* ANSWER GIVEN, never the biggest number in the distribution and never the
|
|
86
|
+
* raw `probability` field.
|
|
87
|
+
*
|
|
88
|
+
* Two ways this used to lie, both live: a `noul` answered `false` at
|
|
89
|
+
* `probability: 0.3` printed "No 30%" when the model was 70% sure of No; and
|
|
90
|
+
* a score's label and percentage came from different levels. For a score the
|
|
91
|
+
* label is the peak level, so this is the peak's own probability — the label
|
|
92
|
+
* and the number always describe the same level.
|
|
93
|
+
*/
|
|
94
|
+
declare function answerProbability(answer: DecisionAnswerView): number | null;
|
|
95
|
+
/** P(true) for a Yes/No — the scale the author's threshold is stated on. */
|
|
96
|
+
declare function probabilityOfTrue(answer: DecisionAnswerView): number | null;
|
|
97
|
+
declare const METHOD_LABELS: Record<DecisionMethod, string>;
|
|
98
|
+
declare const METHOD_EXPLANATIONS: Record<DecisionMethod, string>;
|
|
99
|
+
/**
|
|
100
|
+
* A decision turn as READABLE TEXT — for every surface that shows a run's
|
|
101
|
+
* result as a string instead of drawing the `DecisionAnswers` card: the toast
|
|
102
|
+
* preview, agent apps (their `response` string, public `/p/<slug>` included),
|
|
103
|
+
* a shortcut's `responseText` handed to its caller. A decision turn carries no
|
|
104
|
+
* text at all, so every one of those surfaces used to show nothing — or wait
|
|
105
|
+
* forever — over a paid, finished verdict.
|
|
106
|
+
*
|
|
107
|
+
* One line per answer, with the probability OF THE ANSWER GIVEN (never the raw
|
|
108
|
+
* P(true) a `false` answer carries), then the refusals with their reasons.
|
|
109
|
+
* `null` when the value is not a decision payload — never a guess.
|
|
110
|
+
*/
|
|
111
|
+
declare function decisionAnswersText(payload: unknown): string | null;
|
|
112
|
+
|
|
113
|
+
export { DECISION_ANSWERS_KIND, type DecisionAnswerView, type DecisionAnswersView, type DecisionMethod, METHOD_EXPLANATIONS, METHOD_LABELS, answerProbability, decisionAnswersText, formatAnswerHeadline, formatDecisionAnswer, probabilityOfTrue, readDecisionAnswers };
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// presentation/decision-answers.ts
|
|
2
|
+
var DECISION_ANSWERS_KIND = "decision_answers";
|
|
3
|
+
function record(value) {
|
|
4
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
5
|
+
}
|
|
6
|
+
function finiteNumber(value) {
|
|
7
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
8
|
+
}
|
|
9
|
+
function readMethod(value) {
|
|
10
|
+
return value === "native" || value === "verbalized" || value === "verbalized_calibrated" ? value : null;
|
|
11
|
+
}
|
|
12
|
+
function readDistribution(value, legend) {
|
|
13
|
+
const raw = record(value);
|
|
14
|
+
if (!raw) return [];
|
|
15
|
+
return Object.entries(raw).map(([key, probability]) => ({
|
|
16
|
+
key,
|
|
17
|
+
label: legend[key] ?? key,
|
|
18
|
+
value: finiteNumber(probability) ?? 0
|
|
19
|
+
})).sort((a, b) => b.value - a.value);
|
|
20
|
+
}
|
|
21
|
+
function readLegend(value) {
|
|
22
|
+
const raw = record(value);
|
|
23
|
+
if (!raw) return {};
|
|
24
|
+
const out = {};
|
|
25
|
+
for (const [key, entry] of Object.entries(raw)) {
|
|
26
|
+
if (typeof entry === "string") out[key] = entry;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
function noulDistribution(probability) {
|
|
31
|
+
if (probability == null) return [];
|
|
32
|
+
return [
|
|
33
|
+
{ key: "true", label: "Yes", value: probability },
|
|
34
|
+
{ key: "false", label: "No", value: 1 - probability }
|
|
35
|
+
].sort((a, b) => b.value - a.value);
|
|
36
|
+
}
|
|
37
|
+
function answerKeyFor(type, answer, probabilities) {
|
|
38
|
+
if (answer === null || probabilities.length === 0) return null;
|
|
39
|
+
if (typeof answer === "boolean") return answer ? "true" : "false";
|
|
40
|
+
if (typeof answer === "string") {
|
|
41
|
+
return probabilities.some((entry) => entry.key === answer) ? answer : null;
|
|
42
|
+
}
|
|
43
|
+
if (type === "choice") return null;
|
|
44
|
+
let best = null;
|
|
45
|
+
let bestValue = Number.NEGATIVE_INFINITY;
|
|
46
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
47
|
+
for (const entry of probabilities) {
|
|
48
|
+
const level = Number(entry.key);
|
|
49
|
+
if (!Number.isFinite(level)) continue;
|
|
50
|
+
const distance = Math.abs(level - answer);
|
|
51
|
+
if (entry.value > bestValue || entry.value === bestValue && distance < bestDistance) {
|
|
52
|
+
bestValue = entry.value;
|
|
53
|
+
bestDistance = distance;
|
|
54
|
+
best = entry.key;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return best;
|
|
58
|
+
}
|
|
59
|
+
function readAnswer(name, value) {
|
|
60
|
+
const raw = record(value);
|
|
61
|
+
const type = raw?.type === "noul" || raw?.type === "choice" || raw?.type === "score" ? raw.type : "unknown";
|
|
62
|
+
const legend = readLegend(raw?.legend);
|
|
63
|
+
const answerRaw = raw?.answer;
|
|
64
|
+
const answer = typeof answerRaw === "boolean" || typeof answerRaw === "string" || typeof answerRaw === "number" ? answerRaw : null;
|
|
65
|
+
const probability = finiteNumber(raw?.probability);
|
|
66
|
+
const probabilities = type === "noul" ? noulDistribution(probability) : readDistribution(raw?.probabilities, legend);
|
|
67
|
+
return {
|
|
68
|
+
name,
|
|
69
|
+
type,
|
|
70
|
+
answer,
|
|
71
|
+
probability,
|
|
72
|
+
probabilities,
|
|
73
|
+
answerKey: answerKeyFor(type, answer, probabilities),
|
|
74
|
+
confidence: finiteNumber(raw?.confidence),
|
|
75
|
+
legend,
|
|
76
|
+
suggestedThreshold: finiteNumber(raw?.suggested_threshold),
|
|
77
|
+
instruction: typeof raw?.instructions === "string" ? raw.instructions : null
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function readDecisionAnswers(value) {
|
|
81
|
+
const raw = record(value);
|
|
82
|
+
if (!raw) return null;
|
|
83
|
+
const answers = record(raw.answers);
|
|
84
|
+
if (!answers) return null;
|
|
85
|
+
const unanswerable = record(raw.unanswerable);
|
|
86
|
+
const usage = record(raw.usage);
|
|
87
|
+
return {
|
|
88
|
+
model: typeof raw.model === "string" ? raw.model : null,
|
|
89
|
+
method: readMethod(raw.method),
|
|
90
|
+
answers: Object.entries(answers).map(
|
|
91
|
+
([name, answer]) => readAnswer(name, answer)
|
|
92
|
+
),
|
|
93
|
+
refusals: unanswerable ? Object.entries(unanswerable).map(([name, reason]) => ({
|
|
94
|
+
name,
|
|
95
|
+
reason: typeof reason === "string" && reason.trim() ? reason : "No reason was given."
|
|
96
|
+
})) : [],
|
|
97
|
+
inputTokens: finiteNumber(usage?.input_tokens),
|
|
98
|
+
outputTokens: finiteNumber(usage?.output_tokens),
|
|
99
|
+
costUsd: finiteNumber(raw.cost_usd)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function formatDecisionAnswer(answer) {
|
|
103
|
+
if (answer.answer === null) return "unreadable";
|
|
104
|
+
if (typeof answer.answer === "boolean") return answer.answer ? "Yes" : "No";
|
|
105
|
+
if (typeof answer.answer === "number") {
|
|
106
|
+
const shown = Number.isInteger(answer.answer) ? String(answer.answer) : answer.answer.toFixed(1);
|
|
107
|
+
const key = answer.answerKey;
|
|
108
|
+
const level = key == null ? void 0 : answer.legend[key];
|
|
109
|
+
return level ? `${shown} \u2014 ${level}` : shown;
|
|
110
|
+
}
|
|
111
|
+
return answer.answer;
|
|
112
|
+
}
|
|
113
|
+
function formatAnswerHeadline(answer) {
|
|
114
|
+
if (answer.type === "score" && typeof answer.answer === "number") {
|
|
115
|
+
const key = answer.answerKey;
|
|
116
|
+
const label = key == null ? void 0 : answer.legend[key];
|
|
117
|
+
if (label) return `${key} \xB7 ${label}`;
|
|
118
|
+
}
|
|
119
|
+
return formatDecisionAnswer(answer);
|
|
120
|
+
}
|
|
121
|
+
function answerProbability(answer) {
|
|
122
|
+
const key = answer.answerKey;
|
|
123
|
+
if (key != null) {
|
|
124
|
+
const entry = answer.probabilities.find((e) => e.key === key);
|
|
125
|
+
if (entry) return entry.value;
|
|
126
|
+
}
|
|
127
|
+
if (answer.type === "noul") return null;
|
|
128
|
+
if (answer.probability != null) return answer.probability;
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
function probabilityOfTrue(answer) {
|
|
132
|
+
return answer.type === "noul" ? answer.probability : null;
|
|
133
|
+
}
|
|
134
|
+
var METHOD_LABELS = {
|
|
135
|
+
native: "native",
|
|
136
|
+
verbalized: "verbalized",
|
|
137
|
+
verbalized_calibrated: "verbalized, calibrated"
|
|
138
|
+
};
|
|
139
|
+
var METHOD_EXPLANATIONS = {
|
|
140
|
+
native: "The model's own probability, read from its decision route \u2014 not something it wrote out in words.",
|
|
141
|
+
verbalized: "The model was asked for a probability and wrote one. It reads like a measurement but it is an opinion.",
|
|
142
|
+
verbalized_calibrated: "A written probability, corrected against this agent version's recorded ground-truth verdicts."
|
|
143
|
+
};
|
|
144
|
+
function decisionAnswersText(payload) {
|
|
145
|
+
const view = readDecisionAnswers(payload);
|
|
146
|
+
if (!view) return null;
|
|
147
|
+
const lines = [];
|
|
148
|
+
for (const answer of view.answers) {
|
|
149
|
+
const p = answerProbability(answer);
|
|
150
|
+
const shown = formatDecisionAnswer(answer);
|
|
151
|
+
lines.push(
|
|
152
|
+
`- ${answer.name}: ${shown}${p == null ? "" : ` (${Math.round(p * 100)}%)`}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
for (const refusal of view.refusals) {
|
|
156
|
+
lines.push(`- ${refusal.name}: not answered. ${refusal.reason}`);
|
|
157
|
+
}
|
|
158
|
+
if (lines.length === 0) lines.push("- No answers were returned.");
|
|
159
|
+
const how = view.method ? `${METHOD_LABELS[view.method]} probabilities` : "probabilities of unknown origin";
|
|
160
|
+
const header = `Decision (${how}${view.model ? `, ${view.model}` : ""})`;
|
|
161
|
+
return [header, ...lines].join("\n");
|
|
162
|
+
}
|
|
163
|
+
export {
|
|
164
|
+
DECISION_ANSWERS_KIND,
|
|
165
|
+
METHOD_EXPLANATIONS,
|
|
166
|
+
METHOD_LABELS,
|
|
167
|
+
answerProbability,
|
|
168
|
+
decisionAnswersText,
|
|
169
|
+
formatAnswerHeadline,
|
|
170
|
+
formatDecisionAnswer,
|
|
171
|
+
probabilityOfTrue,
|
|
172
|
+
readDecisionAnswers
|
|
173
|
+
};
|
|
174
|
+
//# sourceMappingURL=decision-answers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../presentation/decision-answers.ts"],"sourcesContent":["/**\n * Reading a `decision_answers` payload — THE ONE reader, shared by every\n * surface that shows a decision (matrx-frontend's answers card, review queue,\n * comparison table and text export; Workflow Studio's decision block).\n *\n * Contract: `common-docs/systems/agents/typed-messages/FEATURE.md`\n * (\"Kind `decision_answers`\"). The reader is DEFENSIVE by design — a\n * malformed answer is shown as unreadable, never coerced into a number that\n * a person would then act on. A probability is a claim about the world; a\n * silently-defaulted one is a lie with a bar chart next to it.\n *\n * Pure and framework-free: no React, no DOM, no host imports.\n */\n\nexport const DECISION_ANSWERS_KIND = \"decision_answers\" as const;\n\n/** How the probability was obtained. Never inferred — always stated. */\nexport type DecisionMethod = \"native\" | \"verbalized\" | \"verbalized_calibrated\";\n\nexport interface DecisionAnswerView {\n name: string;\n type: \"noul\" | \"choice\" | \"score\" | \"unknown\";\n /** The answer verbatim: boolean, option name, or score. */\n answer: boolean | string | number | null;\n /**\n * Yes/No only — probability that the answer is TRUE, verbatim from the\n * payload. This is NOT the probability of the answer given: a `false`\n * answer at `probability: 0.29` is a 71% No. Use `answerProbability` for\n * the number that belongs beside the answer; this one is for the\n * distribution and the author's threshold, both of which are stated on the\n * true-probability scale.\n */\n probability: number | null;\n /**\n * The full distribution, option/level → probability. For Yes/No this is\n * SYNTHESISED from `probability` (Yes p, No 1−p) so every type has one\n * distribution shape and no surface has to derive the complement itself.\n */\n probabilities: Array<{ key: string; label: string; value: number }>;\n /**\n * The distribution key the ANSWER points at: the option for a choice, the\n * MOST LIKELY level (the distribution's peak) for a score, `\"true\"`/`\"false\"`\n * for a Yes/No. `null` when the answer is unreadable or no distribution was\n * returned. A score's weighted number is shown as the number; its label and\n * percentage come from the peak, so the headline and the bold bar below it\n * are the same level (see `answerKeyFor`).\n */\n answerKey: string | null;\n confidence: number | null;\n /** Score only — level number → what that level means. */\n legend: Record<string, string>;\n /** The author's suggested cut, when the surface knows it. */\n suggestedThreshold: number | null;\n /** The question as asked, when the surface can supply it. */\n instruction: string | null;\n}\n\nexport interface DecisionAnswersView {\n model: string | null;\n method: DecisionMethod | null;\n answers: DecisionAnswerView[];\n /** Questions the holder refused, with the reason it gave. */\n refusals: Array<{ name: string; reason: string }>;\n inputTokens: number | null;\n outputTokens: number | null;\n costUsd: number | null;\n}\n\nfunction record(value: unknown): Record<string, unknown> | null {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : null;\n}\n\nfunction finiteNumber(value: unknown): number | null {\n return typeof value === \"number\" && Number.isFinite(value) ? value : null;\n}\n\nfunction readMethod(value: unknown): DecisionMethod | null {\n return value === \"native\" ||\n value === \"verbalized\" ||\n value === \"verbalized_calibrated\"\n ? value\n : null;\n}\n\nfunction readDistribution(\n value: unknown,\n legend: Record<string, string>,\n): Array<{ key: string; label: string; value: number }> {\n const raw = record(value);\n if (!raw) return [];\n return Object.entries(raw)\n .map(([key, probability]) => ({\n key,\n label: legend[key] ?? key,\n value: finiteNumber(probability) ?? 0,\n }))\n .sort((a, b) => b.value - a.value);\n}\n\nfunction readLegend(value: unknown): Record<string, string> {\n const raw = record(value);\n if (!raw) return {};\n const out: Record<string, string> = {};\n for (const [key, entry] of Object.entries(raw)) {\n if (typeof entry === \"string\") out[key] = entry;\n }\n return out;\n}\n\n/**\n * Yes/No as a two-entry distribution. `probability` is P(true), so No is its\n * complement — the one place that subtraction happens.\n */\nfunction noulDistribution(\n probability: number | null,\n): Array<{ key: string; label: string; value: number }> {\n if (probability == null) return [];\n return [\n { key: \"true\", label: \"Yes\", value: probability },\n { key: \"false\", label: \"No\", value: 1 - probability },\n ].sort((a, b) => b.value - a.value);\n}\n\n/**\n * The distribution entry the answer points at.\n *\n * A score is a probability-weighted number BETWEEN levels (2.9 on a\n * five-level scale). Its headline used to take the label of the level NEAREST\n * that number, which on a spread distribution read as a contradiction:\n * \"urgency 2.9 — data or money at risk 16%\" printed above bars led by\n * \"a feature is blocked 46%\". The label now names the MOST LIKELY level — the\n * peak — with the peak's own probability, while the weighted score stays the\n * number (Arman's ruling via the owning session, 2026-09-23). A tie on the\n * peak goes to the level nearest the weighted score, so the choice is stable.\n * Keys are compared as numbers, never by an assumed 0- or 1-based index.\n */\nfunction answerKeyFor(\n type: DecisionAnswerView[\"type\"],\n answer: boolean | string | number | null,\n probabilities: Array<{ key: string; label: string; value: number }>,\n): string | null {\n if (answer === null || probabilities.length === 0) return null;\n if (typeof answer === \"boolean\") return answer ? \"true\" : \"false\";\n if (typeof answer === \"string\") {\n return probabilities.some((entry) => entry.key === answer) ? answer : null;\n }\n if (type === \"choice\") return null;\n let best: string | null = null;\n let bestValue = Number.NEGATIVE_INFINITY;\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const entry of probabilities) {\n const level = Number(entry.key);\n if (!Number.isFinite(level)) continue;\n const distance = Math.abs(level - answer);\n if (\n entry.value > bestValue ||\n (entry.value === bestValue && distance < bestDistance)\n ) {\n bestValue = entry.value;\n bestDistance = distance;\n best = entry.key;\n }\n }\n return best;\n}\n\nfunction readAnswer(name: string, value: unknown): DecisionAnswerView {\n const raw = record(value);\n const type =\n raw?.type === \"noul\" || raw?.type === \"choice\" || raw?.type === \"score\"\n ? raw.type\n : \"unknown\";\n const legend = readLegend(raw?.legend);\n const answerRaw = raw?.answer;\n const answer =\n typeof answerRaw === \"boolean\" ||\n typeof answerRaw === \"string\" ||\n typeof answerRaw === \"number\"\n ? answerRaw\n : null;\n const probability = finiteNumber(raw?.probability);\n const probabilities =\n type === \"noul\"\n ? noulDistribution(probability)\n : readDistribution(raw?.probabilities, legend);\n return {\n name,\n type,\n answer,\n probability,\n probabilities,\n answerKey: answerKeyFor(type, answer, probabilities),\n confidence: finiteNumber(raw?.confidence),\n legend,\n suggestedThreshold: finiteNumber(raw?.suggested_threshold),\n instruction:\n typeof raw?.instructions === \"string\"\n ? (raw.instructions as string)\n : null,\n };\n}\n\n/** `null` when the value is not a decision_answers payload at all. */\nexport function readDecisionAnswers(\n value: unknown,\n): DecisionAnswersView | null {\n const raw = record(value);\n if (!raw) return null;\n const answers = record(raw.answers);\n if (!answers) return null;\n\n const unanswerable = record(raw.unanswerable);\n const usage = record(raw.usage);\n\n return {\n model: typeof raw.model === \"string\" ? raw.model : null,\n method: readMethod(raw.method),\n answers: Object.entries(answers).map(([name, answer]) =>\n readAnswer(name, answer),\n ),\n refusals: unanswerable\n ? Object.entries(unanswerable).map(([name, reason]) => ({\n name,\n reason:\n typeof reason === \"string\" && reason.trim()\n ? reason\n : \"No reason was given.\",\n }))\n : [],\n inputTokens: finiteNumber(usage?.input_tokens),\n outputTokens: finiteNumber(usage?.output_tokens),\n costUsd: finiteNumber(raw.cost_usd),\n };\n}\n\n/** How the answer reads on screen. Never invented — `null` stays unreadable. */\nexport function formatDecisionAnswer(answer: DecisionAnswerView): string {\n if (answer.answer === null) return \"unreadable\";\n if (typeof answer.answer === \"boolean\") return answer.answer ? \"Yes\" : \"No\";\n if (typeof answer.answer === \"number\") {\n const shown = Number.isInteger(answer.answer)\n ? String(answer.answer)\n : answer.answer.toFixed(1);\n const key = answer.answerKey;\n const level = key == null ? undefined : answer.legend[key];\n return level ? `${shown} — ${level}` : shown;\n }\n return answer.answer;\n}\n\n/**\n * The answer in words, for a glance. A score reads as its peak level\n * (\"4 · down for someone\"), never \"2.3 — down for someone\", which put the\n * probability-weighted average and the peak level's name in one phrase. The\n * average stays available on the view (`answer.answer`) for surfaces that show\n * it as a secondary figure; the text export keeps `formatDecisionAnswer`.\n */\nexport function formatAnswerHeadline(answer: DecisionAnswerView): string {\n if (answer.type === \"score\" && typeof answer.answer === \"number\") {\n const key = answer.answerKey;\n const label = key == null ? undefined : answer.legend[key];\n if (label) return `${key} · ${label}`;\n }\n return formatDecisionAnswer(answer);\n}\n\n/**\n * The one number that belongs beside the answer: the probability OF THE\n * ANSWER GIVEN, never the biggest number in the distribution and never the\n * raw `probability` field.\n *\n * Two ways this used to lie, both live: a `noul` answered `false` at\n * `probability: 0.3` printed \"No 30%\" when the model was 70% sure of No; and\n * a score's label and percentage came from different levels. For a score the\n * label is the peak level, so this is the peak's own probability — the label\n * and the number always describe the same level.\n */\nexport function answerProbability(answer: DecisionAnswerView): number | null {\n const key = answer.answerKey;\n if (key != null) {\n const entry = answer.probabilities.find((e) => e.key === key);\n if (entry) return entry.value;\n }\n if (answer.type === \"noul\") return null;\n if (answer.probability != null) return answer.probability;\n return null;\n}\n\n/** P(true) for a Yes/No — the scale the author's threshold is stated on. */\nexport function probabilityOfTrue(answer: DecisionAnswerView): number | null {\n return answer.type === \"noul\" ? answer.probability : null;\n}\n\nexport const METHOD_LABELS: Record<DecisionMethod, string> = {\n native: \"native\",\n verbalized: \"verbalized\",\n verbalized_calibrated: \"verbalized, calibrated\",\n};\n\nexport const METHOD_EXPLANATIONS: Record<DecisionMethod, string> = {\n native:\n \"The model's own probability, read from its decision route — not something it wrote out in words.\",\n verbalized:\n \"The model was asked for a probability and wrote one. It reads like a measurement but it is an opinion.\",\n verbalized_calibrated:\n \"A written probability, corrected against this agent version's recorded ground-truth verdicts.\",\n};\n\n/**\n * A decision turn as READABLE TEXT — for every surface that shows a run's\n * result as a string instead of drawing the `DecisionAnswers` card: the toast\n * preview, agent apps (their `response` string, public `/p/<slug>` included),\n * a shortcut's `responseText` handed to its caller. A decision turn carries no\n * text at all, so every one of those surfaces used to show nothing — or wait\n * forever — over a paid, finished verdict.\n *\n * One line per answer, with the probability OF THE ANSWER GIVEN (never the raw\n * P(true) a `false` answer carries), then the refusals with their reasons.\n * `null` when the value is not a decision payload — never a guess.\n */\nexport function decisionAnswersText(payload: unknown): string | null {\n const view = readDecisionAnswers(payload);\n if (!view) return null;\n const lines: string[] = [];\n for (const answer of view.answers) {\n const p = answerProbability(answer);\n const shown = formatDecisionAnswer(answer);\n lines.push(\n `- ${answer.name}: ${shown}${p == null ? \"\" : ` (${Math.round(p * 100)}%)`}`,\n );\n }\n for (const refusal of view.refusals) {\n lines.push(`- ${refusal.name}: not answered. ${refusal.reason}`);\n }\n if (lines.length === 0) lines.push(\"- No answers were returned.\");\n const how = view.method\n ? `${METHOD_LABELS[view.method]} probabilities`\n : \"probabilities of unknown origin\";\n const header = `Decision (${how}${view.model ? `, ${view.model}` : \"\"})`;\n return [header, ...lines].join(\"\\n\");\n}\n"],"mappings":"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","names":[]}
|