@vsuryav/agent-sim 0.1.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.
Files changed (154) hide show
  1. package/README.md +25 -0
  2. package/bin/agent-sim.js +25 -0
  3. package/package.json +72 -0
  4. package/src/app-paths.ts +29 -0
  5. package/src/app-sync.test.ts +75 -0
  6. package/src/app-sync.ts +110 -0
  7. package/src/cli.ts +129 -0
  8. package/src/collector/claude-code.test.ts +102 -0
  9. package/src/collector/claude-code.ts +133 -0
  10. package/src/collector/codex-cli.test.ts +116 -0
  11. package/src/collector/codex-cli.ts +149 -0
  12. package/src/collector/db.test.ts +59 -0
  13. package/src/collector/db.ts +125 -0
  14. package/src/collector/names.test.ts +21 -0
  15. package/src/collector/names.ts +28 -0
  16. package/src/collector/personality.test.ts +40 -0
  17. package/src/collector/personality.ts +46 -0
  18. package/src/collector/remote-sync.test.ts +31 -0
  19. package/src/collector/remote-sync.ts +171 -0
  20. package/src/collector/sync.test.ts +67 -0
  21. package/src/collector/sync.ts +148 -0
  22. package/src/collector/types.ts +1 -0
  23. package/src/engine/bootstrap/state.ts +3 -0
  24. package/src/engine/buddy/CompanionSprite.tsx +371 -0
  25. package/src/engine/buddy/companion.ts +133 -0
  26. package/src/engine/buddy/prompt.ts +36 -0
  27. package/src/engine/buddy/sprites.ts +514 -0
  28. package/src/engine/buddy/types.ts +148 -0
  29. package/src/engine/buddy/useBuddyNotification.tsx +98 -0
  30. package/src/engine/ink/Ansi.tsx +292 -0
  31. package/src/engine/ink/bidi.ts +139 -0
  32. package/src/engine/ink/clearTerminal.ts +74 -0
  33. package/src/engine/ink/colorize.ts +231 -0
  34. package/src/engine/ink/components/AlternateScreen.tsx +80 -0
  35. package/src/engine/ink/components/App.tsx +658 -0
  36. package/src/engine/ink/components/AppContext.ts +21 -0
  37. package/src/engine/ink/components/Box.tsx +214 -0
  38. package/src/engine/ink/components/Button.tsx +192 -0
  39. package/src/engine/ink/components/ClockContext.tsx +112 -0
  40. package/src/engine/ink/components/CursorDeclarationContext.ts +32 -0
  41. package/src/engine/ink/components/ErrorOverview.tsx +109 -0
  42. package/src/engine/ink/components/Link.tsx +42 -0
  43. package/src/engine/ink/components/Newline.tsx +39 -0
  44. package/src/engine/ink/components/NoSelect.tsx +68 -0
  45. package/src/engine/ink/components/RawAnsi.tsx +57 -0
  46. package/src/engine/ink/components/ScrollBox.tsx +237 -0
  47. package/src/engine/ink/components/Spacer.tsx +20 -0
  48. package/src/engine/ink/components/StdinContext.ts +49 -0
  49. package/src/engine/ink/components/TerminalFocusContext.tsx +52 -0
  50. package/src/engine/ink/components/TerminalSizeContext.tsx +7 -0
  51. package/src/engine/ink/components/Text.tsx +254 -0
  52. package/src/engine/ink/constants.ts +2 -0
  53. package/src/engine/ink/dom.ts +484 -0
  54. package/src/engine/ink/events/click-event.ts +38 -0
  55. package/src/engine/ink/events/dispatcher.ts +233 -0
  56. package/src/engine/ink/events/emitter.ts +39 -0
  57. package/src/engine/ink/events/event-handlers.ts +73 -0
  58. package/src/engine/ink/events/event.ts +11 -0
  59. package/src/engine/ink/events/focus-event.ts +21 -0
  60. package/src/engine/ink/events/input-event.ts +205 -0
  61. package/src/engine/ink/events/keyboard-event.ts +51 -0
  62. package/src/engine/ink/events/terminal-event.ts +107 -0
  63. package/src/engine/ink/events/terminal-focus-event.ts +19 -0
  64. package/src/engine/ink/focus.ts +181 -0
  65. package/src/engine/ink/frame.ts +124 -0
  66. package/src/engine/ink/get-max-width.ts +27 -0
  67. package/src/engine/ink/global.d.ts +18 -0
  68. package/src/engine/ink/hit-test.ts +130 -0
  69. package/src/engine/ink/hooks/use-animation-frame.ts +57 -0
  70. package/src/engine/ink/hooks/use-app.ts +8 -0
  71. package/src/engine/ink/hooks/use-declared-cursor.ts +73 -0
  72. package/src/engine/ink/hooks/use-input.ts +92 -0
  73. package/src/engine/ink/hooks/use-interval.ts +67 -0
  74. package/src/engine/ink/hooks/use-search-highlight.ts +53 -0
  75. package/src/engine/ink/hooks/use-selection.ts +104 -0
  76. package/src/engine/ink/hooks/use-stdin.ts +8 -0
  77. package/src/engine/ink/hooks/use-tab-status.ts +72 -0
  78. package/src/engine/ink/hooks/use-terminal-focus.ts +16 -0
  79. package/src/engine/ink/hooks/use-terminal-title.ts +31 -0
  80. package/src/engine/ink/hooks/use-terminal-viewport.ts +96 -0
  81. package/src/engine/ink/ink.tsx +1723 -0
  82. package/src/engine/ink/instances.ts +10 -0
  83. package/src/engine/ink/layout/engine.ts +6 -0
  84. package/src/engine/ink/layout/geometry.ts +97 -0
  85. package/src/engine/ink/layout/node.ts +152 -0
  86. package/src/engine/ink/layout/yoga.ts +308 -0
  87. package/src/engine/ink/line-width-cache.ts +24 -0
  88. package/src/engine/ink/log-update.ts +773 -0
  89. package/src/engine/ink/measure-element.ts +23 -0
  90. package/src/engine/ink/measure-text.ts +47 -0
  91. package/src/engine/ink/node-cache.ts +54 -0
  92. package/src/engine/ink/optimizer.ts +93 -0
  93. package/src/engine/ink/output.ts +797 -0
  94. package/src/engine/ink/parse-keypress.ts +801 -0
  95. package/src/engine/ink/reconciler.ts +512 -0
  96. package/src/engine/ink/render-border.ts +231 -0
  97. package/src/engine/ink/render-node-to-output.ts +1462 -0
  98. package/src/engine/ink/render-to-screen.ts +231 -0
  99. package/src/engine/ink/renderer.ts +178 -0
  100. package/src/engine/ink/root.ts +184 -0
  101. package/src/engine/ink/screen.ts +1486 -0
  102. package/src/engine/ink/searchHighlight.ts +93 -0
  103. package/src/engine/ink/selection.ts +917 -0
  104. package/src/engine/ink/squash-text-nodes.ts +92 -0
  105. package/src/engine/ink/stringWidth.ts +222 -0
  106. package/src/engine/ink/styles.ts +771 -0
  107. package/src/engine/ink/supports-hyperlinks.ts +57 -0
  108. package/src/engine/ink/tabstops.ts +46 -0
  109. package/src/engine/ink/terminal-focus-state.ts +47 -0
  110. package/src/engine/ink/terminal-querier.ts +212 -0
  111. package/src/engine/ink/terminal.ts +248 -0
  112. package/src/engine/ink/termio/ansi.ts +75 -0
  113. package/src/engine/ink/termio/csi.ts +319 -0
  114. package/src/engine/ink/termio/dec.ts +60 -0
  115. package/src/engine/ink/termio/esc.ts +67 -0
  116. package/src/engine/ink/termio/osc.ts +493 -0
  117. package/src/engine/ink/termio/parser.ts +394 -0
  118. package/src/engine/ink/termio/sgr.ts +308 -0
  119. package/src/engine/ink/termio/tokenize.ts +319 -0
  120. package/src/engine/ink/termio/types.ts +236 -0
  121. package/src/engine/ink/useTerminalNotification.ts +126 -0
  122. package/src/engine/ink/warn.ts +9 -0
  123. package/src/engine/ink/widest-line.ts +19 -0
  124. package/src/engine/ink/wrap-text.ts +74 -0
  125. package/src/engine/ink/wrapAnsi.ts +20 -0
  126. package/src/engine/native-ts/yoga-layout/enums.ts +134 -0
  127. package/src/engine/native-ts/yoga-layout/index.ts +2578 -0
  128. package/src/engine/stubs/bootstrap-state.ts +4 -0
  129. package/src/engine/stubs/debug.ts +6 -0
  130. package/src/engine/stubs/log.ts +4 -0
  131. package/src/engine/utils/debug.ts +5 -0
  132. package/src/engine/utils/earlyInput.ts +4 -0
  133. package/src/engine/utils/env.ts +15 -0
  134. package/src/engine/utils/envUtils.ts +4 -0
  135. package/src/engine/utils/execFileNoThrow.ts +24 -0
  136. package/src/engine/utils/fullscreen.ts +4 -0
  137. package/src/engine/utils/intl.ts +9 -0
  138. package/src/engine/utils/log.ts +3 -0
  139. package/src/engine/utils/semver.ts +13 -0
  140. package/src/engine/utils/sliceAnsi.ts +10 -0
  141. package/src/engine/utils/theme.ts +17 -0
  142. package/src/game/App.tsx +141 -0
  143. package/src/game/agents/behavior.ts +249 -0
  144. package/src/game/agents/speech.ts +57 -0
  145. package/src/game/canvas.ts +98 -0
  146. package/src/game/launch.ts +36 -0
  147. package/src/game/ship/ShipView.tsx +145 -0
  148. package/src/game/ship/ship-map.ts +172 -0
  149. package/src/game/ui/AgentBio.tsx +72 -0
  150. package/src/game/ui/HUD.tsx +63 -0
  151. package/src/game/ui/StatusBar.tsx +49 -0
  152. package/src/game/useKeyboard.ts +62 -0
  153. package/src/main.tsx +22 -0
  154. package/src/run-interactive.ts +74 -0
@@ -0,0 +1,10 @@
1
+ // Store all instances of Ink (instance.js) to ensure that consecutive render() calls
2
+ // use the same instance of Ink and don't create a new one
3
+ //
4
+ // This map has to be stored in a separate file, because render.js creates instances,
5
+ // but instance.js should delete itself from the map on unmount
6
+
7
+ import type Ink from './ink.js'
8
+
9
+ const instances = new Map<NodeJS.WriteStream, Ink>()
10
+ export default instances
@@ -0,0 +1,6 @@
1
+ import type { LayoutNode } from './node.js'
2
+ import { createYogaLayoutNode } from './yoga.js'
3
+
4
+ export function createLayoutNode(): LayoutNode {
5
+ return createYogaLayoutNode()
6
+ }
@@ -0,0 +1,97 @@
1
+ export type Point = {
2
+ x: number
3
+ y: number
4
+ }
5
+
6
+ export type Size = {
7
+ width: number
8
+ height: number
9
+ }
10
+
11
+ export type Rectangle = Point & Size
12
+
13
+ /** Edge insets (padding, margin, border) */
14
+ export type Edges = {
15
+ top: number
16
+ right: number
17
+ bottom: number
18
+ left: number
19
+ }
20
+
21
+ /** Create uniform edges */
22
+ export function edges(all: number): Edges
23
+ export function edges(vertical: number, horizontal: number): Edges
24
+ export function edges(
25
+ top: number,
26
+ right: number,
27
+ bottom: number,
28
+ left: number,
29
+ ): Edges
30
+ export function edges(a: number, b?: number, c?: number, d?: number): Edges {
31
+ if (b === undefined) {
32
+ return { top: a, right: a, bottom: a, left: a }
33
+ }
34
+ if (c === undefined) {
35
+ return { top: a, right: b, bottom: a, left: b }
36
+ }
37
+ return { top: a, right: b, bottom: c, left: d! }
38
+ }
39
+
40
+ /** Add two edge values */
41
+ export function addEdges(a: Edges, b: Edges): Edges {
42
+ return {
43
+ top: a.top + b.top,
44
+ right: a.right + b.right,
45
+ bottom: a.bottom + b.bottom,
46
+ left: a.left + b.left,
47
+ }
48
+ }
49
+
50
+ /** Zero edges constant */
51
+ export const ZERO_EDGES: Edges = { top: 0, right: 0, bottom: 0, left: 0 }
52
+
53
+ /** Convert partial edges to full edges with defaults */
54
+ export function resolveEdges(partial?: Partial<Edges>): Edges {
55
+ return {
56
+ top: partial?.top ?? 0,
57
+ right: partial?.right ?? 0,
58
+ bottom: partial?.bottom ?? 0,
59
+ left: partial?.left ?? 0,
60
+ }
61
+ }
62
+
63
+ export function unionRect(a: Rectangle, b: Rectangle): Rectangle {
64
+ const minX = Math.min(a.x, b.x)
65
+ const minY = Math.min(a.y, b.y)
66
+ const maxX = Math.max(a.x + a.width, b.x + b.width)
67
+ const maxY = Math.max(a.y + a.height, b.y + b.height)
68
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }
69
+ }
70
+
71
+ export function clampRect(rect: Rectangle, size: Size): Rectangle {
72
+ const minX = Math.max(0, rect.x)
73
+ const minY = Math.max(0, rect.y)
74
+ const maxX = Math.min(size.width - 1, rect.x + rect.width - 1)
75
+ const maxY = Math.min(size.height - 1, rect.y + rect.height - 1)
76
+ return {
77
+ x: minX,
78
+ y: minY,
79
+ width: Math.max(0, maxX - minX + 1),
80
+ height: Math.max(0, maxY - minY + 1),
81
+ }
82
+ }
83
+
84
+ export function withinBounds(size: Size, point: Point): boolean {
85
+ return (
86
+ point.x >= 0 &&
87
+ point.y >= 0 &&
88
+ point.x < size.width &&
89
+ point.y < size.height
90
+ )
91
+ }
92
+
93
+ export function clamp(value: number, min?: number, max?: number): number {
94
+ if (min !== undefined && value < min) return min
95
+ if (max !== undefined && value > max) return max
96
+ return value
97
+ }
@@ -0,0 +1,152 @@
1
+ // --
2
+ // Adapter interface for the layout engine (Yoga)
3
+
4
+ export const LayoutEdge = {
5
+ All: 'all',
6
+ Horizontal: 'horizontal',
7
+ Vertical: 'vertical',
8
+ Left: 'left',
9
+ Right: 'right',
10
+ Top: 'top',
11
+ Bottom: 'bottom',
12
+ Start: 'start',
13
+ End: 'end',
14
+ } as const
15
+ export type LayoutEdge = (typeof LayoutEdge)[keyof typeof LayoutEdge]
16
+
17
+ export const LayoutGutter = {
18
+ All: 'all',
19
+ Column: 'column',
20
+ Row: 'row',
21
+ } as const
22
+ export type LayoutGutter = (typeof LayoutGutter)[keyof typeof LayoutGutter]
23
+
24
+ export const LayoutDisplay = {
25
+ Flex: 'flex',
26
+ None: 'none',
27
+ } as const
28
+ export type LayoutDisplay = (typeof LayoutDisplay)[keyof typeof LayoutDisplay]
29
+
30
+ export const LayoutFlexDirection = {
31
+ Row: 'row',
32
+ RowReverse: 'row-reverse',
33
+ Column: 'column',
34
+ ColumnReverse: 'column-reverse',
35
+ } as const
36
+ export type LayoutFlexDirection =
37
+ (typeof LayoutFlexDirection)[keyof typeof LayoutFlexDirection]
38
+
39
+ export const LayoutAlign = {
40
+ Auto: 'auto',
41
+ Stretch: 'stretch',
42
+ FlexStart: 'flex-start',
43
+ Center: 'center',
44
+ FlexEnd: 'flex-end',
45
+ } as const
46
+ export type LayoutAlign = (typeof LayoutAlign)[keyof typeof LayoutAlign]
47
+
48
+ export const LayoutJustify = {
49
+ FlexStart: 'flex-start',
50
+ Center: 'center',
51
+ FlexEnd: 'flex-end',
52
+ SpaceBetween: 'space-between',
53
+ SpaceAround: 'space-around',
54
+ SpaceEvenly: 'space-evenly',
55
+ } as const
56
+ export type LayoutJustify = (typeof LayoutJustify)[keyof typeof LayoutJustify]
57
+
58
+ export const LayoutWrap = {
59
+ NoWrap: 'nowrap',
60
+ Wrap: 'wrap',
61
+ WrapReverse: 'wrap-reverse',
62
+ } as const
63
+ export type LayoutWrap = (typeof LayoutWrap)[keyof typeof LayoutWrap]
64
+
65
+ export const LayoutPositionType = {
66
+ Relative: 'relative',
67
+ Absolute: 'absolute',
68
+ } as const
69
+ export type LayoutPositionType =
70
+ (typeof LayoutPositionType)[keyof typeof LayoutPositionType]
71
+
72
+ export const LayoutOverflow = {
73
+ Visible: 'visible',
74
+ Hidden: 'hidden',
75
+ Scroll: 'scroll',
76
+ } as const
77
+ export type LayoutOverflow =
78
+ (typeof LayoutOverflow)[keyof typeof LayoutOverflow]
79
+
80
+ export type LayoutMeasureFunc = (
81
+ width: number,
82
+ widthMode: LayoutMeasureMode,
83
+ ) => { width: number; height: number }
84
+
85
+ export const LayoutMeasureMode = {
86
+ Undefined: 'undefined',
87
+ Exactly: 'exactly',
88
+ AtMost: 'at-most',
89
+ } as const
90
+ export type LayoutMeasureMode =
91
+ (typeof LayoutMeasureMode)[keyof typeof LayoutMeasureMode]
92
+
93
+ export type LayoutNode = {
94
+ // Tree
95
+ insertChild(child: LayoutNode, index: number): void
96
+ removeChild(child: LayoutNode): void
97
+ getChildCount(): number
98
+ getParent(): LayoutNode | null
99
+
100
+ // Layout computation
101
+ calculateLayout(width?: number, height?: number): void
102
+ setMeasureFunc(fn: LayoutMeasureFunc): void
103
+ unsetMeasureFunc(): void
104
+ markDirty(): void
105
+
106
+ // Layout reading (post-layout)
107
+ getComputedLeft(): number
108
+ getComputedTop(): number
109
+ getComputedWidth(): number
110
+ getComputedHeight(): number
111
+ getComputedBorder(edge: LayoutEdge): number
112
+ getComputedPadding(edge: LayoutEdge): number
113
+
114
+ // Style setters
115
+ setWidth(value: number): void
116
+ setWidthPercent(value: number): void
117
+ setWidthAuto(): void
118
+ setHeight(value: number): void
119
+ setHeightPercent(value: number): void
120
+ setHeightAuto(): void
121
+ setMinWidth(value: number): void
122
+ setMinWidthPercent(value: number): void
123
+ setMinHeight(value: number): void
124
+ setMinHeightPercent(value: number): void
125
+ setMaxWidth(value: number): void
126
+ setMaxWidthPercent(value: number): void
127
+ setMaxHeight(value: number): void
128
+ setMaxHeightPercent(value: number): void
129
+ setFlexDirection(dir: LayoutFlexDirection): void
130
+ setFlexGrow(value: number): void
131
+ setFlexShrink(value: number): void
132
+ setFlexBasis(value: number): void
133
+ setFlexBasisPercent(value: number): void
134
+ setFlexWrap(wrap: LayoutWrap): void
135
+ setAlignItems(align: LayoutAlign): void
136
+ setAlignSelf(align: LayoutAlign): void
137
+ setJustifyContent(justify: LayoutJustify): void
138
+ setDisplay(display: LayoutDisplay): void
139
+ getDisplay(): LayoutDisplay
140
+ setPositionType(type: LayoutPositionType): void
141
+ setPosition(edge: LayoutEdge, value: number): void
142
+ setPositionPercent(edge: LayoutEdge, value: number): void
143
+ setOverflow(overflow: LayoutOverflow): void
144
+ setMargin(edge: LayoutEdge, value: number): void
145
+ setPadding(edge: LayoutEdge, value: number): void
146
+ setBorder(edge: LayoutEdge, value: number): void
147
+ setGap(gutter: LayoutGutter, value: number): void
148
+
149
+ // Lifecycle
150
+ free(): void
151
+ freeRecursive(): void
152
+ }
@@ -0,0 +1,308 @@
1
+ import Yoga, {
2
+ Align,
3
+ Direction,
4
+ Display,
5
+ Edge,
6
+ FlexDirection,
7
+ Gutter,
8
+ Justify,
9
+ MeasureMode,
10
+ Overflow,
11
+ PositionType,
12
+ Wrap,
13
+ type Node as YogaNode,
14
+ } from '../../native-ts/yoga-layout/index.js'
15
+ import {
16
+ type LayoutAlign,
17
+ LayoutDisplay,
18
+ type LayoutEdge,
19
+ type LayoutFlexDirection,
20
+ type LayoutGutter,
21
+ type LayoutJustify,
22
+ type LayoutMeasureFunc,
23
+ LayoutMeasureMode,
24
+ type LayoutNode,
25
+ type LayoutOverflow,
26
+ type LayoutPositionType,
27
+ type LayoutWrap,
28
+ } from './node.js'
29
+
30
+ // --
31
+ // Edge/Gutter mapping
32
+
33
+ const EDGE_MAP: Record<LayoutEdge, Edge> = {
34
+ all: Edge.All,
35
+ horizontal: Edge.Horizontal,
36
+ vertical: Edge.Vertical,
37
+ left: Edge.Left,
38
+ right: Edge.Right,
39
+ top: Edge.Top,
40
+ bottom: Edge.Bottom,
41
+ start: Edge.Start,
42
+ end: Edge.End,
43
+ }
44
+
45
+ const GUTTER_MAP: Record<LayoutGutter, Gutter> = {
46
+ all: Gutter.All,
47
+ column: Gutter.Column,
48
+ row: Gutter.Row,
49
+ }
50
+
51
+ // --
52
+ // Yoga adapter
53
+
54
+ export class YogaLayoutNode implements LayoutNode {
55
+ readonly yoga: YogaNode
56
+
57
+ constructor(yoga: YogaNode) {
58
+ this.yoga = yoga
59
+ }
60
+
61
+ // Tree
62
+
63
+ insertChild(child: LayoutNode, index: number): void {
64
+ this.yoga.insertChild((child as YogaLayoutNode).yoga, index)
65
+ }
66
+
67
+ removeChild(child: LayoutNode): void {
68
+ this.yoga.removeChild((child as YogaLayoutNode).yoga)
69
+ }
70
+
71
+ getChildCount(): number {
72
+ return this.yoga.getChildCount()
73
+ }
74
+
75
+ getParent(): LayoutNode | null {
76
+ const p = this.yoga.getParent()
77
+ return p ? new YogaLayoutNode(p) : null
78
+ }
79
+
80
+ // Layout
81
+
82
+ calculateLayout(width?: number, _height?: number): void {
83
+ this.yoga.calculateLayout(width, undefined, Direction.LTR)
84
+ }
85
+
86
+ setMeasureFunc(fn: LayoutMeasureFunc): void {
87
+ this.yoga.setMeasureFunc((w, wMode) => {
88
+ const mode =
89
+ wMode === MeasureMode.Exactly
90
+ ? LayoutMeasureMode.Exactly
91
+ : wMode === MeasureMode.AtMost
92
+ ? LayoutMeasureMode.AtMost
93
+ : LayoutMeasureMode.Undefined
94
+ return fn(w, mode)
95
+ })
96
+ }
97
+
98
+ unsetMeasureFunc(): void {
99
+ this.yoga.unsetMeasureFunc()
100
+ }
101
+
102
+ markDirty(): void {
103
+ this.yoga.markDirty()
104
+ }
105
+
106
+ // Computed layout
107
+
108
+ getComputedLeft(): number {
109
+ return this.yoga.getComputedLeft()
110
+ }
111
+
112
+ getComputedTop(): number {
113
+ return this.yoga.getComputedTop()
114
+ }
115
+
116
+ getComputedWidth(): number {
117
+ return this.yoga.getComputedWidth()
118
+ }
119
+
120
+ getComputedHeight(): number {
121
+ return this.yoga.getComputedHeight()
122
+ }
123
+
124
+ getComputedBorder(edge: LayoutEdge): number {
125
+ return this.yoga.getComputedBorder(EDGE_MAP[edge]!)
126
+ }
127
+
128
+ getComputedPadding(edge: LayoutEdge): number {
129
+ return this.yoga.getComputedPadding(EDGE_MAP[edge]!)
130
+ }
131
+
132
+ // Style setters
133
+
134
+ setWidth(value: number): void {
135
+ this.yoga.setWidth(value)
136
+ }
137
+ setWidthPercent(value: number): void {
138
+ this.yoga.setWidthPercent(value)
139
+ }
140
+ setWidthAuto(): void {
141
+ this.yoga.setWidthAuto()
142
+ }
143
+ setHeight(value: number): void {
144
+ this.yoga.setHeight(value)
145
+ }
146
+ setHeightPercent(value: number): void {
147
+ this.yoga.setHeightPercent(value)
148
+ }
149
+ setHeightAuto(): void {
150
+ this.yoga.setHeightAuto()
151
+ }
152
+ setMinWidth(value: number): void {
153
+ this.yoga.setMinWidth(value)
154
+ }
155
+ setMinWidthPercent(value: number): void {
156
+ this.yoga.setMinWidthPercent(value)
157
+ }
158
+ setMinHeight(value: number): void {
159
+ this.yoga.setMinHeight(value)
160
+ }
161
+ setMinHeightPercent(value: number): void {
162
+ this.yoga.setMinHeightPercent(value)
163
+ }
164
+ setMaxWidth(value: number): void {
165
+ this.yoga.setMaxWidth(value)
166
+ }
167
+ setMaxWidthPercent(value: number): void {
168
+ this.yoga.setMaxWidthPercent(value)
169
+ }
170
+ setMaxHeight(value: number): void {
171
+ this.yoga.setMaxHeight(value)
172
+ }
173
+ setMaxHeightPercent(value: number): void {
174
+ this.yoga.setMaxHeightPercent(value)
175
+ }
176
+
177
+ setFlexDirection(dir: LayoutFlexDirection): void {
178
+ const map: Record<LayoutFlexDirection, FlexDirection> = {
179
+ row: FlexDirection.Row,
180
+ 'row-reverse': FlexDirection.RowReverse,
181
+ column: FlexDirection.Column,
182
+ 'column-reverse': FlexDirection.ColumnReverse,
183
+ }
184
+ this.yoga.setFlexDirection(map[dir]!)
185
+ }
186
+
187
+ setFlexGrow(value: number): void {
188
+ this.yoga.setFlexGrow(value)
189
+ }
190
+ setFlexShrink(value: number): void {
191
+ this.yoga.setFlexShrink(value)
192
+ }
193
+ setFlexBasis(value: number): void {
194
+ this.yoga.setFlexBasis(value)
195
+ }
196
+ setFlexBasisPercent(value: number): void {
197
+ this.yoga.setFlexBasisPercent(value)
198
+ }
199
+
200
+ setFlexWrap(wrap: LayoutWrap): void {
201
+ const map: Record<LayoutWrap, Wrap> = {
202
+ nowrap: Wrap.NoWrap,
203
+ wrap: Wrap.Wrap,
204
+ 'wrap-reverse': Wrap.WrapReverse,
205
+ }
206
+ this.yoga.setFlexWrap(map[wrap]!)
207
+ }
208
+
209
+ setAlignItems(align: LayoutAlign): void {
210
+ const map: Record<LayoutAlign, Align> = {
211
+ auto: Align.Auto,
212
+ stretch: Align.Stretch,
213
+ 'flex-start': Align.FlexStart,
214
+ center: Align.Center,
215
+ 'flex-end': Align.FlexEnd,
216
+ }
217
+ this.yoga.setAlignItems(map[align]!)
218
+ }
219
+
220
+ setAlignSelf(align: LayoutAlign): void {
221
+ const map: Record<LayoutAlign, Align> = {
222
+ auto: Align.Auto,
223
+ stretch: Align.Stretch,
224
+ 'flex-start': Align.FlexStart,
225
+ center: Align.Center,
226
+ 'flex-end': Align.FlexEnd,
227
+ }
228
+ this.yoga.setAlignSelf(map[align]!)
229
+ }
230
+
231
+ setJustifyContent(justify: LayoutJustify): void {
232
+ const map: Record<LayoutJustify, Justify> = {
233
+ 'flex-start': Justify.FlexStart,
234
+ center: Justify.Center,
235
+ 'flex-end': Justify.FlexEnd,
236
+ 'space-between': Justify.SpaceBetween,
237
+ 'space-around': Justify.SpaceAround,
238
+ 'space-evenly': Justify.SpaceEvenly,
239
+ }
240
+ this.yoga.setJustifyContent(map[justify]!)
241
+ }
242
+
243
+ setDisplay(display: LayoutDisplay): void {
244
+ this.yoga.setDisplay(display === 'flex' ? Display.Flex : Display.None)
245
+ }
246
+
247
+ getDisplay(): LayoutDisplay {
248
+ return this.yoga.getDisplay() === Display.None
249
+ ? LayoutDisplay.None
250
+ : LayoutDisplay.Flex
251
+ }
252
+
253
+ setPositionType(type: LayoutPositionType): void {
254
+ this.yoga.setPositionType(
255
+ type === 'absolute' ? PositionType.Absolute : PositionType.Relative,
256
+ )
257
+ }
258
+
259
+ setPosition(edge: LayoutEdge, value: number): void {
260
+ this.yoga.setPosition(EDGE_MAP[edge]!, value)
261
+ }
262
+
263
+ setPositionPercent(edge: LayoutEdge, value: number): void {
264
+ this.yoga.setPositionPercent(EDGE_MAP[edge]!, value)
265
+ }
266
+
267
+ setOverflow(overflow: LayoutOverflow): void {
268
+ const map: Record<LayoutOverflow, Overflow> = {
269
+ visible: Overflow.Visible,
270
+ hidden: Overflow.Hidden,
271
+ scroll: Overflow.Scroll,
272
+ }
273
+ this.yoga.setOverflow(map[overflow]!)
274
+ }
275
+
276
+ setMargin(edge: LayoutEdge, value: number): void {
277
+ this.yoga.setMargin(EDGE_MAP[edge]!, value)
278
+ }
279
+ setPadding(edge: LayoutEdge, value: number): void {
280
+ this.yoga.setPadding(EDGE_MAP[edge]!, value)
281
+ }
282
+ setBorder(edge: LayoutEdge, value: number): void {
283
+ this.yoga.setBorder(EDGE_MAP[edge]!, value)
284
+ }
285
+ setGap(gutter: LayoutGutter, value: number): void {
286
+ this.yoga.setGap(GUTTER_MAP[gutter]!, value)
287
+ }
288
+
289
+ // Lifecycle
290
+
291
+ free(): void {
292
+ this.yoga.free()
293
+ }
294
+ freeRecursive(): void {
295
+ this.yoga.freeRecursive()
296
+ }
297
+ }
298
+
299
+ // --
300
+ // Instance management
301
+ //
302
+ // The TS yoga-layout port is synchronous — no WASM loading, no linear memory
303
+ // growth, so no preload/swap/reset machinery is needed. The Yoga instance is
304
+ // just a plain JS object available at import time.
305
+
306
+ export function createYogaLayoutNode(): LayoutNode {
307
+ return new YogaLayoutNode(Yoga.Node.create())
308
+ }
@@ -0,0 +1,24 @@
1
+ import { stringWidth } from './stringWidth.js'
2
+
3
+ // During streaming, text grows but completed lines are immutable.
4
+ // Caching stringWidth per-line avoids re-measuring hundreds of
5
+ // unchanged lines on every token (~50x reduction in stringWidth calls).
6
+ const cache = new Map<string, number>()
7
+
8
+ const MAX_CACHE_SIZE = 4096
9
+
10
+ export function lineWidth(line: string): number {
11
+ const cached = cache.get(line)
12
+ if (cached !== undefined) return cached
13
+
14
+ const width = stringWidth(line)
15
+
16
+ // Evict when cache grows too large (e.g. after many different responses).
17
+ // Simple full-clear is fine — the cache repopulates in one frame.
18
+ if (cache.size >= MAX_CACHE_SIZE) {
19
+ cache.clear()
20
+ }
21
+
22
+ cache.set(line, width)
23
+ return width
24
+ }