@ericsanchezok/synergy-workbench 0.0.0-stage → 3.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 (153) hide show
  1. package/AGENTS.md +30 -0
  2. package/README.md +16 -2
  3. package/dist/agents.js +11 -0
  4. package/dist/cli-adapter.js +8 -0
  5. package/dist/component.js +65 -0
  6. package/dist/config-schema.js +265 -0
  7. package/dist/execution/activity.js +78 -0
  8. package/dist/execution/content.js +184 -0
  9. package/dist/execution/json-sections.js +231 -0
  10. package/dist/execution/presentation.js +79 -0
  11. package/dist/execution/preview.js +13 -0
  12. package/dist/execution/process.js +125 -0
  13. package/dist/execution/projection.js +327 -0
  14. package/dist/execution/query.js +18 -0
  15. package/dist/execution/routes.js +189 -0
  16. package/dist/execution/schema.js +322 -0
  17. package/dist/execution/service.js +1993 -0
  18. package/dist/features.js +22 -0
  19. package/dist/http.js +65 -0
  20. package/dist/performance/analysis.js +330 -0
  21. package/dist/performance/catalog.js +391 -0
  22. package/dist/performance/dashboard.js +483 -0
  23. package/dist/performance/error.js +15 -0
  24. package/dist/performance/index.js +6 -0
  25. package/dist/performance/inflight.js +43 -0
  26. package/dist/performance/projection.js +138 -0
  27. package/dist/performance/prompt/base.txt +38 -0
  28. package/dist/performance/prompt/builder.js +21 -0
  29. package/dist/performance/routes/performance-route.js +439 -0
  30. package/dist/performance/schema.js +628 -0
  31. package/dist/performance/timeline.js +98 -0
  32. package/dist/performance/trace-detail.js +91 -0
  33. package/dist/presentation/service.js +270 -0
  34. package/dist/presentation/tools/workbench-view.js +65 -0
  35. package/dist/presentation/tools.js +10 -0
  36. package/dist/project/directories.js +363 -0
  37. package/dist/project/git-health.js +498 -0
  38. package/dist/project/ide.js +66 -0
  39. package/dist/project/migration.js +22 -0
  40. package/dist/project/routes/directories.js +89 -0
  41. package/dist/project/routes/git.js +30 -0
  42. package/dist/project/routes/task-defaults.js +42 -0
  43. package/dist/project/session-health.js +9 -0
  44. package/dist/project/startup.js +28 -0
  45. package/dist/project/task-defaults.js +68 -0
  46. package/dist/project/tools/session-control.js +664 -0
  47. package/dist/project/tools/session-control.txt +60 -0
  48. package/dist/project/tools/worktree-archive.js +76 -0
  49. package/dist/project/tools/worktree-enter.js +175 -0
  50. package/dist/project/tools/worktree-leave.js +119 -0
  51. package/dist/project/tools/worktree-list.js +52 -0
  52. package/dist/project/tools.js +26 -0
  53. package/dist/project/vcs-branch-watcher.js +76 -0
  54. package/dist/project/vcs.js +114 -0
  55. package/dist/project/worktree-janitor.js +140 -0
  56. package/dist/project/worktrees.js +98 -0
  57. package/dist/push/bridge.js +171 -0
  58. package/dist/push/index.js +4 -0
  59. package/dist/push/routes/push.js +224 -0
  60. package/dist/push/service.js +93 -0
  61. package/dist/push/store.js +121 -0
  62. package/dist/push/types.js +58 -0
  63. package/dist/review/git.js +226 -0
  64. package/dist/review/routes.js +45 -0
  65. package/dist/review/schema.js +32 -0
  66. package/dist/review/state.js +79 -0
  67. package/dist/stats/aggregator.js +252 -0
  68. package/dist/stats/canonical.js +89 -0
  69. package/dist/stats/cli/stats.js +302 -0
  70. package/dist/stats/engine.js +176 -0
  71. package/dist/stats/index.js +5 -0
  72. package/dist/stats/rollup.js +460 -0
  73. package/dist/stats/routes/stats.js +94 -0
  74. package/dist/stats/routes/usage.js +83 -0
  75. package/dist/stats/storage.js +86 -0
  76. package/dist/stats/types.js +267 -0
  77. package/dist/worker.js +6 -0
  78. package/package.json +232 -4
  79. package/src/agents.ts +15 -0
  80. package/src/cli-adapter.ts +11 -0
  81. package/src/component.ts +73 -0
  82. package/src/config-schema.ts +289 -0
  83. package/src/execution/activity.ts +83 -0
  84. package/src/execution/content.ts +189 -0
  85. package/src/execution/json-sections.ts +230 -0
  86. package/src/execution/presentation.ts +91 -0
  87. package/src/execution/preview.ts +12 -0
  88. package/src/execution/process.ts +118 -0
  89. package/src/execution/projection.ts +330 -0
  90. package/src/execution/query.ts +19 -0
  91. package/src/execution/routes.ts +324 -0
  92. package/src/execution/schema.ts +336 -0
  93. package/src/execution/service.ts +2149 -0
  94. package/src/features.ts +22 -0
  95. package/src/http.ts +75 -0
  96. package/src/performance/analysis.ts +356 -0
  97. package/src/performance/catalog.ts +902 -0
  98. package/src/performance/dashboard.ts +521 -0
  99. package/src/performance/error.ts +32 -0
  100. package/src/performance/index.ts +6 -0
  101. package/src/performance/inflight.ts +46 -0
  102. package/src/performance/projection.ts +143 -0
  103. package/src/performance/prompt/base.txt +38 -0
  104. package/src/performance/prompt/builder.ts +24 -0
  105. package/src/performance/routes/performance-route.ts +541 -0
  106. package/src/performance/schema.ts +695 -0
  107. package/src/performance/timeline.ts +100 -0
  108. package/src/performance/trace-detail.ts +97 -0
  109. package/src/presentation/service.ts +303 -0
  110. package/src/presentation/tools/workbench-view.ts +75 -0
  111. package/src/presentation/tools.ts +11 -0
  112. package/src/project/directories.ts +391 -0
  113. package/src/project/git-health.ts +577 -0
  114. package/src/project/ide.ts +80 -0
  115. package/src/project/migration.ts +24 -0
  116. package/src/project/routes/directories.ts +124 -0
  117. package/src/project/routes/git.ts +42 -0
  118. package/src/project/routes/task-defaults.ts +50 -0
  119. package/src/project/session-health.ts +10 -0
  120. package/src/project/startup.ts +28 -0
  121. package/src/project/task-defaults.ts +70 -0
  122. package/src/project/tools/session-control.ts +725 -0
  123. package/src/project/tools/session-control.txt +60 -0
  124. package/src/project/tools/worktree-archive.ts +92 -0
  125. package/src/project/tools/worktree-enter.ts +227 -0
  126. package/src/project/tools/worktree-leave.ts +140 -0
  127. package/src/project/tools/worktree-list.ts +68 -0
  128. package/src/project/tools.ts +29 -0
  129. package/src/project/vcs-branch-watcher.ts +81 -0
  130. package/src/project/vcs.ts +124 -0
  131. package/src/project/worktree-janitor.ts +153 -0
  132. package/src/project/worktrees.ts +110 -0
  133. package/src/push/bridge.ts +182 -0
  134. package/src/push/index.ts +4 -0
  135. package/src/push/routes/push.ts +267 -0
  136. package/src/push/service.ts +108 -0
  137. package/src/push/store.ts +120 -0
  138. package/src/push/types.ts +67 -0
  139. package/src/review/git.ts +221 -0
  140. package/src/review/routes.ts +65 -0
  141. package/src/review/schema.ts +34 -0
  142. package/src/review/state.ts +77 -0
  143. package/src/stats/aggregator.ts +277 -0
  144. package/src/stats/canonical.ts +93 -0
  145. package/src/stats/cli/stats.ts +348 -0
  146. package/src/stats/engine.ts +197 -0
  147. package/src/stats/index.ts +5 -0
  148. package/src/stats/rollup.ts +522 -0
  149. package/src/stats/routes/stats.ts +107 -0
  150. package/src/stats/routes/usage.ts +103 -0
  151. package/src/stats/storage.ts +78 -0
  152. package/src/stats/types.ts +351 -0
  153. package/src/worker.ts +7 -0
@@ -0,0 +1,184 @@
1
+ import { ExecutionQueryError, queryCursor } from "./query.js";
2
+ import { z } from "zod";
3
+ import { RuntimeContext } from "@ericsanchezok/synergy-harness/lifecycle/context";
4
+ import { ExecutionJson } from "./json-sections.js";
5
+ import { ScopeContext } from "@ericsanchezok/synergy-harness/scope/context";
6
+ export var ExecutionContent;
7
+ (function (ExecutionContent) {
8
+ ExecutionContent.Query = z.object({
9
+ field: z.string().min(1).max(100),
10
+ runID: z.string().optional(),
11
+ version: z.string().max(4096).optional(),
12
+ });
13
+ ExecutionContent.PageQuery = ExecutionContent.Query.extend({
14
+ cursor: z.string().max(4096).optional(),
15
+ limit: z.coerce.number().int().min(1).max(500).default(100),
16
+ });
17
+ ExecutionContent.SearchQuery = ExecutionContent.PageQuery.extend({
18
+ query: z.string().min(1).max(500),
19
+ caseSensitive: z.preprocess((value) => (value === "false" ? false : value), z.coerce.boolean()).default(true),
20
+ });
21
+ ExecutionContent.Section = z.object({
22
+ path: z.array(z.string()),
23
+ kind: z.enum(["object", "array", "string", "number", "boolean", "null"]),
24
+ offset: z.number(),
25
+ bytes: z.number(),
26
+ role: z.string().optional(),
27
+ preview: z.string(),
28
+ });
29
+ ExecutionContent.Sections = z
30
+ .object({
31
+ contentVersion: z.string(),
32
+ format: z.enum(["json", "text", "partial"]),
33
+ items: z.array(ExecutionContent.Section),
34
+ total: z.number(),
35
+ nextCursor: z.string().nullable(),
36
+ truncated: z.boolean(),
37
+ })
38
+ .meta({ ref: "ExecutionContentSections" });
39
+ ExecutionContent.Search = z
40
+ .object({
41
+ contentVersion: z.string(),
42
+ status: z.enum(["partial", "complete"]),
43
+ items: z.array(z.object({ offset: z.number(), bytes: z.number(), preview: z.string() })),
44
+ nextCursor: z.string().nullable(),
45
+ })
46
+ .meta({ ref: "ExecutionContentSearch" });
47
+ const cache = RuntimeContext.state(() => ({
48
+ entries: new Map(),
49
+ bytes: 0,
50
+ }));
51
+ function cursor(version, query, offset) {
52
+ return Buffer.from(JSON.stringify({ version, query, offset })).toString("base64url");
53
+ }
54
+ function position(input, version, query) {
55
+ if (!input)
56
+ return 0;
57
+ const value = queryCursor(z.object({ version: z.string(), query: z.string(), offset: z.number().int().nonnegative() }), input);
58
+ if (value.version !== version || value.query !== query)
59
+ throw new ExecutionQueryError("Execution content cursor does not match its version and query");
60
+ return value.offset;
61
+ }
62
+ async function sections(source, input, signal) {
63
+ const offset = position(input.cursor, source.contentVersion, "sections");
64
+ if (source.status === "partial" || !source.mediaType.includes("json"))
65
+ return ExecutionContent.Sections.parse({
66
+ contentVersion: source.contentVersion,
67
+ format: source.status === "partial" ? "partial" : "text",
68
+ items: [],
69
+ total: 0,
70
+ nextCursor: null,
71
+ truncated: false,
72
+ });
73
+ const state = cache();
74
+ const key = ScopeContext.current.scope.id + ":" + source.contentVersion;
75
+ let value = state.entries.get(key)?.value;
76
+ if (!value) {
77
+ try {
78
+ value = await ExecutionJson.index(source.stream(), signal);
79
+ }
80
+ catch (error) {
81
+ if (!(error instanceof SyntaxError))
82
+ throw error;
83
+ return ExecutionContent.Sections.parse({
84
+ contentVersion: source.contentVersion,
85
+ format: "text",
86
+ items: [],
87
+ total: 0,
88
+ nextCursor: null,
89
+ truncated: false,
90
+ });
91
+ }
92
+ const bytes = Buffer.byteLength(JSON.stringify(value));
93
+ if (!state.entries.has(key) && bytes <= 8 * 1024 * 1024) {
94
+ while (state.bytes + bytes > 8 * 1024 * 1024 && state.entries.size) {
95
+ const [key, entry] = state.entries.entries().next().value;
96
+ state.entries.delete(key);
97
+ state.bytes -= entry.bytes;
98
+ }
99
+ state.entries.set(key, { value, bytes });
100
+ state.bytes += bytes;
101
+ }
102
+ }
103
+ const items = value.items.slice(offset, offset + input.limit);
104
+ return ExecutionContent.Sections.parse({
105
+ contentVersion: source.contentVersion,
106
+ format: "json",
107
+ items,
108
+ total: value.items.length,
109
+ nextCursor: offset + items.length < value.items.length
110
+ ? cursor(source.contentVersion, "sections", offset + items.length)
111
+ : null,
112
+ truncated: value.truncated,
113
+ });
114
+ }
115
+ ExecutionContent.sections = sections;
116
+ async function value(source, path, signal) {
117
+ let next;
118
+ let section;
119
+ do {
120
+ const page = await sections(source, ExecutionContent.PageQuery.parse({ field: "request", cursor: next, limit: 500 }), signal);
121
+ section = page.items.find((item) => JSON.stringify(item.path) === JSON.stringify(path));
122
+ next = page.nextCursor ?? undefined;
123
+ } while (!section && next);
124
+ if (!section || section.bytes > 8 * 1024 * 1024)
125
+ return undefined;
126
+ const parts = [];
127
+ for (let offset = section.offset; offset < section.offset + section.bytes;) {
128
+ signal?.throwIfAborted();
129
+ const page = await source.read(offset, Math.min(65_536, section.offset + section.bytes - offset));
130
+ parts.push(page.text);
131
+ offset = page.nextOffset ?? source.bytes;
132
+ }
133
+ return JSON.parse(parts.join(""));
134
+ }
135
+ ExecutionContent.value = value;
136
+ async function search(source, input, signal) {
137
+ const fingerprint = JSON.stringify([input.query, input.caseSensitive]);
138
+ let offset = position(input.cursor, source.contentVersion, fingerprint);
139
+ const pattern = new RegExp(input.query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), input.caseSensitive ? "gu" : "giu");
140
+ const items = [];
141
+ let carry = "";
142
+ let carryOffset = offset;
143
+ let next = null;
144
+ while (offset < source.bytes) {
145
+ signal?.throwIfAborted();
146
+ const page = await source.read(offset, 65_536);
147
+ const text = carry + page.text;
148
+ const base = carry ? carryOffset : offset;
149
+ const safe = page.nextOffset === null ? text.length : Math.max(0, text.length - input.query.length - 2);
150
+ pattern.lastIndex = 0;
151
+ let consumed = 0;
152
+ for (let match = pattern.exec(text); match && match.index < safe; match = pattern.exec(text)) {
153
+ const start = base + Buffer.byteLength(text.slice(0, match.index));
154
+ const bytes = Buffer.byteLength(match[0]);
155
+ items.push({
156
+ offset: start,
157
+ bytes,
158
+ preview: text.slice(Math.max(0, match.index - 60), match.index + match[0].length + 100),
159
+ });
160
+ consumed = match.index + match[0].length;
161
+ if (items.length === input.limit) {
162
+ next = start + bytes;
163
+ break;
164
+ }
165
+ }
166
+ if (next !== null)
167
+ break;
168
+ let retained = Math.max(safe, consumed);
169
+ if (retained && retained < text.length && /[\uDC00-\uDFFF]/.test(text[retained]))
170
+ retained--;
171
+ carry = text.slice(retained);
172
+ carryOffset = base + Buffer.byteLength(text.slice(0, retained));
173
+ offset = page.nextOffset ?? source.bytes;
174
+ await new Promise((resolve) => setImmediate(resolve));
175
+ }
176
+ return ExecutionContent.Search.parse({
177
+ contentVersion: source.contentVersion,
178
+ status: source.status,
179
+ items,
180
+ nextCursor: next !== null && next < source.bytes ? cursor(source.contentVersion, fingerprint, next) : null,
181
+ });
182
+ }
183
+ ExecutionContent.search = search;
184
+ })(ExecutionContent || (ExecutionContent = {}));
@@ -0,0 +1,231 @@
1
+ export var ExecutionJson;
2
+ (function (ExecutionJson) {
3
+ async function index(source, signal) {
4
+ const stack = [];
5
+ const items = [];
6
+ let token;
7
+ let offset = 0;
8
+ let done = false;
9
+ let truncated = false;
10
+ let indexBytes = 0;
11
+ const utf8 = new TextDecoder("utf-8", { fatal: true });
12
+ const invalid = () => {
13
+ throw new SyntaxError("Execution content is not complete JSON");
14
+ };
15
+ const path = () => {
16
+ const parent = stack.at(-1);
17
+ if (!parent)
18
+ return [];
19
+ return [...parent.path, parent.kind === "array" ? String(parent.index) : parent.key];
20
+ };
21
+ const finishValue = () => {
22
+ const parent = stack.at(-1);
23
+ if (!parent) {
24
+ done = true;
25
+ return;
26
+ }
27
+ if (parent.state !== "value")
28
+ invalid();
29
+ parent.state = "comma";
30
+ parent.empty = false;
31
+ parent.index++;
32
+ };
33
+ const sectionBytes = (value) => 96 + value.path.reduce((sum, key) => sum + key.length * 4, 0) + value.preview.length * 4;
34
+ const section = (value) => {
35
+ if (value.path.length > 2)
36
+ return;
37
+ const bytes = sectionBytes(value);
38
+ while (value.path.length <= 1 && (items.length >= 10_000 || indexBytes + bytes > 2 * 1024 * 1024)) {
39
+ const index = items.findLastIndex((item) => item.path.length > 1);
40
+ if (index < 0)
41
+ break;
42
+ indexBytes -= sectionBytes(items[index]);
43
+ items.splice(index, 1);
44
+ truncated = true;
45
+ }
46
+ if (items.length >= 10_000 || indexBytes + bytes > 2 * 1024 * 1024) {
47
+ truncated = true;
48
+ return;
49
+ }
50
+ indexBytes += bytes;
51
+ items.push(value);
52
+ };
53
+ const finishToken = (end) => {
54
+ const current = token;
55
+ const raw = Buffer.from(current.sample).toString();
56
+ if (current.key) {
57
+ if (current.overflow)
58
+ throw new RangeError("Execution JSON key exceeds the section index budget");
59
+ stack.at(-1).key = JSON.parse(raw);
60
+ stack.at(-1).state = "colon";
61
+ }
62
+ else {
63
+ let kind = "string";
64
+ let preview = "";
65
+ if (current.kind === "scalar") {
66
+ if (current.overflow || !/^(?:null|true|false|-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)$/.test(raw))
67
+ invalid();
68
+ kind = raw === "null" ? "null" : raw === "true" || raw === "false" ? "boolean" : "number";
69
+ preview = raw;
70
+ }
71
+ else if (!current.overflow) {
72
+ const value = JSON.parse(raw);
73
+ if (typeof value === "string")
74
+ preview = value.slice(0, 160);
75
+ if (current.path.at(-1) === "role" && typeof value === "string")
76
+ stack.at(-1).role = value.slice(0, 64);
77
+ }
78
+ section({ path: current.path, kind, offset: current.start, bytes: end - current.start, preview });
79
+ finishValue();
80
+ }
81
+ token = undefined;
82
+ };
83
+ const sample = (byte) => {
84
+ const current = token;
85
+ const budget = current.key ? 65_536 : current.kind === "scalar" ? 1024 : 512;
86
+ if (current.sample.length < budget)
87
+ current.sample.push(byte);
88
+ else
89
+ current.overflow = true;
90
+ };
91
+ const consume = (byte) => {
92
+ if (token) {
93
+ if (token.kind === "scalar") {
94
+ if (byte === 32 || byte === 9 || byte === 10 || byte === 13 || byte === 44 || byte === 93 || byte === 125) {
95
+ finishToken(offset);
96
+ return false;
97
+ }
98
+ sample(byte);
99
+ return true;
100
+ }
101
+ sample(byte);
102
+ if (token.unicode) {
103
+ if (!((byte >= 48 && byte <= 57) || (byte >= 65 && byte <= 70) || (byte >= 97 && byte <= 102)))
104
+ invalid();
105
+ token.unicode--;
106
+ }
107
+ else if (token.escaped) {
108
+ if (byte === 117)
109
+ token.unicode = 4;
110
+ else if (![34, 92, 47, 98, 102, 110, 114, 116].includes(byte))
111
+ invalid();
112
+ token.escaped = false;
113
+ }
114
+ else if (byte === 92)
115
+ token.escaped = true;
116
+ else if (byte === 34)
117
+ finishToken(offset + 1);
118
+ else if (byte < 32)
119
+ invalid();
120
+ return true;
121
+ }
122
+ if (byte === 32 || byte === 9 || byte === 10 || byte === 13)
123
+ return true;
124
+ if (done)
125
+ invalid();
126
+ const parent = stack.at(-1);
127
+ if (byte === 125 || byte === 93) {
128
+ if (!parent ||
129
+ parent.kind !== (byte === 125 ? "object" : "array") ||
130
+ !(parent.state === "comma" || (parent.empty && parent.state === (parent.kind === "object" ? "key" : "value"))))
131
+ invalid();
132
+ const frame = stack.pop();
133
+ section({
134
+ path: frame.path,
135
+ kind: frame.kind,
136
+ offset: frame.start,
137
+ bytes: offset + 1 - frame.start,
138
+ role: frame.role,
139
+ preview: "",
140
+ });
141
+ finishValue();
142
+ return true;
143
+ }
144
+ if (parent?.state === "comma") {
145
+ if (byte !== 44)
146
+ invalid();
147
+ parent.state = parent.kind === "object" ? "key" : "value";
148
+ return true;
149
+ }
150
+ if (parent?.state === "colon") {
151
+ if (byte !== 58)
152
+ invalid();
153
+ parent.state = "value";
154
+ return true;
155
+ }
156
+ if (parent?.state === "key") {
157
+ if (byte !== 34)
158
+ invalid();
159
+ token = {
160
+ start: offset,
161
+ path: parent.path,
162
+ kind: "string",
163
+ key: true,
164
+ escaped: false,
165
+ unicode: 0,
166
+ sample: [byte],
167
+ overflow: false,
168
+ };
169
+ return true;
170
+ }
171
+ if (parent && parent.state !== "value")
172
+ invalid();
173
+ const currentPath = path();
174
+ if (byte === 123 || byte === 91) {
175
+ if (stack.length >= 128)
176
+ throw new RangeError("Execution JSON nesting exceeds the section index budget");
177
+ stack.push({
178
+ path: currentPath,
179
+ kind: byte === 123 ? "object" : "array",
180
+ start: offset,
181
+ state: byte === 123 ? "key" : "value",
182
+ empty: true,
183
+ index: 0,
184
+ });
185
+ }
186
+ else if (byte === 34) {
187
+ token = {
188
+ start: offset,
189
+ path: currentPath,
190
+ kind: "string",
191
+ key: false,
192
+ escaped: false,
193
+ unicode: 0,
194
+ sample: [byte],
195
+ overflow: false,
196
+ };
197
+ }
198
+ else {
199
+ if (!(byte === 45 || (byte >= 48 && byte <= 57) || byte === 116 || byte === 102 || byte === 110))
200
+ invalid();
201
+ token = {
202
+ start: offset,
203
+ path: currentPath,
204
+ kind: "scalar",
205
+ key: false,
206
+ escaped: false,
207
+ unicode: 0,
208
+ sample: [byte],
209
+ overflow: false,
210
+ };
211
+ }
212
+ return true;
213
+ };
214
+ for await (const chunk of source) {
215
+ signal?.throwIfAborted();
216
+ utf8.decode(chunk, { stream: true });
217
+ for (const byte of chunk) {
218
+ while (!consume(byte)) { }
219
+ offset++;
220
+ }
221
+ await new Promise((resolve) => setImmediate(resolve));
222
+ }
223
+ utf8.decode();
224
+ if (token?.kind === "scalar")
225
+ finishToken(offset);
226
+ if (token || stack.length || !done)
227
+ invalid();
228
+ return { items: items.toSorted((a, b) => a.offset - b.offset || b.bytes - a.bytes), truncated };
229
+ }
230
+ ExecutionJson.index = index;
231
+ })(ExecutionJson || (ExecutionJson = {}));
@@ -0,0 +1,79 @@
1
+ import { z } from "zod";
2
+ import { RolloutAccounting } from "@ericsanchezok/synergy-harness/rollout";
3
+ export var ExecutionPresentation;
4
+ (function (ExecutionPresentation) {
5
+ const Estimate = z.object({
6
+ basis: z.enum(["api", "unclassified", "subscription", "historical"]),
7
+ currency: z.literal("USD"),
8
+ known: z.number(),
9
+ maximum: z.number(),
10
+ unknown: z.number().int().nonnegative(),
11
+ });
12
+ ExecutionPresentation.Cost = z
13
+ .object({
14
+ state: z.enum(["unrecorded", "local", "subscription", "reported", "estimated", "mixed", "partial", "unknown"]),
15
+ reported: z.array(z.object({ currency: z.string(), amount: z.number() })),
16
+ estimates: z.array(Estimate),
17
+ equivalent: Estimate.nullable(),
18
+ missing: z.number().int().nonnegative(),
19
+ historical: z.number().int().nonnegative(),
20
+ knownUSD: z.number(),
21
+ })
22
+ .meta({ ref: "ExecutionCostPresentation" });
23
+ function cost(summary) {
24
+ const reported = Object.entries(summary.reported.currencies)
25
+ .toSorted(([a], [b]) => a.localeCompare(b))
26
+ .map(([currency, amount]) => ({ currency, amount }));
27
+ const coverage = summary.costCoverage;
28
+ const estimates = [];
29
+ const entry = (basis, metric, maximum = metric.known) => ({ basis, currency: "USD", known: metric.known, maximum, unknown: metric.unknown });
30
+ if (coverage) {
31
+ for (const basis of ["api", "unclassified"]) {
32
+ const bucket = coverage[basis];
33
+ if ((bucket.unreportedRequests ??
34
+ Number(bucket.attempts && (bucket.unreported.known || bucket.unreported.unknown || !reported.length))) > 0)
35
+ estimates.push(entry(basis, bucket.unreported, bucket.maximum.known));
36
+ }
37
+ }
38
+ const historicalAmount = (coverage
39
+ ? (coverage.historicalAmount ?? 0)
40
+ : !reported.length
41
+ ? summary.apiEstimate.known + summary.unclassifiedEquivalent.known
42
+ : 0) + summary.legacy.cost;
43
+ if (historicalAmount)
44
+ estimates.push(entry("historical", { known: historicalAmount, total: historicalAmount, unknown: 0 }));
45
+ const equivalent = coverage?.subscription.attempts
46
+ ? entry("subscription", summary.subscriptionEquivalent, summary.subscriptionEquivalent.known +
47
+ Math.max(0, coverage.subscription.maximum.known - coverage.subscription.unreported.known))
48
+ : null;
49
+ const historical = coverage
50
+ ? coverage.historical + summary.legacy.messages
51
+ : summary.attempts + summary.unobservedCalls + summary.legacy.messages;
52
+ const missing = estimates.reduce((count, estimate) => count + estimate.unknown, historical);
53
+ const state = missing
54
+ ? reported.length || estimates.some((estimate) => estimate.known)
55
+ ? "partial"
56
+ : "unknown"
57
+ : reported.length
58
+ ? estimates.length
59
+ ? "mixed"
60
+ : "reported"
61
+ : estimates.length
62
+ ? "estimated"
63
+ : equivalent
64
+ ? "subscription"
65
+ : coverage?.local
66
+ ? "local"
67
+ : "unrecorded";
68
+ return {
69
+ state,
70
+ reported,
71
+ estimates,
72
+ equivalent,
73
+ missing,
74
+ historical,
75
+ knownUSD: RolloutAccounting.knownExpense(summary),
76
+ };
77
+ }
78
+ ExecutionPresentation.cost = cost;
79
+ })(ExecutionPresentation || (ExecutionPresentation = {}));
@@ -0,0 +1,13 @@
1
+ // Provenance: ../../../../docs/decisions/implemented/bug-fix/2026-10-22-detach-execution-previews.md
2
+ // Local adaptation: build bounded UTF-16 output without retaining source-string backing storage.
3
+ export function preview(value) {
4
+ const codes = [];
5
+ let whitespace = false;
6
+ for (let index = 0; index < value.length && codes.length < 240; index++) {
7
+ const space = /\s/.test(value[index]);
8
+ if (!space || !whitespace)
9
+ codes.push(space ? 32 : value.charCodeAt(index));
10
+ whitespace = space;
11
+ }
12
+ return String.fromCharCode(...codes);
13
+ }
@@ -0,0 +1,125 @@
1
+ export var ExecutionProcess;
2
+ (function (ExecutionProcess) {
3
+ function auxiliaryKey(node) {
4
+ if (node.evidenceKind !== "call" || node.kind === "compaction")
5
+ return;
6
+ if (node.usageRole !== "auxiliary" && (!node.modelKind || node.modelKind === "chat"))
7
+ return;
8
+ return JSON.stringify([node.sessionID, node.runID, node.modelKind ?? "chat", node.purpose?.trim() || null]);
9
+ }
10
+ function auxiliaryMembers(nodes, node) {
11
+ const key = auxiliaryKey(node);
12
+ return key
13
+ ? nodes
14
+ .filter((row) => auxiliaryKey(row) === key)
15
+ .toSorted((a, b) => a.started - b.started || a.id.localeCompare(b.id))
16
+ : [];
17
+ }
18
+ ExecutionProcess.auxiliaryMembers = auxiliaryMembers;
19
+ function project(nodes, mode, root) {
20
+ const byID = new Map(nodes.map((node) => [node.id, node]));
21
+ const children = new Map();
22
+ const auxiliary = new Map();
23
+ for (const node of nodes) {
24
+ const key = auxiliaryKey(node);
25
+ if (key) {
26
+ const rows = auxiliary.get(key) ?? [];
27
+ rows.push(node);
28
+ auxiliary.set(key, rows);
29
+ }
30
+ if (!node.parentID)
31
+ continue;
32
+ const rows = children.get(node.parentID) ?? [];
33
+ rows.push(node);
34
+ children.set(node.parentID, rows);
35
+ }
36
+ const groups = new Map(nodes
37
+ .filter((node) => node.evidenceKind === "call")
38
+ .map((node) => {
39
+ const members = children.get(node.id) ?? [];
40
+ const attempts = members.filter((child) => child.evidenceKind === "attempt");
41
+ return [
42
+ node.id,
43
+ {
44
+ id: node.id,
45
+ started: node.started,
46
+ callCount: 1,
47
+ memberCount: members.length,
48
+ attemptCount: attempts.length,
49
+ retryCount: attempts.filter((attempt) => (attempt.attemptIndex ?? 0) > 0).length,
50
+ anomalies: members.filter((child) => ["failed", "cancelled", "interrupted"].includes(child.status))
51
+ .length,
52
+ purpose: node.purpose?.trim() || null,
53
+ },
54
+ ];
55
+ }));
56
+ const heads = new Map();
57
+ const hidden = new Set();
58
+ if (mode === "process")
59
+ for (const members of auxiliary.values()) {
60
+ if (members.length < 2)
61
+ continue;
62
+ members.sort((a, b) => a.started - b.started || a.id.localeCompare(b.id));
63
+ const first = members[0];
64
+ for (const member of members.slice(1))
65
+ hidden.add(member.id);
66
+ const counters = members.map((node) => groups.get(node.id));
67
+ const status = members.some((node) => node.status === "running")
68
+ ? "running"
69
+ : (members.find((node) => ["failed", "cancelled", "interrupted", "unknown"].includes(node.status))?.status ??
70
+ "completed");
71
+ heads.set(first.id, {
72
+ ...first,
73
+ status,
74
+ ended: members.every((node) => node.ended !== undefined)
75
+ ? Math.max(...members.map((node) => node.ended))
76
+ : undefined,
77
+ group: {
78
+ id: first.id,
79
+ started: first.started,
80
+ callCount: members.length,
81
+ memberCount: counters.reduce((sum, value) => sum + value.memberCount, 0),
82
+ attemptCount: counters.reduce((sum, value) => sum + value.attemptCount, 0),
83
+ retryCount: counters.reduce((sum, value) => sum + value.retryCount, 0),
84
+ anomalies: counters.reduce((sum, value) => sum + value.anomalies, 0),
85
+ purpose: first.purpose?.trim() || null,
86
+ },
87
+ });
88
+ }
89
+ return nodes
90
+ .filter((node) => {
91
+ if (mode === "records")
92
+ return true;
93
+ if (hidden.has(node.id))
94
+ return false;
95
+ if (node.evidenceKind === "attempt")
96
+ return (node.attemptIndex ?? 0) > 0;
97
+ if (node.evidenceKind !== "call" || node.kind === "compaction" || auxiliaryKey(node))
98
+ return true;
99
+ return !(children.get(node.id) ?? []).some((child) => ["reasoning", "output", "tool"].includes(child.kind));
100
+ })
101
+ .map((original) => {
102
+ const node = heads.get(original.id) ?? original;
103
+ const ancestors = [];
104
+ const seen = new Set([node.id]);
105
+ let parent = node.parentID && byID.get(node.parentID);
106
+ while (parent && !seen.has(parent.id)) {
107
+ seen.add(parent.id);
108
+ ancestors.unshift({ id: parent.id, title: parent.title, kind: parent.kind });
109
+ parent = parent.parentID && byID.get(parent.parentID);
110
+ }
111
+ const rootRunID = !root || node.sessionID === root
112
+ ? node.runID
113
+ : (ancestors.map((ancestor) => byID.get(ancestor.id)).find((ancestor) => ancestor?.sessionID === root)
114
+ ?.runID ?? null);
115
+ return {
116
+ ...node,
117
+ ancestors,
118
+ rootRunID,
119
+ attribution: node.attribution ?? (rootRunID === null ? "unassigned" : "known"),
120
+ group: node.group ?? groups.get(node.id) ?? (node.parentID ? groups.get(node.parentID) : undefined),
121
+ };
122
+ });
123
+ }
124
+ ExecutionProcess.project = project;
125
+ })(ExecutionProcess || (ExecutionProcess = {}));