@a5c-ai/genty-runtime 5.1.1-staging.00ceebd28cf2

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 (221) hide show
  1. package/README.md +69 -0
  2. package/dist/apiResult.d.ts +19 -0
  3. package/dist/apiResult.d.ts.map +1 -0
  4. package/dist/apiResult.js +16 -0
  5. package/dist/background/state.d.ts +20 -0
  6. package/dist/background/state.d.ts.map +1 -0
  7. package/dist/background/state.js +52 -0
  8. package/dist/backgroundProcessRegistry.d.ts +124 -0
  9. package/dist/backgroundProcessRegistry.d.ts.map +1 -0
  10. package/dist/backgroundProcessRegistry.js +427 -0
  11. package/dist/cost/claudeCodeParser.d.ts +81 -0
  12. package/dist/cost/claudeCodeParser.d.ts.map +1 -0
  13. package/dist/cost/claudeCodeParser.js +232 -0
  14. package/dist/cost/collector.d.ts +42 -0
  15. package/dist/cost/collector.d.ts.map +1 -0
  16. package/dist/cost/collector.js +105 -0
  17. package/dist/cost/effectCost.d.ts +23 -0
  18. package/dist/cost/effectCost.d.ts.map +1 -0
  19. package/dist/cost/effectCost.js +26 -0
  20. package/dist/cost/index.d.ts +19 -0
  21. package/dist/cost/index.d.ts.map +1 -0
  22. package/dist/cost/index.js +39 -0
  23. package/dist/cost/journal.d.ts +40 -0
  24. package/dist/cost/journal.d.ts.map +1 -0
  25. package/dist/cost/journal.js +137 -0
  26. package/dist/cost/types.d.ts +164 -0
  27. package/dist/cost/types.d.ts.map +1 -0
  28. package/dist/cost/types.js +228 -0
  29. package/dist/daemon/automationExecutor.d.ts +16 -0
  30. package/dist/daemon/automationExecutor.d.ts.map +1 -0
  31. package/dist/daemon/automationExecutor.js +222 -0
  32. package/dist/daemon/config.d.ts +8 -0
  33. package/dist/daemon/config.d.ts.map +1 -0
  34. package/dist/daemon/config.js +245 -0
  35. package/dist/daemon/daemonLog.d.ts +30 -0
  36. package/dist/daemon/daemonLog.d.ts.map +1 -0
  37. package/dist/daemon/daemonLog.js +140 -0
  38. package/dist/daemon/durableQueue.d.ts +41 -0
  39. package/dist/daemon/durableQueue.d.ts.map +1 -0
  40. package/dist/daemon/durableQueue.js +183 -0
  41. package/dist/daemon/fileWatcher.d.ts +9 -0
  42. package/dist/daemon/fileWatcher.d.ts.map +1 -0
  43. package/dist/daemon/fileWatcher.js +144 -0
  44. package/dist/daemon/index.d.ts +15 -0
  45. package/dist/daemon/index.d.ts.map +1 -0
  46. package/dist/daemon/index.js +25 -0
  47. package/dist/daemon/lifecycle.d.ts +13 -0
  48. package/dist/daemon/lifecycle.d.ts.map +1 -0
  49. package/dist/daemon/lifecycle.js +320 -0
  50. package/dist/daemon/loop.d.ts +27 -0
  51. package/dist/daemon/loop.d.ts.map +1 -0
  52. package/dist/daemon/loop.js +387 -0
  53. package/dist/daemon/timerScheduler.d.ts +13 -0
  54. package/dist/daemon/timerScheduler.d.ts.map +1 -0
  55. package/dist/daemon/timerScheduler.js +212 -0
  56. package/dist/daemon/types.d.ts +122 -0
  57. package/dist/daemon/types.d.ts.map +1 -0
  58. package/dist/daemon/types.js +25 -0
  59. package/dist/daemon/webhookListener.d.ts +6 -0
  60. package/dist/daemon/webhookListener.d.ts.map +1 -0
  61. package/dist/daemon/webhookListener.js +132 -0
  62. package/dist/execution/index.d.ts +10 -0
  63. package/dist/execution/index.d.ts.map +1 -0
  64. package/dist/execution/index.js +20 -0
  65. package/dist/execution/modes/docker.d.ts +26 -0
  66. package/dist/execution/modes/docker.d.ts.map +1 -0
  67. package/dist/execution/modes/docker.js +183 -0
  68. package/dist/execution/modes/index.d.ts +10 -0
  69. package/dist/execution/modes/index.d.ts.map +1 -0
  70. package/dist/execution/modes/index.js +14 -0
  71. package/dist/execution/modes/kubernetes.d.ts +46 -0
  72. package/dist/execution/modes/kubernetes.d.ts.map +1 -0
  73. package/dist/execution/modes/kubernetes.js +334 -0
  74. package/dist/execution/modes/local.d.ts +23 -0
  75. package/dist/execution/modes/local.d.ts.map +1 -0
  76. package/dist/execution/modes/local.js +117 -0
  77. package/dist/execution/modes/ssh.d.ts +23 -0
  78. package/dist/execution/modes/ssh.d.ts.map +1 -0
  79. package/dist/execution/modes/ssh.js +144 -0
  80. package/dist/execution/policy.d.ts +15 -0
  81. package/dist/execution/policy.d.ts.map +1 -0
  82. package/dist/execution/policy.js +121 -0
  83. package/dist/execution/provider.d.ts +32 -0
  84. package/dist/execution/provider.d.ts.map +1 -0
  85. package/dist/execution/provider.js +90 -0
  86. package/dist/execution/types.d.ts +189 -0
  87. package/dist/execution/types.d.ts.map +1 -0
  88. package/dist/execution/types.js +9 -0
  89. package/dist/index.d.ts +12 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +44 -0
  92. package/dist/observability/diagnostics.d.ts +25 -0
  93. package/dist/observability/diagnostics.d.ts.map +1 -0
  94. package/dist/observability/diagnostics.js +98 -0
  95. package/dist/observability/health.d.ts +19 -0
  96. package/dist/observability/health.d.ts.map +1 -0
  97. package/dist/observability/health.js +145 -0
  98. package/dist/observability/index.d.ts +7 -0
  99. package/dist/observability/index.d.ts.map +1 -0
  100. package/dist/observability/index.js +25 -0
  101. package/dist/observability/runStatus.d.ts +44 -0
  102. package/dist/observability/runStatus.d.ts.map +1 -0
  103. package/dist/observability/runStatus.js +169 -0
  104. package/dist/observability/timeline.d.ts +11 -0
  105. package/dist/observability/timeline.d.ts.map +1 -0
  106. package/dist/observability/timeline.js +176 -0
  107. package/dist/observability/types.d.ts +65 -0
  108. package/dist/observability/types.d.ts.map +1 -0
  109. package/dist/observability/types.js +8 -0
  110. package/dist/observability/webhooks.d.ts +68 -0
  111. package/dist/observability/webhooks.d.ts.map +1 -0
  112. package/dist/observability/webhooks.js +132 -0
  113. package/dist/resources/budget-tracker.d.ts +56 -0
  114. package/dist/resources/budget-tracker.d.ts.map +1 -0
  115. package/dist/resources/budget-tracker.js +131 -0
  116. package/dist/resources/concurrency-guard.d.ts +55 -0
  117. package/dist/resources/concurrency-guard.d.ts.map +1 -0
  118. package/dist/resources/concurrency-guard.js +132 -0
  119. package/dist/resources/index.d.ts +12 -0
  120. package/dist/resources/index.d.ts.map +1 -0
  121. package/dist/resources/index.js +20 -0
  122. package/dist/resources/manager.d.ts +52 -0
  123. package/dist/resources/manager.d.ts.map +1 -0
  124. package/dist/resources/manager.js +150 -0
  125. package/dist/resources/timeout-cascade.d.ts +56 -0
  126. package/dist/resources/timeout-cascade.d.ts.map +1 -0
  127. package/dist/resources/timeout-cascade.js +145 -0
  128. package/dist/resources/types.d.ts +130 -0
  129. package/dist/resources/types.d.ts.map +1 -0
  130. package/dist/resources/types.js +9 -0
  131. package/dist/rpc/index.d.ts +5 -0
  132. package/dist/rpc/index.d.ts.map +1 -0
  133. package/dist/rpc/index.js +7 -0
  134. package/dist/rpc/server.d.ts +13 -0
  135. package/dist/rpc/server.d.ts.map +1 -0
  136. package/dist/rpc/server.js +64 -0
  137. package/dist/rpc/server.test.d.ts +2 -0
  138. package/dist/rpc/server.test.d.ts.map +1 -0
  139. package/dist/rpc/server.test.js +35 -0
  140. package/dist/rpc/types.d.ts +23 -0
  141. package/dist/rpc/types.d.ts.map +1 -0
  142. package/dist/rpc/types.js +20 -0
  143. package/dist/session/context.d.ts +22 -0
  144. package/dist/session/context.d.ts.map +1 -0
  145. package/dist/session/context.js +113 -0
  146. package/dist/session/continuityState.d.ts +39 -0
  147. package/dist/session/continuityState.d.ts.map +1 -0
  148. package/dist/session/continuityState.js +164 -0
  149. package/dist/session/cost.d.ts +63 -0
  150. package/dist/session/cost.d.ts.map +1 -0
  151. package/dist/session/cost.js +194 -0
  152. package/dist/session/discovery.d.ts +22 -0
  153. package/dist/session/discovery.d.ts.map +1 -0
  154. package/dist/session/discovery.js +35 -0
  155. package/dist/session/export.d.ts +4 -0
  156. package/dist/session/export.d.ts.map +1 -0
  157. package/dist/session/export.js +56 -0
  158. package/dist/session/export.test.d.ts +2 -0
  159. package/dist/session/export.test.d.ts.map +1 -0
  160. package/dist/session/export.test.js +42 -0
  161. package/dist/session/history.d.ts +30 -0
  162. package/dist/session/history.d.ts.map +1 -0
  163. package/dist/session/history.js +143 -0
  164. package/dist/session/index.d.ts +20 -0
  165. package/dist/session/index.d.ts.map +1 -0
  166. package/dist/session/index.js +78 -0
  167. package/dist/session/memoryExtraction.d.ts +65 -0
  168. package/dist/session/memoryExtraction.d.ts.map +1 -0
  169. package/dist/session/memoryExtraction.js +201 -0
  170. package/dist/session/parse.d.ts +45 -0
  171. package/dist/session/parse.d.ts.map +1 -0
  172. package/dist/session/parse.js +170 -0
  173. package/dist/session/persistence.d.ts +46 -0
  174. package/dist/session/persistence.d.ts.map +1 -0
  175. package/dist/session/persistence.js +180 -0
  176. package/dist/session/rewind.d.ts +45 -0
  177. package/dist/session/rewind.d.ts.map +1 -0
  178. package/dist/session/rewind.js +68 -0
  179. package/dist/session/rewind.test.d.ts +2 -0
  180. package/dist/session/rewind.test.d.ts.map +1 -0
  181. package/dist/session/rewind.test.js +96 -0
  182. package/dist/session/tree.d.ts +29 -0
  183. package/dist/session/tree.d.ts.map +1 -0
  184. package/dist/session/tree.js +115 -0
  185. package/dist/session/tree.test.d.ts +2 -0
  186. package/dist/session/tree.test.d.ts.map +1 -0
  187. package/dist/session/tree.test.js +75 -0
  188. package/dist/session/types.d.ts +267 -0
  189. package/dist/session/types.d.ts.map +1 -0
  190. package/dist/session/types.js +45 -0
  191. package/dist/session/write.d.ts +61 -0
  192. package/dist/session/write.d.ts.map +1 -0
  193. package/dist/session/write.js +213 -0
  194. package/dist/shellInvocation.d.ts +6 -0
  195. package/dist/shellInvocation.d.ts.map +1 -0
  196. package/dist/shellInvocation.js +8 -0
  197. package/dist/shellInvocation.test.d.ts +2 -0
  198. package/dist/shellInvocation.test.d.ts.map +1 -0
  199. package/dist/shellInvocation.test.js +18 -0
  200. package/dist/telemetry/audit-log.d.ts +56 -0
  201. package/dist/telemetry/audit-log.d.ts.map +1 -0
  202. package/dist/telemetry/audit-log.js +59 -0
  203. package/dist/telemetry/exporters.d.ts +35 -0
  204. package/dist/telemetry/exporters.d.ts.map +1 -0
  205. package/dist/telemetry/exporters.js +141 -0
  206. package/dist/telemetry/index.d.ts +12 -0
  207. package/dist/telemetry/index.d.ts.map +1 -0
  208. package/dist/telemetry/index.js +25 -0
  209. package/dist/telemetry/provider.d.ts +57 -0
  210. package/dist/telemetry/provider.d.ts.map +1 -0
  211. package/dist/telemetry/provider.js +261 -0
  212. package/dist/telemetry/span-tree.d.ts +46 -0
  213. package/dist/telemetry/span-tree.d.ts.map +1 -0
  214. package/dist/telemetry/span-tree.js +93 -0
  215. package/dist/telemetry/traceContext.d.ts +10 -0
  216. package/dist/telemetry/traceContext.d.ts.map +1 -0
  217. package/dist/telemetry/traceContext.js +43 -0
  218. package/dist/telemetry/types.d.ts +109 -0
  219. package/dist/telemetry/types.d.ts.map +1 -0
  220. package/dist/telemetry/types.js +21 -0
  221. package/package.json +137 -0
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ /**
3
+ * GAP-STATE-003: Session State Persistence.
4
+ *
5
+ * Stores rich persistent state: findings, file modifications, breakpoint patterns,
6
+ * and user preferences. Follows the same atomic temp-file + rename pattern
7
+ * as context.ts and history.ts.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.SESSION_PERSISTENT_SCHEMA_VERSION = void 0;
44
+ exports.getSessionPersistentStatePath = getSessionPersistentStatePath;
45
+ exports.getSessionPersistentState = getSessionPersistentState;
46
+ exports.addFinding = addFinding;
47
+ exports.setPreference = setPreference;
48
+ exports.recordFileModification = recordFileModification;
49
+ exports.recordBreakpointInteraction = recordBreakpointInteraction;
50
+ exports.buildResumeContext = buildResumeContext;
51
+ const node_fs_1 = require("node:fs");
52
+ const path = __importStar(require("node:path"));
53
+ exports.SESSION_PERSISTENT_SCHEMA_VERSION = "2026.01.session-persistent-v1";
54
+ // ---------------------------------------------------------------------------
55
+ // Internal helpers
56
+ // ---------------------------------------------------------------------------
57
+ function emptyPersistentState() {
58
+ return {
59
+ schemaVersion: exports.SESSION_PERSISTENT_SCHEMA_VERSION,
60
+ findings: [],
61
+ preferences: {},
62
+ fileModifications: [],
63
+ breakpointPatterns: [],
64
+ };
65
+ }
66
+ function nowTimestamp() {
67
+ return new Date().toISOString();
68
+ }
69
+ async function readRaw(filePath) {
70
+ let raw;
71
+ try {
72
+ raw = await node_fs_1.promises.readFile(filePath, "utf8");
73
+ }
74
+ catch (error) {
75
+ const err = error;
76
+ if (err.code === "ENOENT")
77
+ return emptyPersistentState();
78
+ return emptyPersistentState();
79
+ }
80
+ try {
81
+ const data = JSON.parse(raw);
82
+ return {
83
+ schemaVersion: typeof data.schemaVersion === "string" ? data.schemaVersion : exports.SESSION_PERSISTENT_SCHEMA_VERSION,
84
+ findings: Array.isArray(data.findings) ? data.findings : [],
85
+ preferences: data.preferences && typeof data.preferences === "object" ? data.preferences : {},
86
+ fileModifications: Array.isArray(data.fileModifications) ? data.fileModifications : [],
87
+ breakpointPatterns: Array.isArray(data.breakpointPatterns) ? data.breakpointPatterns : [],
88
+ };
89
+ }
90
+ catch {
91
+ return emptyPersistentState();
92
+ }
93
+ }
94
+ async function writeRaw(filePath, data) {
95
+ const dir = path.dirname(filePath);
96
+ const tempPath = `${filePath}.tmp.${process.pid}`;
97
+ await node_fs_1.promises.mkdir(dir, { recursive: true });
98
+ await node_fs_1.promises.writeFile(tempPath, JSON.stringify(data, null, 2), "utf8");
99
+ await node_fs_1.promises.rename(tempPath, filePath);
100
+ }
101
+ // ---------------------------------------------------------------------------
102
+ // Public API
103
+ // ---------------------------------------------------------------------------
104
+ function getSessionPersistentStatePath(stateDir, sessionId) {
105
+ return path.join(stateDir, `${sessionId}.persistent.json`);
106
+ }
107
+ async function getSessionPersistentState(stateDir, sessionId) {
108
+ return readRaw(getSessionPersistentStatePath(stateDir, sessionId));
109
+ }
110
+ async function addFinding(stateDir, sessionId, finding) {
111
+ const filePath = getSessionPersistentStatePath(stateDir, sessionId);
112
+ const data = await readRaw(filePath);
113
+ data.findings.push({ ...finding, timestamp: nowTimestamp() });
114
+ await writeRaw(filePath, data);
115
+ }
116
+ async function setPreference(stateDir, sessionId, key, value) {
117
+ const filePath = getSessionPersistentStatePath(stateDir, sessionId);
118
+ const data = await readRaw(filePath);
119
+ data.preferences[key] = value;
120
+ await writeRaw(filePath, data);
121
+ }
122
+ async function recordFileModification(stateDir, sessionId, mod) {
123
+ const filePath = getSessionPersistentStatePath(stateDir, sessionId);
124
+ const data = await readRaw(filePath);
125
+ data.fileModifications.push({ ...mod, timestamp: nowTimestamp() });
126
+ await writeRaw(filePath, data);
127
+ }
128
+ async function recordBreakpointInteraction(stateDir, sessionId, breakpointId, action) {
129
+ const filePath = getSessionPersistentStatePath(stateDir, sessionId);
130
+ const data = await readRaw(filePath);
131
+ const now = nowTimestamp();
132
+ const existing = data.breakpointPatterns.find((p) => p.breakpointId === breakpointId);
133
+ if (existing) {
134
+ if (action === "approved")
135
+ existing.approvedCount++;
136
+ else
137
+ existing.rejectedCount++;
138
+ existing.lastAction = action;
139
+ existing.lastInteractionAt = now;
140
+ }
141
+ else {
142
+ data.breakpointPatterns.push({
143
+ breakpointId,
144
+ approvedCount: action === "approved" ? 1 : 0,
145
+ rejectedCount: action === "rejected" ? 1 : 0,
146
+ lastAction: action,
147
+ lastInteractionAt: now,
148
+ });
149
+ }
150
+ await writeRaw(filePath, data);
151
+ }
152
+ /**
153
+ * Render persistent state into a markdown section for resume prompt injection.
154
+ * Returns empty string when no meaningful data exists.
155
+ */
156
+ async function buildResumeContext(stateDir, sessionId) {
157
+ const state = await getSessionPersistentState(stateDir, sessionId);
158
+ const sections = [];
159
+ if (state.findings.length > 0) {
160
+ const lines = state.findings.map((f) => `- [${f.category}] ${f.content}`);
161
+ sections.push(`### Findings\n${lines.join("\n")}`);
162
+ }
163
+ const prefKeys = Object.keys(state.preferences);
164
+ if (prefKeys.length > 0) {
165
+ const lines = prefKeys.map((k) => `- **${k}**: ${state.preferences[k]}`);
166
+ sections.push(`### Preferences\n${lines.join("\n")}`);
167
+ }
168
+ if (state.fileModifications.length > 0) {
169
+ const recent = state.fileModifications.slice(-10);
170
+ const lines = recent.map((m) => `- ${m.action} \`${m.filePath}\``);
171
+ sections.push(`### Recent File Changes\n${lines.join("\n")}`);
172
+ }
173
+ if (state.breakpointPatterns.length > 0) {
174
+ const lines = state.breakpointPatterns.map((p) => `- \`${p.breakpointId}\`: ${p.approvedCount} approved, ${p.rejectedCount} rejected (last: ${p.lastAction})`);
175
+ sections.push(`### Breakpoint Patterns\n${lines.join("\n")}`);
176
+ }
177
+ if (sections.length === 0)
178
+ return "";
179
+ return `## Session Context\n\n${sections.join("\n\n")}`;
180
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * GAP-STATE-006: Session Rewind and History.
3
+ *
4
+ * Provides a rewind point manager that works with the SessionTree
5
+ * to create, list, and navigate to rewind points in the conversation.
6
+ */
7
+ import type { SessionTree } from './tree.js';
8
+ export interface RewindPoint {
9
+ /** Unique rewind point identifier. */
10
+ id: string;
11
+ /** The tree node at this rewind point. */
12
+ nodeId: string;
13
+ /** The branch ID at this rewind point. */
14
+ branchId: string;
15
+ /** ISO timestamp when the rewind point was created. */
16
+ timestamp: string;
17
+ /** Optional label for the rewind point. */
18
+ label?: string;
19
+ }
20
+ export declare class SessionRewindManager {
21
+ private rewindPoints;
22
+ /**
23
+ * Create a new rewind point at the current position in the tree.
24
+ */
25
+ createRewindPoint(tree: SessionTree, label?: string): RewindPoint;
26
+ /**
27
+ * List all rewind points, sorted by creation time (most recent first).
28
+ */
29
+ listRewindPoints(): RewindPoint[];
30
+ /**
31
+ * Rewind to a specific point by navigating the tree to that node
32
+ * and forking a new branch from it.
33
+ * Returns the new branch ID.
34
+ */
35
+ rewindTo(tree: SessionTree, pointId: string): string;
36
+ /**
37
+ * Check whether there are any rewind points available.
38
+ */
39
+ canRewind(): boolean;
40
+ /**
41
+ * Get a rewind point by ID.
42
+ */
43
+ getRewindPoint(pointId: string): RewindPoint | undefined;
44
+ }
45
+ //# sourceMappingURL=rewind.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewind.d.ts","sourceRoot":"","sources":["../../src/session/rewind.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAO7C,MAAM,WAAW,WAAW;IAC1B,sCAAsC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAMD,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,YAAY,CAAuC;IAE3D;;OAEG;IACH,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,WAAW;IAajE;;OAEG;IACH,gBAAgB,IAAI,WAAW,EAAE;IAMjC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAgBpD;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;CAGzD"}
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ /**
3
+ * GAP-STATE-006: Session Rewind and History.
4
+ *
5
+ * Provides a rewind point manager that works with the SessionTree
6
+ * to create, list, and navigate to rewind points in the conversation.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.SessionRewindManager = void 0;
10
+ const node_crypto_1 = require("node:crypto");
11
+ const tree_js_1 = require("./tree.js");
12
+ // ---------------------------------------------------------------------------
13
+ // SessionRewindManager
14
+ // ---------------------------------------------------------------------------
15
+ class SessionRewindManager {
16
+ rewindPoints = new Map();
17
+ /**
18
+ * Create a new rewind point at the current position in the tree.
19
+ */
20
+ createRewindPoint(tree, label) {
21
+ const point = {
22
+ id: (0, node_crypto_1.randomUUID)(),
23
+ nodeId: tree.activeNodeId,
24
+ branchId: tree.activeBranchId,
25
+ timestamp: new Date().toISOString(),
26
+ label,
27
+ };
28
+ this.rewindPoints.set(point.id, point);
29
+ return point;
30
+ }
31
+ /**
32
+ * List all rewind points, sorted by creation time (most recent first).
33
+ */
34
+ listRewindPoints() {
35
+ return [...this.rewindPoints.values()].sort((a, b) => b.timestamp.localeCompare(a.timestamp));
36
+ }
37
+ /**
38
+ * Rewind to a specific point by navigating the tree to that node
39
+ * and forking a new branch from it.
40
+ * Returns the new branch ID.
41
+ */
42
+ rewindTo(tree, pointId) {
43
+ const point = this.rewindPoints.get(pointId);
44
+ if (!point) {
45
+ throw new Error(`Rewind point "${pointId}" not found`);
46
+ }
47
+ // Verify the node still exists
48
+ if (!tree.nodes.has(point.nodeId)) {
49
+ throw new Error(`Node "${point.nodeId}" no longer exists in the tree`);
50
+ }
51
+ // Fork from that node to create a new branch
52
+ const newBranchId = (0, tree_js_1.forkFromNode)(tree, point.nodeId);
53
+ return newBranchId;
54
+ }
55
+ /**
56
+ * Check whether there are any rewind points available.
57
+ */
58
+ canRewind() {
59
+ return this.rewindPoints.size > 0;
60
+ }
61
+ /**
62
+ * Get a rewind point by ID.
63
+ */
64
+ getRewindPoint(pointId) {
65
+ return this.rewindPoints.get(pointId);
66
+ }
67
+ }
68
+ exports.SessionRewindManager = SessionRewindManager;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rewind.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rewind.test.d.ts","sourceRoot":"","sources":["../../src/session/rewind.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const rewind_js_1 = require("./rewind.js");
5
+ const tree_js_1 = require("./tree.js");
6
+ (0, vitest_1.describe)('session/rewind', () => {
7
+ let tree;
8
+ let manager;
9
+ (0, vitest_1.beforeEach)(() => {
10
+ tree = (0, tree_js_1.createSessionTree)();
11
+ manager = new rewind_js_1.SessionRewindManager();
12
+ });
13
+ // -------------------------------------------------------------------------
14
+ // createRewindPoint
15
+ // -------------------------------------------------------------------------
16
+ (0, vitest_1.describe)('createRewindPoint', () => {
17
+ (0, vitest_1.it)('creates a rewind point at current position', () => {
18
+ (0, tree_js_1.addMessage)(tree, 'user', 'Hello');
19
+ const point = manager.createRewindPoint(tree, 'before-experiment');
20
+ (0, vitest_1.expect)(point.nodeId).toBe(tree.activeNodeId);
21
+ (0, vitest_1.expect)(point.branchId).toBe(tree.activeBranchId);
22
+ (0, vitest_1.expect)(point.label).toBe('before-experiment');
23
+ (0, vitest_1.expect)(point.timestamp).toBeTruthy();
24
+ (0, vitest_1.expect)(point.id).toBeTruthy();
25
+ });
26
+ (0, vitest_1.it)('creates a point without label', () => {
27
+ const point = manager.createRewindPoint(tree);
28
+ (0, vitest_1.expect)(point.label).toBeUndefined();
29
+ });
30
+ });
31
+ // -------------------------------------------------------------------------
32
+ // listRewindPoints
33
+ // -------------------------------------------------------------------------
34
+ (0, vitest_1.describe)('listRewindPoints', () => {
35
+ (0, vitest_1.it)('returns empty array initially', () => {
36
+ (0, vitest_1.expect)(manager.listRewindPoints()).toHaveLength(0);
37
+ });
38
+ (0, vitest_1.it)('returns points sorted by most recent first', () => {
39
+ (0, tree_js_1.addMessage)(tree, 'user', 'Step 1');
40
+ const p1 = manager.createRewindPoint(tree, 'first');
41
+ // Manually adjust timestamp so ordering is deterministic
42
+ p1.timestamp = '2026-01-01T00:00:00.000Z';
43
+ (0, tree_js_1.addMessage)(tree, 'assistant', 'Step 2');
44
+ const p2 = manager.createRewindPoint(tree, 'second');
45
+ p2.timestamp = '2026-01-01T00:01:00.000Z';
46
+ const points = manager.listRewindPoints();
47
+ (0, vitest_1.expect)(points).toHaveLength(2);
48
+ (0, vitest_1.expect)(points[0].label).toBe('second');
49
+ (0, vitest_1.expect)(points[1].label).toBe('first');
50
+ });
51
+ });
52
+ // -------------------------------------------------------------------------
53
+ // canRewind
54
+ // -------------------------------------------------------------------------
55
+ (0, vitest_1.describe)('canRewind', () => {
56
+ (0, vitest_1.it)('returns false when no rewind points exist', () => {
57
+ (0, vitest_1.expect)(manager.canRewind()).toBe(false);
58
+ });
59
+ (0, vitest_1.it)('returns true when rewind points exist', () => {
60
+ manager.createRewindPoint(tree);
61
+ (0, vitest_1.expect)(manager.canRewind()).toBe(true);
62
+ });
63
+ });
64
+ // -------------------------------------------------------------------------
65
+ // rewindTo
66
+ // -------------------------------------------------------------------------
67
+ (0, vitest_1.describe)('rewindTo', () => {
68
+ (0, vitest_1.it)('forks a new branch from the rewind point', () => {
69
+ (0, tree_js_1.addMessage)(tree, 'user', 'Question 1');
70
+ const point = manager.createRewindPoint(tree, 'checkpoint');
71
+ (0, tree_js_1.addMessage)(tree, 'assistant', 'Answer 1');
72
+ (0, tree_js_1.addMessage)(tree, 'user', 'Question 2');
73
+ const newBranch = manager.rewindTo(tree, point.id);
74
+ (0, vitest_1.expect)(newBranch).toMatch(/^branch-/);
75
+ (0, vitest_1.expect)(tree.activeNodeId).toBe(point.nodeId);
76
+ (0, vitest_1.expect)(tree.activeBranchId).toBe(newBranch);
77
+ });
78
+ (0, vitest_1.it)('throws for unknown rewind point', () => {
79
+ (0, vitest_1.expect)(() => manager.rewindTo(tree, 'nonexistent')).toThrow('not found');
80
+ });
81
+ });
82
+ // -------------------------------------------------------------------------
83
+ // getRewindPoint
84
+ // -------------------------------------------------------------------------
85
+ (0, vitest_1.describe)('getRewindPoint', () => {
86
+ (0, vitest_1.it)('returns the point by ID', () => {
87
+ const created = manager.createRewindPoint(tree, 'my-point');
88
+ const fetched = manager.getRewindPoint(created.id);
89
+ (0, vitest_1.expect)(fetched).toBeDefined();
90
+ (0, vitest_1.expect)(fetched.label).toBe('my-point');
91
+ });
92
+ (0, vitest_1.it)('returns undefined for unknown ID', () => {
93
+ (0, vitest_1.expect)(manager.getRewindPoint('nope')).toBeUndefined();
94
+ });
95
+ });
96
+ });
@@ -0,0 +1,29 @@
1
+ export interface TreeNode {
2
+ id: string;
3
+ parentId: string | null;
4
+ branchId: string;
5
+ role: 'user' | 'assistant' | 'system' | 'tool';
6
+ content: string;
7
+ metadata?: Record<string, unknown>;
8
+ timestamp: string;
9
+ bookmark?: string;
10
+ }
11
+ export interface SessionTree {
12
+ rootId: string;
13
+ activeBranchId: string;
14
+ activeNodeId: string;
15
+ nodes: Map<string, TreeNode>;
16
+ branches: Map<string, string[]>;
17
+ }
18
+ export declare function createSessionTree(): SessionTree;
19
+ export declare function addMessage(tree: SessionTree, role: TreeNode['role'], content: string, metadata?: Record<string, unknown>): TreeNode;
20
+ export declare function forkFromNode(tree: SessionTree, nodeId: string): string;
21
+ export declare function navigateToNode(tree: SessionTree, nodeId: string): void;
22
+ export declare function getPathToNode(tree: SessionTree, nodeId: string): string[];
23
+ export declare function getMessages(tree: SessionTree, branchId?: string): TreeNode[];
24
+ export declare function getBranches(tree: SessionTree): string[];
25
+ export declare function bookmarkNode(tree: SessionTree, nodeId: string, label: string): void;
26
+ export declare function getBookmarks(tree: SessionTree): TreeNode[];
27
+ export declare function serializeTree(tree: SessionTree): string;
28
+ export declare function deserializeTree(json: string): SessionTree;
29
+ //# sourceMappingURL=tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/session/tree.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC7B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACjC;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAmB/C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,QAAQ,CAkBnI;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAatE;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAKtE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAUzE;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,EAAE,CAI5E;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,CAEvD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAInF;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,GAAG,QAAQ,EAAE,CAE1D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,CAQvD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CASzD"}
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSessionTree = createSessionTree;
4
+ exports.addMessage = addMessage;
5
+ exports.forkFromNode = forkFromNode;
6
+ exports.navigateToNode = navigateToNode;
7
+ exports.getPathToNode = getPathToNode;
8
+ exports.getMessages = getMessages;
9
+ exports.getBranches = getBranches;
10
+ exports.bookmarkNode = bookmarkNode;
11
+ exports.getBookmarks = getBookmarks;
12
+ exports.serializeTree = serializeTree;
13
+ exports.deserializeTree = deserializeTree;
14
+ const node_crypto_1 = require("node:crypto");
15
+ function createSessionTree() {
16
+ const rootId = (0, node_crypto_1.randomUUID)();
17
+ const branchId = 'main';
18
+ const root = {
19
+ id: rootId,
20
+ parentId: null,
21
+ branchId,
22
+ role: 'system',
23
+ content: 'Session started',
24
+ timestamp: new Date().toISOString(),
25
+ };
26
+ const nodes = new Map();
27
+ nodes.set(rootId, root);
28
+ const branches = new Map();
29
+ branches.set(branchId, [rootId]);
30
+ return { rootId, activeBranchId: branchId, activeNodeId: rootId, nodes, branches };
31
+ }
32
+ function addMessage(tree, role, content, metadata) {
33
+ const node = {
34
+ id: (0, node_crypto_1.randomUUID)(),
35
+ parentId: tree.activeNodeId,
36
+ branchId: tree.activeBranchId,
37
+ role,
38
+ content,
39
+ metadata,
40
+ timestamp: new Date().toISOString(),
41
+ };
42
+ tree.nodes.set(node.id, node);
43
+ const branch = tree.branches.get(tree.activeBranchId) ?? [];
44
+ branch.push(node.id);
45
+ tree.branches.set(tree.activeBranchId, branch);
46
+ tree.activeNodeId = node.id;
47
+ return node;
48
+ }
49
+ function forkFromNode(tree, nodeId) {
50
+ const node = tree.nodes.get(nodeId);
51
+ if (!node)
52
+ throw new Error(`Node ${nodeId} not found`);
53
+ const newBranchId = `branch-${(0, node_crypto_1.randomUUID)().slice(0, 8)}`;
54
+ // Copy path from root to this node
55
+ const path = getPathToNode(tree, nodeId);
56
+ tree.branches.set(newBranchId, [...path]);
57
+ tree.activeBranchId = newBranchId;
58
+ tree.activeNodeId = nodeId;
59
+ return newBranchId;
60
+ }
61
+ function navigateToNode(tree, nodeId) {
62
+ const node = tree.nodes.get(nodeId);
63
+ if (!node)
64
+ throw new Error(`Node ${nodeId} not found`);
65
+ tree.activeNodeId = nodeId;
66
+ tree.activeBranchId = node.branchId;
67
+ }
68
+ function getPathToNode(tree, nodeId) {
69
+ const path = [];
70
+ let current = nodeId;
71
+ while (current) {
72
+ path.unshift(current);
73
+ const node = tree.nodes.get(current);
74
+ if (!node?.parentId)
75
+ break;
76
+ current = node.parentId;
77
+ }
78
+ return path;
79
+ }
80
+ function getMessages(tree, branchId) {
81
+ const bid = branchId ?? tree.activeBranchId;
82
+ const nodeIds = tree.branches.get(bid) ?? [];
83
+ return nodeIds.map(id => tree.nodes.get(id)).filter(Boolean);
84
+ }
85
+ function getBranches(tree) {
86
+ return [...tree.branches.keys()];
87
+ }
88
+ function bookmarkNode(tree, nodeId, label) {
89
+ const node = tree.nodes.get(nodeId);
90
+ if (!node)
91
+ throw new Error(`Node ${nodeId} not found`);
92
+ node.bookmark = label;
93
+ }
94
+ function getBookmarks(tree) {
95
+ return [...tree.nodes.values()].filter(n => n.bookmark);
96
+ }
97
+ function serializeTree(tree) {
98
+ return JSON.stringify({
99
+ rootId: tree.rootId,
100
+ activeBranchId: tree.activeBranchId,
101
+ activeNodeId: tree.activeNodeId,
102
+ nodes: Object.fromEntries(tree.nodes),
103
+ branches: Object.fromEntries(tree.branches),
104
+ });
105
+ }
106
+ function deserializeTree(json) {
107
+ const data = JSON.parse(json);
108
+ return {
109
+ rootId: data.rootId,
110
+ activeBranchId: data.activeBranchId,
111
+ activeNodeId: data.activeNodeId,
112
+ nodes: new Map(Object.entries(data.nodes)),
113
+ branches: new Map(Object.entries(data.branches)),
114
+ };
115
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tree.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.test.d.ts","sourceRoot":"","sources":["../../src/session/tree.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const tree_js_1 = require("./tree.js");
5
+ (0, vitest_1.describe)('session/tree', () => {
6
+ (0, vitest_1.it)('creates a tree with a root node', () => {
7
+ const tree = (0, tree_js_1.createSessionTree)();
8
+ (0, vitest_1.expect)(tree.rootId).toBeTruthy();
9
+ (0, vitest_1.expect)(tree.activeBranchId).toBe('main');
10
+ (0, vitest_1.expect)(tree.nodes.size).toBe(1);
11
+ });
12
+ (0, vitest_1.it)('adds messages to the active branch', () => {
13
+ const tree = (0, tree_js_1.createSessionTree)();
14
+ (0, tree_js_1.addMessage)(tree, 'user', 'Hello');
15
+ (0, tree_js_1.addMessage)(tree, 'assistant', 'Hi there');
16
+ const messages = (0, tree_js_1.getMessages)(tree);
17
+ (0, vitest_1.expect)(messages).toHaveLength(3); // root + 2
18
+ (0, vitest_1.expect)(messages[1].role).toBe('user');
19
+ (0, vitest_1.expect)(messages[2].content).toBe('Hi there');
20
+ });
21
+ (0, vitest_1.it)('forks from a node creating a new branch', () => {
22
+ const tree = (0, tree_js_1.createSessionTree)();
23
+ const m1 = (0, tree_js_1.addMessage)(tree, 'user', 'First question');
24
+ (0, tree_js_1.addMessage)(tree, 'assistant', 'First answer');
25
+ const newBranch = (0, tree_js_1.forkFromNode)(tree, m1.id);
26
+ (0, vitest_1.expect)(newBranch).toMatch(/^branch-/);
27
+ (0, vitest_1.expect)(tree.activeBranchId).toBe(newBranch);
28
+ (0, vitest_1.expect)((0, tree_js_1.getBranches)(tree)).toHaveLength(2);
29
+ (0, tree_js_1.addMessage)(tree, 'user', 'Different question');
30
+ const branchMessages = (0, tree_js_1.getMessages)(tree, newBranch);
31
+ (0, vitest_1.expect)(branchMessages[branchMessages.length - 1].content).toBe('Different question');
32
+ });
33
+ (0, vitest_1.it)('navigates to a specific node', () => {
34
+ const tree = (0, tree_js_1.createSessionTree)();
35
+ const m1 = (0, tree_js_1.addMessage)(tree, 'user', 'Q1');
36
+ (0, tree_js_1.addMessage)(tree, 'assistant', 'A1');
37
+ (0, tree_js_1.navigateToNode)(tree, m1.id);
38
+ (0, vitest_1.expect)(tree.activeNodeId).toBe(m1.id);
39
+ });
40
+ (0, vitest_1.it)('gets path from root to a node', () => {
41
+ const tree = (0, tree_js_1.createSessionTree)();
42
+ (0, tree_js_1.addMessage)(tree, 'user', 'Step 1');
43
+ const m2 = (0, tree_js_1.addMessage)(tree, 'assistant', 'Step 2');
44
+ const path = (0, tree_js_1.getPathToNode)(tree, m2.id);
45
+ (0, vitest_1.expect)(path).toHaveLength(3); // root → user → assistant
46
+ (0, vitest_1.expect)(path[0]).toBe(tree.rootId);
47
+ (0, vitest_1.expect)(path[path.length - 1]).toBe(m2.id);
48
+ });
49
+ (0, vitest_1.it)('bookmarks a node', () => {
50
+ const tree = (0, tree_js_1.createSessionTree)();
51
+ const m1 = (0, tree_js_1.addMessage)(tree, 'user', 'Important question');
52
+ (0, tree_js_1.bookmarkNode)(tree, m1.id, 'key-decision');
53
+ const bookmarks = (0, tree_js_1.getBookmarks)(tree);
54
+ (0, vitest_1.expect)(bookmarks).toHaveLength(1);
55
+ (0, vitest_1.expect)(bookmarks[0].bookmark).toBe('key-decision');
56
+ });
57
+ (0, vitest_1.it)('serializes and deserializes a tree', () => {
58
+ const tree = (0, tree_js_1.createSessionTree)();
59
+ (0, tree_js_1.addMessage)(tree, 'user', 'Hello');
60
+ (0, tree_js_1.addMessage)(tree, 'assistant', 'World');
61
+ const json = (0, tree_js_1.serializeTree)(tree);
62
+ const restored = (0, tree_js_1.deserializeTree)(json);
63
+ (0, vitest_1.expect)(restored.nodes.size).toBe(tree.nodes.size);
64
+ (0, vitest_1.expect)(restored.activeBranchId).toBe(tree.activeBranchId);
65
+ (0, vitest_1.expect)((0, tree_js_1.getMessages)(restored)).toHaveLength(3);
66
+ });
67
+ (0, vitest_1.it)('throws on navigate to nonexistent node', () => {
68
+ const tree = (0, tree_js_1.createSessionTree)();
69
+ (0, vitest_1.expect)(() => (0, tree_js_1.navigateToNode)(tree, 'nonexistent')).toThrow('not found');
70
+ });
71
+ (0, vitest_1.it)('throws on fork from nonexistent node', () => {
72
+ const tree = (0, tree_js_1.createSessionTree)();
73
+ (0, vitest_1.expect)(() => (0, tree_js_1.forkFromNode)(tree, 'nonexistent')).toThrow('not found');
74
+ });
75
+ });