fraim-framework 2.0.194 → 2.0.195

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.
@@ -2252,9 +2252,20 @@ class AiHubServer {
2252
2252
  if (!Array.isArray(body.conversations)) {
2253
2253
  return res.status(400).json({ error: 'conversations array required' });
2254
2254
  }
2255
+ // The client strips server-owned heavy fields (messages/events/artifacts/run/
2256
+ // delegation) from its PUT payload to stay under the body-size limit. Merge them
2257
+ // back from the stored record so a client-owned change (e.g. reviewApproved) never
2258
+ // erases run history. List membership still comes from the incoming array, so
2259
+ // deletes are honored (a conversation absent here is dropped).
2260
+ const prior = this.conversationStore.loadProject(key);
2261
+ const priorById = new Map(prior.conversations.map((entry) => [entry.id, entry]));
2262
+ const conversations = body.conversations.map((incoming) => {
2263
+ const existing = incoming && incoming.id ? priorById.get(incoming.id) : undefined;
2264
+ return existing ? { ...existing, ...incoming } : incoming;
2265
+ });
2255
2266
  const saved = this.conversationStore.replaceProject(key, {
2256
2267
  activeId: body.activeId ?? null,
2257
- conversations: body.conversations,
2268
+ conversations,
2258
2269
  });
2259
2270
  return res.json({ projectPath: key, scope: scope ?? 'project', ...saved, source: 'disk' });
2260
2271
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-framework",
3
- "version": "2.0.194",
3
+ "version": "2.0.195",
4
4
  "description": "FRAIM: AI Workforce Infrastructure — the organizational capability that turns AI agents into an accountable workforce, their operators into capable AI managers, and executives into leaders with clear optics on AI proficiency.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -33,18 +33,18 @@
33
33
  <div class="am-email" id="am-email"></div>
34
34
  </div>
35
35
  </div>
36
- <button class="am-item" id="am-account" type="button">
36
+ <button class="am-item" id="am-account" type="button">
37
37
  <span class="am-ico">👤</span>
38
38
  <div><div class="am-label">Account</div><div class="am-sub">API key, sessions, billing</div></div>
39
- </button>
39
+ </button>
40
40
  <button class="am-item" id="am-brain" type="button">
41
41
  <span class="am-ico">🧠</span>
42
42
  <div><div class="am-label">Brain</div><div class="am-sub">Everything your team knows</div></div>
43
43
  </button>
44
- <button class="am-item" id="am-analytics" type="button">
44
+ <button class="am-item" id="am-analytics" type="button">
45
45
  <span class="am-ico">📊</span>
46
46
  <div><div class="am-label">Analytics</div><div class="am-sub">Usage, cost, and ROI</div></div>
47
- </button>
47
+ </button>
48
48
  <button class="am-item am-theme" id="am-theme-toggle" type="button" role="switch" aria-checked="false">
49
49
  <span class="am-ico" id="am-theme-ico">🌙</span>
50
50
  <div><div class="am-label">Dark mode</div><div class="am-sub" id="am-theme-sub">Off</div></div>
@@ -141,21 +141,21 @@
141
141
  </div>
142
142
  </section>
143
143
 
144
- <section class="hub-area" id="area-connected" hidden>
145
- <div class="connected-shell">
146
- <div class="connected-toolbar">
147
- <div class="connected-title-block">
148
- <div class="connected-kicker" id="connected-kicker">Connected FRAIM</div>
149
- <div class="connected-title" id="connected-title">Account</div>
150
- </div>
151
- <div class="connected-origin" id="connected-origin"></div>
152
- <button class="connected-close" id="connected-close" type="button">Back</button>
153
- </div>
154
- <iframe id="connected-frame" class="connected-frame" title="FRAIM connected surface" allow="clipboard-read; clipboard-write"></iframe>
155
- </div>
156
- </section>
157
-
158
- <!-- Issue #512: Projects area wraps the existing single-surface Hub. -->
144
+ <section class="hub-area" id="area-connected" hidden>
145
+ <div class="connected-shell">
146
+ <div class="connected-toolbar">
147
+ <div class="connected-title-block">
148
+ <div class="connected-kicker" id="connected-kicker">Connected FRAIM</div>
149
+ <div class="connected-title" id="connected-title">Account</div>
150
+ </div>
151
+ <div class="connected-origin" id="connected-origin"></div>
152
+ <button class="connected-close" id="connected-close" type="button">Back</button>
153
+ </div>
154
+ <iframe id="connected-frame" class="connected-frame" title="FRAIM connected surface" allow="clipboard-read; clipboard-write"></iframe>
155
+ </div>
156
+ </section>
157
+
158
+ <!-- Issue #512: Projects area wraps the existing single-surface Hub. -->
159
159
  <section class="hub-area on" id="area-projects">
160
160
  <div class="proj-tabs" id="proj-tabs">
161
161
  <button class="ptab on" id="ptab-overview" type="button" data-view="overview">Overview</button>
@@ -868,6 +868,6 @@
868
868
  </div>
869
869
  </div>
870
870
 
871
- <script src="./script.js"></script>
871
+ <script src="./script.js?v=conv-persist-20260705"></script>
872
872
  </body>
873
873
  </html>
@@ -507,11 +507,29 @@ function normalizeGeminiConversationMessages(conv) {
507
507
  return changed;
508
508
  }
509
509
 
510
+ // Server-owned conversation fields. The server re-derives these from the run
511
+ // registry and writes them to the conversation store on every run update
512
+ // (persistRunConversation), so the client must NOT re-upload them: a large run's
513
+ // messages/events/artifacts push the PUT body past the server's size limit, the
514
+ // request 413s, the error is swallowed, and the client-owned flags in the SAME
515
+ // payload (e.g. reviewApproved from "Mark complete") never persist. Strip them
516
+ // here; the server's PUT handler merges them back from the stored record.
517
+ const SERVER_OWNED_CONV_FIELDS = ['messages', 'events', 'artifacts', 'run', 'delegation'];
518
+ function slimConversationForPersist(conv) {
519
+ if (!conv || typeof conv !== 'object') return conv;
520
+ const slim = { ...conv };
521
+ for (const field of SERVER_OWNED_CONV_FIELDS) delete slim[field];
522
+ return slim;
523
+ }
524
+ function slimConversationsForPersist(list) {
525
+ return Array.isArray(list) ? list.map(slimConversationForPersist) : list;
526
+ }
527
+
510
528
  function projectConversationPayload() {
511
529
  return {
512
530
  projectPath: state.projectPath || '',
513
531
  activeId: state.activeId || null,
514
- conversations: projectConversations(),
532
+ conversations: slimConversationsForPersist(projectConversations()),
515
533
  };
516
534
  }
517
535
 
@@ -537,7 +555,7 @@ function scheduleConversationDiskPersist() {
537
555
  await requestJson('/api/ai-hub/conversations', {
538
556
  method: 'PUT',
539
557
  headers: { 'Content-Type': 'application/json' },
540
- body: JSON.stringify({ scope, activeId: null, conversations: bucket }),
558
+ body: JSON.stringify({ scope, activeId: null, conversations: slimConversationsForPersist(bucket) }),
541
559
  });
542
560
  }
543
561
  state.conversationDiskAvailable = true;