mixdog 0.9.154 → 0.9.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/scripts/build-voice-runtime-unix.sh +24 -0
- package/scripts/voice-runtime-config.json +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +5 -2
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +47 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +4 -1
- package/src/runtime/channels/data/voice-runtime-manifest.json +17 -17
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +59 -0
- package/src/runtime/office/assurance-benchmark.mjs +67 -1
- package/src/runtime/office/assurance.mjs +24 -17
- package/src/runtime/office/capabilities.mjs +17 -17
- package/src/runtime/office/composition-system.mjs +451 -0
- package/src/runtime/office/design-library.mjs +102 -0
- package/src/runtime/office/design-system.mjs +357 -168
- package/src/runtime/office/index.mjs +149 -20
- package/src/runtime/office/office-com-host.ps1 +132 -46
- package/src/runtime/office/pdf-render.mjs +203 -35
- package/src/runtime/office/portable-ooxml.mjs +1090 -47
- package/src/runtime/office/portable-package.mjs +390 -0
- package/src/runtime/office/portable-sheet-styles.mjs +241 -0
- package/src/runtime/office/portable-slide-shapes.mjs +283 -0
- package/src/runtime/office/quality-live-benchmark.mjs +82 -36
- package/src/runtime/office/quality-pipeline.mjs +3 -0
- package/src/runtime/office/tool-defs.mjs +2 -2
- package/src/session-runtime/goal-reminder.mjs +79 -0
- package/src/session-runtime/goal-runtime.mjs +105 -45
- package/src/session-runtime/goal-text.mjs +58 -0
- package/src/session-runtime/runtime-core.mjs +14 -1
- package/src/session-runtime/session-lifecycle.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +5 -0
- package/src/standalone/session-service.mjs +30 -3
- package/src/tui/dist/index.mjs +12 -3
- package/src/tui/lib/voice-setup.mjs +19 -3
- package/src/tui/session/goal-continuation.mjs +7 -0
|
@@ -54,19 +54,19 @@ const CATALOG = {
|
|
|
54
54
|
fields: ['toc', 'page', 'numPages', 'pageBreak', 'sectionBreak'],
|
|
55
55
|
links: ['address', 'subAddress', 'display', 'bookmark'],
|
|
56
56
|
provenance: ['source.document', 'source.target', 'source.label'],
|
|
57
|
-
design: ['profile', 'intent', 'audience', 'tone', 'density', 'palette', 'typography', 'signature', 'content.packageId', 'content.audience', 'content.objective', 'content.decision', 'content.period', 'content.facts', 'content.claims', 'review'],
|
|
57
|
+
design: ['profile', 'purpose', 'expressionMode', 'intent', 'audience', 'tone', 'density', 'palette', 'typography', 'signature', 'content.packageId', 'content.audience', 'content.objective', 'content.decision', 'content.period', 'content.facts', 'content.claims', 'review'],
|
|
58
58
|
},
|
|
59
59
|
},
|
|
60
60
|
xlsx: {
|
|
61
61
|
paths: ['/sheet[NAME]', '/sheet[NAME]/cell[A1]', '/sheet[NAME]/range[A1:C10]'],
|
|
62
62
|
operations: {
|
|
63
|
-
common: ['replace_text', 'set_cell', 'set_formula', 'set_range', 'append_row', 'clear_cell'],
|
|
64
|
-
office: ['
|
|
63
|
+
common: ['replace_text', 'set_cell', 'set_formula', 'set_range', 'append_row', 'clear_cell', 'compose_sheet', 'add_sheet', 'delete_sheet', 'rename_sheet', 'set_style', 'merge_cells', 'unmerge_cells', 'freeze_panes', 'autofit_range', 'set_page_setup', 'set_sheet_view'],
|
|
64
|
+
office: ['copy_sheet', 'add_note', 'delete_note', 'add_image', 'add_table', 'add_chart', 'add_conditional_format', 'delete_conditional_formats', 'add_validation', 'add_pivot_table', 'set_sheet_visibility', 'insert_rows', 'delete_rows', 'insert_columns', 'delete_columns', 'set_autofilter', 'set_hyperlink', 'define_name', 'delete_name', 'protect_sheet', 'unprotect_sheet', 'add_provenance'],
|
|
65
65
|
portable: [],
|
|
66
66
|
},
|
|
67
67
|
properties: {
|
|
68
68
|
cellStyle: ['fontName', 'fontSize', 'bold', 'italic', 'color', 'fillColor', 'numberFormat', 'horizontalAlignment', 'verticalAlignment', 'wrapText'],
|
|
69
|
-
chart: ['chartType', 'left', 'top', 'width', 'height', 'title'],
|
|
69
|
+
chart: ['chartType', 'left', 'top', 'width', 'height', 'title', 'seriesColors', 'showValues', 'showLegend', 'zeroBaseline', 'valueNumberFormat', 'dataLabelPosition', 'dataLabelColor'],
|
|
70
70
|
table: ['name', 'style'],
|
|
71
71
|
pivot: ['source', 'destination', 'name', 'rows', 'columns', 'values'],
|
|
72
72
|
note: ['text', 'author'],
|
|
@@ -77,14 +77,14 @@ const CATALOG = {
|
|
|
77
77
|
pageSetup: ['printArea', 'orientation', 'fitToPagesWide', 'fitToPagesTall', 'centerHorizontally', 'topMargin', 'bottomMargin', 'leftMargin', 'rightMargin'],
|
|
78
78
|
sheetView: ['showGridlines', 'zoom'],
|
|
79
79
|
provenance: ['source.document', 'source.target', 'source.label'],
|
|
80
|
-
design: ['profile', 'intent', 'audience', 'tone', 'density', 'palette', 'typography', 'signature', 'content.packageId', 'content.audience', 'content.objective', 'content.decision', 'content.period', 'content.facts', 'content.claims', 'review'],
|
|
80
|
+
design: ['profile', 'purpose', 'expressionMode', 'intent', 'audience', 'tone', 'density', 'palette', 'typography', 'signature', 'content.packageId', 'content.audience', 'content.objective', 'content.decision', 'content.period', 'content.facts', 'content.claims', 'review'],
|
|
81
81
|
},
|
|
82
82
|
},
|
|
83
83
|
pptx: {
|
|
84
84
|
paths: ['/slide[N]', '/slide[N]/shape[N]'],
|
|
85
85
|
operations: {
|
|
86
|
-
common: ['replace_text', 'fill_template', 'set_text', 'add_textbox', 'delete_shape'],
|
|
87
|
-
office: ['
|
|
86
|
+
common: ['replace_text', 'fill_template', 'set_text', 'add_textbox', 'delete_shape', 'compose_slide', 'add_slide', 'delete_slide', 'move_slide', 'set_notes', 'add_image', 'add_shape', 'add_table', 'set_shape', 'set_slide_background', 'import_slides', 'replace_image', 'set_table_data'],
|
|
87
|
+
office: ['duplicate_slide', 'keep_slides', 'set_footer', 'set_slide_number', 'add_comment', 'delete_comment', 'crop_image', 'add_media', 'group_shapes', 'ungroup_shape', 'set_layout', 'apply_theme', 'set_transition', 'add_animation', 'add_chart', 'fit_text', 'set_chart_data', 'set_chart_series', 'set_chart_axis', 'set_chart_data_labels', 'set_chart_trendline', 'set_chart_error_bars', 'set_hyperlink', 'z_order', 'align_shapes', 'distribute_shapes', 'add_provenance'],
|
|
88
88
|
portable: [],
|
|
89
89
|
},
|
|
90
90
|
properties: {
|
|
@@ -100,7 +100,7 @@ const CATALOG = {
|
|
|
100
100
|
media: ['kind', 'link', 'embed', 'poster'],
|
|
101
101
|
comment: ['author', 'initials', 'left', 'top'],
|
|
102
102
|
provenance: ['source.document', 'source.target', 'source.label'],
|
|
103
|
-
design: ['profile', 'intent', 'audience', 'tone', 'density', 'palette', 'typography', 'signature', 'content.packageId', 'content.audience', 'content.objective', 'content.decision', 'content.period', 'content.facts', 'content.claims', 'template', 'deck.backgroundMode', 'deck.dominantColorRole', 'deck.motif', 'deck.spacingScale', 'deck.sectionSlides', 'deck.roles', 'deck.requireSlidePlan', 'deck.templateMode', 'review', 'reviewed', 'reviewToken', 'critique'],
|
|
103
|
+
design: ['profile', 'purpose', 'expressionMode', 'intent', 'audience', 'tone', 'density', 'palette', 'typography', 'signature', 'content.packageId', 'content.audience', 'content.objective', 'content.decision', 'content.period', 'content.facts', 'content.claims', 'template', 'deck.backgroundMode', 'deck.dominantColorRole', 'deck.motif', 'deck.spacingScale', 'deck.sectionSlides', 'deck.roles', 'deck.requireSlidePlan', 'deck.templateMode', 'review', 'reviewed', 'reviewToken', 'critique'],
|
|
104
104
|
},
|
|
105
105
|
},
|
|
106
106
|
pdf: {
|
|
@@ -143,9 +143,9 @@ const COMMON_SIGNATURES = {
|
|
|
143
143
|
|
|
144
144
|
const FORMAT_SIGNATURES = {
|
|
145
145
|
docx: {
|
|
146
|
-
compose_document: signature(['title'], ['claimId', 'subtitle', 'summary', 'meta', 'sections', 'footer', 'orientation', 'pageNumbers'], {
|
|
146
|
+
compose_document: signature(['title'], ['claimId', 'purpose', 'expressionMode', 'variant', 'subtitle', 'summary', 'meta', 'sections', 'footer', 'orientation', 'pageNumbers'], {
|
|
147
147
|
propertySets: ['design'],
|
|
148
|
-
notes: '
|
|
148
|
+
notes: 'Purpose-aware native Word composition; variant is optional because content topology selects the default.',
|
|
149
149
|
}),
|
|
150
150
|
append_text: signature(['text'], ['style', 'properties'], {
|
|
151
151
|
propertySets: ['paragraph', 'font', 'paragraphFormat'],
|
|
@@ -179,7 +179,7 @@ const FORMAT_SIGNATURES = {
|
|
|
179
179
|
set_page: signature(['properties'], ['section'], { propertySets: ['page'] }),
|
|
180
180
|
fit_table: signature(['table']),
|
|
181
181
|
insert_toc: signature([], ['paragraph', 'lowerHeadingLevel', 'upperHeadingLevel']),
|
|
182
|
-
add_page_numbers: signature([], ['section', 'kind', 'prefix', 'separator', 'includeTotal']),
|
|
182
|
+
add_page_numbers: signature([], ['section', 'kind', 'prefix', 'separator', 'includeTotal', 'alignment']),
|
|
183
183
|
insert_break: signature([], ['paragraph', 'kind']),
|
|
184
184
|
set_list: signature(['paragraph', 'kind'], ['level']),
|
|
185
185
|
add_hyperlink: signature([], ['find', 'paragraph', 'address', 'subAddress', 'display'], {
|
|
@@ -190,9 +190,9 @@ const FORMAT_SIGNATURES = {
|
|
|
190
190
|
add_provenance: signature(['paragraph', 'source'], [], { propertySets: ['provenance'] }),
|
|
191
191
|
},
|
|
192
192
|
xlsx: {
|
|
193
|
-
compose_sheet: signature(['rows'], ['claimId', 'sheet', 'kind', 'title', 'subtitle', 'source', 'headers', 'metrics', 'insights', 'columnFormats', 'tableName', 'tableStyle', 'chart'], {
|
|
193
|
+
compose_sheet: signature(['rows'], ['claimId', 'purpose', 'expressionMode', 'variant', 'sheet', 'kind', 'title', 'subtitle', 'source', 'headers', 'metrics', 'insights', 'columnFormats', 'tableName', 'tableStyle', 'chart'], {
|
|
194
194
|
propertySets: ['design'],
|
|
195
|
-
notes: '
|
|
195
|
+
notes: 'Purpose-aware native Excel composition with content-selected dashboard, trend, comparison, scorecard, or analysis layout.',
|
|
196
196
|
}),
|
|
197
197
|
set_cell: signature(['cell', 'value'], ['sheet']),
|
|
198
198
|
set_formula: signature(['cell', 'formula'], ['sheet']),
|
|
@@ -211,7 +211,7 @@ const FORMAT_SIGNATURES = {
|
|
|
211
211
|
delete_note: signature(['cell'], ['sheet']),
|
|
212
212
|
add_image: signature(['path'], ['sheet', 'left', 'top', 'width', 'height']),
|
|
213
213
|
add_table: signature(['range'], ['sheet', 'name', 'style'], { propertySets: ['table'] }),
|
|
214
|
-
add_chart: signature(['range'], ['sheet', 'chartType', 'title', 'left', 'top', 'width', 'height'], {
|
|
214
|
+
add_chart: signature(['range'], ['sheet', 'chartType', 'title', 'left', 'top', 'width', 'height', 'seriesColors', 'showValues', 'showLegend', 'zeroBaseline', 'valueNumberFormat', 'dataLabelPosition', 'dataLabelColor'], {
|
|
215
215
|
propertySets: ['chart'],
|
|
216
216
|
notes: 'The first source column supplies categories; remaining columns become series.',
|
|
217
217
|
}),
|
|
@@ -223,7 +223,7 @@ const FORMAT_SIGNATURES = {
|
|
|
223
223
|
autofit_range: signature(['range'], ['sheet', 'rows'], {
|
|
224
224
|
notes: 'Accepts cell, whole-column, or whole-row ranges such as A1:D5, A:D, or 2:8.',
|
|
225
225
|
}),
|
|
226
|
-
set_page_setup: signature([], ['sheet', 'printArea', 'orientation', 'fitToPagesWide', 'fitToPagesTall', 'centerHorizontally', 'topMargin', 'bottomMargin', 'leftMargin', 'rightMargin'], {
|
|
226
|
+
set_page_setup: signature([], ['sheet', 'printArea', 'orientation', 'fitToPagesWide', 'fitToPagesTall', 'centerHorizontally', 'centerVertically', 'topMargin', 'bottomMargin', 'leftMargin', 'rightMargin'], {
|
|
227
227
|
propertySets: ['pageSetup'],
|
|
228
228
|
}),
|
|
229
229
|
set_sheet_view: signature([], ['sheet', 'showGridlines', 'zoom'], {
|
|
@@ -247,9 +247,9 @@ const FORMAT_SIGNATURES = {
|
|
|
247
247
|
add_provenance: signature(['cell', 'source'], ['sheet'], { propertySets: ['provenance'] }),
|
|
248
248
|
},
|
|
249
249
|
pptx: {
|
|
250
|
-
compose_slide: signature(['kind'], ['claimId', 'title', 'subtitle', 'takeaway', 'eyebrow', 'body', 'bullets', 'metrics', 'columns', 'steps', 'chart', 'table', 'image', 'imagePath', 'visualText', 'visualLabel', 'meta', 'notes', 'source', 'background', 'backgroundRole', 'slideRole', 'plan', 'create', 'slide', 'layoutId', 'variant', 'titleSize'], {
|
|
250
|
+
compose_slide: signature(['kind'], ['claimId', 'purpose', 'expressionMode', 'title', 'subtitle', 'takeaway', 'eyebrow', 'body', 'bullets', 'metrics', 'columns', 'steps', 'chart', 'table', 'image', 'imagePath', 'visualText', 'visualLabel', 'meta', 'notes', 'source', 'background', 'backgroundRole', 'slideRole', 'plan', 'create', 'slide', 'layoutId', 'variant', 'titleSize'], {
|
|
251
251
|
propertySets: ['design'],
|
|
252
|
-
notes: 'Template-first semantic slide
|
|
252
|
+
notes: 'Template-first semantic slide; generic content is reclassified from its evidence, while explicit layoutId or variant stays pinned.',
|
|
253
253
|
}),
|
|
254
254
|
set_text: signature(['slide', 'shape', 'text']),
|
|
255
255
|
add_textbox: signature(['slide', 'text'], ['paragraphs', 'left', 'top', 'width', 'height', 'fontName', 'fontSize', 'color', 'properties'], { propertySets: ['shape', 'authoring'] }),
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
const PURPOSES = new Set(['monitor', 'decide', 'compare', 'explain', 'inspect']);
|
|
4
|
+
const EXPRESSION_MODES = new Set(['conservative', 'strong-fit', 'divergent']);
|
|
5
|
+
|
|
6
|
+
const FORMAT_CANDIDATES = Object.freeze({
|
|
7
|
+
docx: Object.freeze([
|
|
8
|
+
Object.freeze({
|
|
9
|
+
id: 'decision-brief',
|
|
10
|
+
family: 'decision',
|
|
11
|
+
purposes: ['decide'],
|
|
12
|
+
modes: ['conservative', 'strong-fit'],
|
|
13
|
+
evidence: ['claims', 'facts'],
|
|
14
|
+
densities: ['balanced', 'dense'],
|
|
15
|
+
novelty: 0,
|
|
16
|
+
}),
|
|
17
|
+
Object.freeze({
|
|
18
|
+
id: 'evidence-brief',
|
|
19
|
+
family: 'evidence',
|
|
20
|
+
purposes: ['decide', 'compare', 'inspect'],
|
|
21
|
+
modes: ['strong-fit', 'divergent'],
|
|
22
|
+
evidence: ['table', 'facts'],
|
|
23
|
+
densities: ['balanced', 'dense'],
|
|
24
|
+
novelty: 1,
|
|
25
|
+
}),
|
|
26
|
+
Object.freeze({
|
|
27
|
+
id: 'editorial-report',
|
|
28
|
+
family: 'narrative',
|
|
29
|
+
purposes: ['explain'],
|
|
30
|
+
modes: ['strong-fit', 'divergent'],
|
|
31
|
+
evidence: ['narrative', 'claims'],
|
|
32
|
+
densities: ['light', 'balanced'],
|
|
33
|
+
novelty: 1,
|
|
34
|
+
}),
|
|
35
|
+
Object.freeze({
|
|
36
|
+
id: 'compact-memo',
|
|
37
|
+
family: 'memo',
|
|
38
|
+
purposes: ['decide', 'inspect'],
|
|
39
|
+
modes: ['conservative'],
|
|
40
|
+
evidence: ['narrative'],
|
|
41
|
+
densities: ['balanced', 'dense'],
|
|
42
|
+
novelty: 0,
|
|
43
|
+
}),
|
|
44
|
+
]),
|
|
45
|
+
xlsx: Object.freeze([
|
|
46
|
+
Object.freeze({
|
|
47
|
+
id: 'monitor-dashboard',
|
|
48
|
+
family: 'monitor',
|
|
49
|
+
purposes: ['monitor'],
|
|
50
|
+
modes: ['conservative', 'strong-fit'],
|
|
51
|
+
evidence: ['metrics', 'table'],
|
|
52
|
+
densities: ['balanced', 'dense'],
|
|
53
|
+
novelty: 0,
|
|
54
|
+
}),
|
|
55
|
+
Object.freeze({
|
|
56
|
+
id: 'trend-dashboard',
|
|
57
|
+
family: 'trend',
|
|
58
|
+
purposes: ['monitor', 'explain'],
|
|
59
|
+
modes: ['strong-fit', 'divergent'],
|
|
60
|
+
evidence: ['chart', 'table'],
|
|
61
|
+
densities: ['balanced'],
|
|
62
|
+
novelty: 1,
|
|
63
|
+
}),
|
|
64
|
+
Object.freeze({
|
|
65
|
+
id: 'comparison-board',
|
|
66
|
+
family: 'comparison',
|
|
67
|
+
purposes: ['compare', 'decide'],
|
|
68
|
+
modes: ['strong-fit', 'divergent'],
|
|
69
|
+
evidence: ['chart', 'metrics'],
|
|
70
|
+
densities: ['balanced', 'dense'],
|
|
71
|
+
novelty: 1,
|
|
72
|
+
}),
|
|
73
|
+
Object.freeze({
|
|
74
|
+
id: 'analysis-sheet',
|
|
75
|
+
family: 'analysis',
|
|
76
|
+
purposes: ['inspect', 'explain'],
|
|
77
|
+
modes: ['conservative', 'strong-fit'],
|
|
78
|
+
evidence: ['table', 'facts'],
|
|
79
|
+
densities: ['dense'],
|
|
80
|
+
novelty: 0,
|
|
81
|
+
}),
|
|
82
|
+
Object.freeze({
|
|
83
|
+
id: 'narrative-scorecard',
|
|
84
|
+
family: 'scorecard',
|
|
85
|
+
purposes: ['monitor', 'decide'],
|
|
86
|
+
modes: ['divergent'],
|
|
87
|
+
evidence: ['metrics', 'claims'],
|
|
88
|
+
densities: ['light', 'balanced'],
|
|
89
|
+
novelty: 2,
|
|
90
|
+
}),
|
|
91
|
+
]),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const PPTX_VARIANTS = Object.freeze({
|
|
95
|
+
cover: Object.freeze([
|
|
96
|
+
Object.freeze({ variant: 'editorial-left', family: 'editorial', modes: ['conservative', 'strong-fit'], novelty: 0 }),
|
|
97
|
+
Object.freeze({ variant: 'minimal-focus', family: 'minimal', modes: ['strong-fit', 'divergent'], novelty: 1 }),
|
|
98
|
+
]),
|
|
99
|
+
statement: Object.freeze([
|
|
100
|
+
Object.freeze({ variant: 'metric-statement', family: 'evidence', purposes: ['decide', 'monitor'], evidence: ['metrics'], novelty: 0 }),
|
|
101
|
+
Object.freeze({ variant: 'typographic-statement', family: 'typography', purposes: ['decide', 'explain'], evidence: ['narrative'], novelty: 1 }),
|
|
102
|
+
]),
|
|
103
|
+
metrics: Object.freeze([
|
|
104
|
+
Object.freeze({ variant: 'asymmetric-metrics', family: 'evidence', purposes: ['decide', 'monitor'], modes: ['conservative', 'strong-fit'], novelty: 0 }),
|
|
105
|
+
Object.freeze({ variant: 'metric-band', family: 'rhythm', purposes: ['monitor', 'explain'], modes: ['strong-fit', 'divergent'], novelty: 1 }),
|
|
106
|
+
]),
|
|
107
|
+
comparison: Object.freeze([
|
|
108
|
+
Object.freeze({ variant: 'contrast-panels', family: 'comparison', modes: ['conservative', 'strong-fit'], novelty: 0 }),
|
|
109
|
+
Object.freeze({ variant: 'aligned-evidence', family: 'evidence', modes: ['strong-fit', 'divergent'], novelty: 1 }),
|
|
110
|
+
]),
|
|
111
|
+
process: Object.freeze([
|
|
112
|
+
Object.freeze({ variant: 'horizontal-flow', family: 'process', modes: ['conservative', 'strong-fit'], novelty: 0 }),
|
|
113
|
+
Object.freeze({ variant: 'staggered-flow', family: 'rhythm', modes: ['strong-fit', 'divergent'], novelty: 1 }),
|
|
114
|
+
]),
|
|
115
|
+
chart: Object.freeze([
|
|
116
|
+
Object.freeze({ variant: 'commentary-chart', family: 'evidence', purposes: ['decide', 'explain'], evidence: ['narrative'], novelty: 0 }),
|
|
117
|
+
Object.freeze({ variant: 'chart-led', family: 'data', purposes: ['monitor', 'compare'], evidence: ['chart'], modes: ['strong-fit', 'divergent'], novelty: 1 }),
|
|
118
|
+
]),
|
|
119
|
+
table: Object.freeze([
|
|
120
|
+
Object.freeze({ variant: 'table-led', family: 'evidence', modes: ['conservative', 'strong-fit'], novelty: 0 }),
|
|
121
|
+
Object.freeze({ variant: 'table-callout', family: 'comparison', modes: ['strong-fit', 'divergent'], novelty: 1 }),
|
|
122
|
+
]),
|
|
123
|
+
split: Object.freeze([
|
|
124
|
+
Object.freeze({ variant: 'visual-right', family: 'split', modes: ['conservative', 'strong-fit'], novelty: 0 }),
|
|
125
|
+
Object.freeze({ variant: 'visual-left', family: 'editorial', modes: ['strong-fit', 'divergent'], novelty: 1 }),
|
|
126
|
+
]),
|
|
127
|
+
content: Object.freeze([
|
|
128
|
+
Object.freeze({ variant: 'evidence-right', family: 'evidence', purposes: ['decide', 'inspect'], evidence: ['visual'], novelty: 0 }),
|
|
129
|
+
Object.freeze({ variant: 'editorial-wide', family: 'typography', purposes: ['explain'], evidence: ['narrative'], modes: ['strong-fit', 'divergent'], novelty: 1 }),
|
|
130
|
+
]),
|
|
131
|
+
closing: Object.freeze([
|
|
132
|
+
Object.freeze({ variant: 'decision-left', family: 'decision', modes: ['conservative', 'strong-fit'], novelty: 0 }),
|
|
133
|
+
Object.freeze({ variant: 'decision-focus', family: 'minimal', modes: ['strong-fit', 'divergent'], novelty: 1 }),
|
|
134
|
+
]),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
function plainObject(value) {
|
|
138
|
+
return value && typeof value === 'object' && !Array.isArray(value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function stableValue(value) {
|
|
142
|
+
if (Array.isArray(value)) return value.map(stableValue);
|
|
143
|
+
if (!plainObject(value)) return value;
|
|
144
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, stableValue(value[key])]));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function sha256(value) {
|
|
148
|
+
return createHash('sha256').update(JSON.stringify(stableValue(value))).digest('hex');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function bucket(value, boundaries = [0, 1, 3, 8]) {
|
|
152
|
+
const count = Math.max(0, Number(value) || 0);
|
|
153
|
+
if (count <= boundaries[0]) return '0';
|
|
154
|
+
if (count <= boundaries[1]) return '1';
|
|
155
|
+
if (count <= boundaries[2]) return 'few';
|
|
156
|
+
if (count <= boundaries[3]) return 'several';
|
|
157
|
+
return 'many';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function rowsOf(value) {
|
|
161
|
+
if (Array.isArray(value)) return value;
|
|
162
|
+
return Array.isArray(value?.values) ? value.values : [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function textCount(value) {
|
|
166
|
+
if (Array.isArray(value)) return value.filter((entry) => String(entry ?? '').trim()).length;
|
|
167
|
+
return value == null || value === '' ? 0 : 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function normalizePurpose(value, fallback) {
|
|
171
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
172
|
+
return PURPOSES.has(normalized) ? normalized : fallback;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizeExpressionMode(value) {
|
|
176
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
177
|
+
return EXPRESSION_MODES.has(normalized) ? normalized : 'strong-fit';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function defaultPurpose(format, input) {
|
|
181
|
+
if (format === 'xlsx') return 'monitor';
|
|
182
|
+
if (format === 'csv' || format === 'tsv') return 'inspect';
|
|
183
|
+
if (input?.content?.decision) return 'decide';
|
|
184
|
+
return 'explain';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function normalizedRecentCompositions(value) {
|
|
188
|
+
if (!Array.isArray(value)) return [];
|
|
189
|
+
return value
|
|
190
|
+
.filter(plainObject)
|
|
191
|
+
.slice(0, 24)
|
|
192
|
+
.map((entry) => ({
|
|
193
|
+
fingerprint: String(entry.fingerprint || ''),
|
|
194
|
+
format: String(entry.format || ''),
|
|
195
|
+
profile: String(entry.profile || ''),
|
|
196
|
+
purpose: String(entry.purpose || ''),
|
|
197
|
+
expressionMode: String(entry.expressionMode || ''),
|
|
198
|
+
compositionIds: Array.isArray(entry.compositionIds)
|
|
199
|
+
? entry.compositionIds.map((id) => String(id || '')).filter(Boolean).slice(0, 64)
|
|
200
|
+
: [],
|
|
201
|
+
createdAt: String(entry.createdAt || ''),
|
|
202
|
+
}));
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function resolveOfficeCompositionContext(format, input = {}, {
|
|
206
|
+
recentCompositions = [],
|
|
207
|
+
} = {}) {
|
|
208
|
+
const normalizedFormat = String(format || '').toLowerCase();
|
|
209
|
+
const composition = plainObject(input.composition) ? input.composition : {};
|
|
210
|
+
const fallbackPurpose = defaultPurpose(normalizedFormat, input);
|
|
211
|
+
return {
|
|
212
|
+
purpose: normalizePurpose(input.purpose || composition.purpose, fallbackPurpose),
|
|
213
|
+
expressionMode: normalizeExpressionMode(input.expressionMode || composition.expressionMode),
|
|
214
|
+
recentCompositions: normalizedRecentCompositions(recentCompositions),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function officeContentTopology(format, operation = {}, design = {}) {
|
|
219
|
+
const sections = Array.isArray(operation.sections) ? operation.sections : [];
|
|
220
|
+
const tableRows = Math.max(
|
|
221
|
+
rowsOf(operation.table).length,
|
|
222
|
+
...sections.map((section) => rowsOf(section?.table).length),
|
|
223
|
+
);
|
|
224
|
+
const metrics = Array.isArray(operation.metrics) ? operation.metrics.length : 0;
|
|
225
|
+
const columns = Array.isArray(operation.columns) ? operation.columns.length : 0;
|
|
226
|
+
const steps = Array.isArray(operation.steps) ? operation.steps.length : 0;
|
|
227
|
+
const rows = Array.isArray(operation.rows) ? operation.rows.length : 0;
|
|
228
|
+
const chartSeries = Array.isArray(operation.chart?.series) ? operation.chart.series.length : 0;
|
|
229
|
+
const paragraphs = textCount(operation.body)
|
|
230
|
+
+ textCount(operation.bullets)
|
|
231
|
+
+ sections.reduce((total, section) => (
|
|
232
|
+
total + textCount(section?.paragraphs || section?.body) + textCount(section?.bullets)
|
|
233
|
+
), 0);
|
|
234
|
+
const facts = Array.isArray(design.content?.facts) ? design.content.facts.length : 0;
|
|
235
|
+
const claims = Array.isArray(design.content?.claims) ? design.content.claims.length : 0;
|
|
236
|
+
const evidence = [];
|
|
237
|
+
if (metrics) evidence.push('metrics');
|
|
238
|
+
if (tableRows || rows) evidence.push('table');
|
|
239
|
+
if (operation.chart || chartSeries) evidence.push('chart');
|
|
240
|
+
if (operation.image || operation.imagePath) evidence.push('image');
|
|
241
|
+
if (operation.visualText) evidence.push('visual');
|
|
242
|
+
if (columns) evidence.push('comparison');
|
|
243
|
+
if (steps) evidence.push('process');
|
|
244
|
+
if (paragraphs) evidence.push('narrative');
|
|
245
|
+
if (facts) evidence.push('facts');
|
|
246
|
+
if (claims) evidence.push('claims');
|
|
247
|
+
const weight = metrics * 2 + columns * 2 + steps + Math.ceil((rows + tableRows) / 4) + paragraphs + claims;
|
|
248
|
+
const density = weight >= 14 ? 'dense' : weight <= 4 ? 'light' : 'balanced';
|
|
249
|
+
const signature = [
|
|
250
|
+
String(format || '').toLowerCase(),
|
|
251
|
+
`m:${bucket(metrics)}`,
|
|
252
|
+
`c:${bucket(columns)}`,
|
|
253
|
+
`s:${bucket(steps)}`,
|
|
254
|
+
`r:${bucket(rows + tableRows, [0, 3, 10, 24])}`,
|
|
255
|
+
`p:${bucket(paragraphs)}`,
|
|
256
|
+
`e:${evidence.sort().join(',')}`,
|
|
257
|
+
].join('|');
|
|
258
|
+
return {
|
|
259
|
+
metrics,
|
|
260
|
+
columns,
|
|
261
|
+
steps,
|
|
262
|
+
rows,
|
|
263
|
+
tableRows,
|
|
264
|
+
chartSeries,
|
|
265
|
+
paragraphs,
|
|
266
|
+
sections: sections.length,
|
|
267
|
+
facts,
|
|
268
|
+
claims,
|
|
269
|
+
evidence,
|
|
270
|
+
density,
|
|
271
|
+
signature,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function recentPenalty(context, candidateId) {
|
|
276
|
+
return context.recentCompositions.slice(0, 8).reduce((total, entry, index) => (
|
|
277
|
+
entry.compositionIds.includes(candidateId)
|
|
278
|
+
? total + Math.max(1, 4 - Math.floor(index / 2))
|
|
279
|
+
: total
|
|
280
|
+
), 0);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function stableTie(seed, id) {
|
|
284
|
+
return Number.parseInt(sha256(`${seed}\0${id}`).slice(0, 8), 16) / 0xFFFFFFFF;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function selectCandidate(candidates, {
|
|
288
|
+
context,
|
|
289
|
+
topology,
|
|
290
|
+
density,
|
|
291
|
+
seed,
|
|
292
|
+
usage,
|
|
293
|
+
explicitVariant = '',
|
|
294
|
+
}) {
|
|
295
|
+
const evidence = new Set(topology.evidence);
|
|
296
|
+
const ranked = candidates.map((candidate) => {
|
|
297
|
+
const candidateId = String(candidate.id || candidate.variant || '');
|
|
298
|
+
let score = 0;
|
|
299
|
+
if (candidate.purposes?.includes(context.purpose)) score += 18;
|
|
300
|
+
else if (candidate.purposes?.length) score -= 6;
|
|
301
|
+
if (candidate.modes?.includes(context.expressionMode)) score += 5;
|
|
302
|
+
if (candidate.densities?.includes(density || topology.density)) score += 3;
|
|
303
|
+
score += (candidate.evidence || []).filter((entry) => evidence.has(entry)).length * 3;
|
|
304
|
+
if (context.expressionMode === 'divergent') score += Number(candidate.novelty || 0) * 3;
|
|
305
|
+
if (context.expressionMode === 'conservative') score -= Number(candidate.novelty || 0) * 2;
|
|
306
|
+
if (explicitVariant && (candidate.variant === explicitVariant || candidate.id === explicitVariant)) score += 100;
|
|
307
|
+
const historyPenalty = recentPenalty(context, candidateId);
|
|
308
|
+
score -= historyPenalty;
|
|
309
|
+
score -= (usage.get(candidateId) || 0) * 7;
|
|
310
|
+
score += stableTie(seed, candidateId);
|
|
311
|
+
return { candidate, candidateId, score, historyPenalty };
|
|
312
|
+
});
|
|
313
|
+
ranked.sort((left, right) => right.score - left.score || left.candidateId.localeCompare(right.candidateId));
|
|
314
|
+
return ranked[0];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function resolvedPptxKind(operation, topology) {
|
|
318
|
+
const requested = String(operation.kind || 'content').toLowerCase();
|
|
319
|
+
if (requested !== 'content' || operation.layoutId) return requested;
|
|
320
|
+
if (operation.chart) return 'chart';
|
|
321
|
+
if (operation.table) return 'table';
|
|
322
|
+
if (topology.metrics >= 2) return 'metrics';
|
|
323
|
+
if (topology.columns >= 2) return 'comparison';
|
|
324
|
+
if (topology.steps >= 2) return 'process';
|
|
325
|
+
if (operation.image || operation.imagePath) return 'split';
|
|
326
|
+
return 'content';
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function planOfficeComposition(format, operation = {}, design = {}, {
|
|
330
|
+
usage = new Map(),
|
|
331
|
+
} = {}) {
|
|
332
|
+
const normalizedFormat = String(format || '').toLowerCase();
|
|
333
|
+
const topology = officeContentTopology(normalizedFormat, operation, design);
|
|
334
|
+
const context = {
|
|
335
|
+
purpose: normalizePurpose(operation.purpose, design.purpose || 'explain'),
|
|
336
|
+
expressionMode: normalizeExpressionMode(operation.expressionMode || design.expressionMode),
|
|
337
|
+
recentCompositions: normalizedRecentCompositions(design.recentCompositions),
|
|
338
|
+
};
|
|
339
|
+
const seed = design.content?.fingerprint
|
|
340
|
+
|| [design.intent, design.audience, design.signature, operation.title, topology.signature].join('|');
|
|
341
|
+
if (normalizedFormat === 'pptx') {
|
|
342
|
+
const kind = resolvedPptxKind(operation, topology);
|
|
343
|
+
const explicitVariant = String(operation.variant || '').trim().toLowerCase();
|
|
344
|
+
const variants = PPTX_VARIANTS[kind] || PPTX_VARIANTS.content;
|
|
345
|
+
const candidates = explicitVariant && !variants.some((entry) => entry.variant === explicitVariant)
|
|
346
|
+
? [...variants, { variant: explicitVariant, family: kind, novelty: 0 }]
|
|
347
|
+
: variants;
|
|
348
|
+
const selected = selectCandidate(candidates.map((entry) => ({
|
|
349
|
+
...entry,
|
|
350
|
+
id: `${kind}:${entry.variant}`,
|
|
351
|
+
purposes: entry.purposes || [context.purpose],
|
|
352
|
+
})), {
|
|
353
|
+
context,
|
|
354
|
+
topology,
|
|
355
|
+
density: design.density,
|
|
356
|
+
seed,
|
|
357
|
+
usage,
|
|
358
|
+
explicitVariant,
|
|
359
|
+
});
|
|
360
|
+
const result = {
|
|
361
|
+
id: selected.candidateId,
|
|
362
|
+
family: selected.candidate.family,
|
|
363
|
+
kind,
|
|
364
|
+
variant: selected.candidate.variant,
|
|
365
|
+
purpose: context.purpose,
|
|
366
|
+
expressionMode: context.expressionMode,
|
|
367
|
+
topology,
|
|
368
|
+
historyPenalty: selected.historyPenalty,
|
|
369
|
+
source: explicitVariant ? 'explicit' : 'content-planner',
|
|
370
|
+
};
|
|
371
|
+
usage.set(result.id, (usage.get(result.id) || 0) + 1);
|
|
372
|
+
return result;
|
|
373
|
+
}
|
|
374
|
+
const candidates = FORMAT_CANDIDATES[normalizedFormat] || [{
|
|
375
|
+
id: `${normalizedFormat || 'office'}-default`,
|
|
376
|
+
family: 'default',
|
|
377
|
+
purposes: [context.purpose],
|
|
378
|
+
modes: [context.expressionMode],
|
|
379
|
+
novelty: 0,
|
|
380
|
+
}];
|
|
381
|
+
const selected = selectCandidate(candidates, {
|
|
382
|
+
context,
|
|
383
|
+
topology,
|
|
384
|
+
density: design.density,
|
|
385
|
+
seed,
|
|
386
|
+
usage,
|
|
387
|
+
explicitVariant: String(operation.variant || '').trim().toLowerCase(),
|
|
388
|
+
});
|
|
389
|
+
const result = {
|
|
390
|
+
id: selected.candidateId,
|
|
391
|
+
family: selected.candidate.family,
|
|
392
|
+
purpose: context.purpose,
|
|
393
|
+
expressionMode: context.expressionMode,
|
|
394
|
+
topology,
|
|
395
|
+
historyPenalty: selected.historyPenalty,
|
|
396
|
+
source: operation.variant ? 'explicit' : 'content-planner',
|
|
397
|
+
};
|
|
398
|
+
usage.set(result.id, (usage.get(result.id) || 0) + 1);
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
export function summarizeOfficeCompositions(format, compositions = []) {
|
|
403
|
+
const normalized = (Array.isArray(compositions) ? compositions : [])
|
|
404
|
+
.map((entry) => entry?.composition || entry)
|
|
405
|
+
.filter((entry) => plainObject(entry) && entry.id)
|
|
406
|
+
.map((entry) => ({
|
|
407
|
+
id: String(entry.id),
|
|
408
|
+
family: String(entry.family || ''),
|
|
409
|
+
kind: String(entry.kind || ''),
|
|
410
|
+
purpose: String(entry.purpose || ''),
|
|
411
|
+
topology: String(entry.topology?.signature || ''),
|
|
412
|
+
}));
|
|
413
|
+
if (!normalized.length) {
|
|
414
|
+
return {
|
|
415
|
+
format: String(format || '').toLowerCase(),
|
|
416
|
+
fingerprint: '',
|
|
417
|
+
compositionIds: [],
|
|
418
|
+
count: 0,
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
return {
|
|
422
|
+
format: String(format || '').toLowerCase(),
|
|
423
|
+
fingerprint: sha256({ format: String(format || '').toLowerCase(), sequence: normalized }),
|
|
424
|
+
compositionIds: normalized.map((entry) => entry.id),
|
|
425
|
+
count: normalized.length,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
export function reviewOfficeCompositionSequence({
|
|
430
|
+
format,
|
|
431
|
+
compositions = [],
|
|
432
|
+
recentCompositions = [],
|
|
433
|
+
allowRepetition = false,
|
|
434
|
+
} = {}) {
|
|
435
|
+
const plans = (Array.isArray(compositions) ? compositions : [])
|
|
436
|
+
.map((entry) => entry?.composition || entry)
|
|
437
|
+
.filter((entry) => plainObject(entry) && entry.id);
|
|
438
|
+
const summary = summarizeOfficeCompositions(format, plans);
|
|
439
|
+
if (allowRepetition || !plans.length) return { summary, repeated: null, recentMatch: null };
|
|
440
|
+
const eligible = plans.filter((entry) => !['cover', 'closing'].includes(String(entry.kind || '')));
|
|
441
|
+
const counts = new Map();
|
|
442
|
+
for (const entry of eligible) counts.set(entry.id, (counts.get(entry.id) || 0) + 1);
|
|
443
|
+
const [repeatedId = '', repeatedCount = 0] = [...counts.entries()]
|
|
444
|
+
.sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0]))[0] || [];
|
|
445
|
+
const repeated = eligible.length >= 4 && repeatedCount / eligible.length >= 0.75
|
|
446
|
+
? { id: repeatedId, count: repeatedCount, total: eligible.length }
|
|
447
|
+
: null;
|
|
448
|
+
const recentMatch = normalizedRecentCompositions(recentCompositions)
|
|
449
|
+
.find((entry) => entry.fingerprint && entry.fingerprint === summary.fingerprint) || null;
|
|
450
|
+
return { summary, repeated, recentMatch };
|
|
451
|
+
}
|