fraim-hub 2.0.228 → 2.0.229

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.
@@ -757,7 +757,7 @@ class AiHubConversationStore {
757
757
  }
758
758
  // Update the index header for one conversation and return the (header-shaped) project state.
759
759
  reindexAfterUpsert(bucketDir, bucketKey, conv, activeId) {
760
- const idx = this.readIndex(bucketDir) ?? { activeId: null, headers: [] };
760
+ const idx = this.loadIndex(bucketDir, bucketKey);
761
761
  const headers = idx.headers.filter((h) => h.id !== conv.id);
762
762
  headers.push(toHeader(conv));
763
763
  headers.sort(newestFirst);
@@ -2927,19 +2927,30 @@ class AiHubServer {
2927
2927
  return res.status(400).json({ error: 'projectPath required for project conversations' });
2928
2928
  }
2929
2929
  const projectPath = scope ? (0, conversation_store_1.conversationScopeKey)(scope, '') : ensureDirectoryPath(body.projectPath);
2930
- const patchFields = Object.keys(body).filter((key) => key !== 'projectPath' && key !== 'scope' && key !== 'activeId');
2930
+ const patch = { ...body };
2931
+ delete patch.projectPath;
2932
+ delete patch.scope;
2933
+ delete patch.activeId;
2934
+ const patchFields = Object.keys(patch);
2935
+ let saved = null;
2931
2936
  if (patchFields.length > 0) {
2932
- this.conversationStore.patchConversation(projectPath, req.params.conversationId, body);
2937
+ saved = this.conversationStore.patchConversation(projectPath, req.params.conversationId, patch);
2933
2938
  }
2934
2939
  if (body.activeId !== undefined) {
2935
2940
  // Body-safe: set activeId via the index, never by rewriting conversation bodies (#820).
2936
- this.conversationStore.setActiveId(projectPath, body.activeId);
2941
+ saved = this.conversationStore.setActiveId(projectPath, body.activeId);
2937
2942
  }
2938
2943
  if (patchFields.length === 0 && body.activeId !== undefined) {
2939
2944
  return res.json({ projectPath, scope: scope ?? 'project', activeId: body.activeId, source: 'disk' });
2940
2945
  }
2941
- const loaded = this.conversationStore.loadProject(projectPath);
2942
- return res.json({ projectPath, ...loaded, source: 'disk' });
2946
+ const conversation = saved?.conversations.find((entry) => entry.id === req.params.conversationId) ?? null;
2947
+ return res.json({
2948
+ projectPath,
2949
+ scope: scope ?? 'project',
2950
+ activeId: saved?.activeId ?? null,
2951
+ conversation,
2952
+ source: 'disk',
2953
+ });
2943
2954
  }
2944
2955
  catch (error) {
2945
2956
  return res.status(400).json({ error: error instanceof Error ? error.message : 'Could not persist conversation.' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim-hub",
3
- "version": "2.0.228",
3
+ "version": "2.0.229",
4
4
  "description": "FRAIM Hub local companion package.",
5
5
  "bin": {
6
6
  "fraim-hub": "bin/fraim-hub.js"
@@ -158,7 +158,7 @@
158
158
  "electron": "^41.2.2",
159
159
  "electron-updater": "^6.8.9",
160
160
  "express": "^5.2.1",
161
- "fraim": "2.0.228",
161
+ "fraim": "2.0.229",
162
162
  "mongodb": "^7.0.0",
163
163
  "node-cron": "4.2.1",
164
164
  "node-edge-tts": "^1.2.10",
@@ -728,6 +728,29 @@ function persistActiveConversationSelection(id) {
728
728
  });
729
729
  }
730
730
 
731
+ function persistConversation(conv) {
732
+ if (!conv || !conv.id) return;
733
+ const body = slimConversationForPersist(conv);
734
+ if (!body || typeof body !== 'object') return;
735
+ const scope = convScope(conv);
736
+ if (scope === 'manager' || scope === 'company') body.scope = scope;
737
+ else {
738
+ body.projectPath = conv.projectPath || state.projectPath || '';
739
+ if (!body.projectPath) return;
740
+ }
741
+ if (state.activeId === conv.id) body.activeId = conv.id;
742
+ requestJson(`/api/ai-hub/conversations/${encodeURIComponent(conv.id)}`, {
743
+ method: 'PATCH',
744
+ headers: { 'Content-Type': 'application/json' },
745
+ body: JSON.stringify(body),
746
+ }).then(() => {
747
+ state.conversationDiskAvailable = true;
748
+ }).catch((error) => {
749
+ state.conversationDiskAvailable = false;
750
+ console.warn('Could not persist conversation to local disk:', error);
751
+ });
752
+ }
753
+
731
754
  function projectConversationPayload() {
732
755
  return {
733
756
  projectPath: state.projectPath || '',
@@ -1260,7 +1283,7 @@ function upsertConversation(conv, options) {
1260
1283
  else list.unshift(conv);
1261
1284
  state.conversations[key] = list;
1262
1285
  const opts = options || {};
1263
- if (opts.disk !== false) persistConversations({ scope: convScope(conv) });
1286
+ if (opts.disk !== false) persistConversation(conv);
1264
1287
  }
1265
1288
 
1266
1289
  function newConversationId() {
@@ -4834,10 +4857,10 @@ function isTaughtJob(jobId) {
4834
4857
  // Start the evolve-employee job, optionally seeded from a completed run or a
4835
4858
  // document. This is the single entry point shared by the rail teach button,
4836
4859
  // roster chip, and the "+ New job" palette teach entries.
4837
- function startTeachFlow(opts) {
4838
- const options = opts || {};
4839
- const employeeId = (state.bootstrap && state.bootstrap.preferences && state.bootstrap.preferences.employeeId) || 'claude';
4840
- const seedConv = options.seedConv || null;
4860
+ function startTeachFlow(opts) {
4861
+ const options = opts || {};
4862
+ const employeeId = (state.bootstrap && state.bootstrap.preferences && state.bootstrap.preferences.employeeId) || 'claude';
4863
+ const seedConv = options.seedConv || null;
4841
4864
  let instructions = '';
4842
4865
  if (options.seedSource === 'run' && seedConv) {
4843
4866
  const transcript = (seedConv.messages || [])
@@ -4854,9 +4877,13 @@ function startTeachFlow(opts) {
4854
4877
  } else {
4855
4878
  instructions = 'Teach yourself a new job. I will describe the capability I want. Pre-fill the plan, check for an existing job first, and recommend which employee should own it.';
4856
4879
  }
4857
- const job = { id: 'evolve-employee', title: options.mode === 'customize' ? 'Improve a job' : 'Teach a new skill' };
4858
- startRun(job, instructions, employeeId);
4859
- }
4880
+ const job = {
4881
+ id: 'evolve-employee',
4882
+ title: options.mode === 'customize' ? 'Improve a job' : 'Teach a new job',
4883
+ requiredPersonaKey: 'mandy',
4884
+ };
4885
+ startRun(job, instructions, employeeId);
4886
+ }
4860
4887
 
4861
4888
  // Comments follow the artifact (R7.7): a PR opens GitHub; local artifacts open
4862
4889
  // from disk; reports view inline. Nothing new is invented - these surface what
@@ -6027,10 +6054,10 @@ async function startRun(job, instructions, employeeId, preassignedConvId, invoke
6027
6054
  };
6028
6055
  upsertConversation(conv);
6029
6056
  state.activeId = conv.id;
6057
+ persistActiveConversationSelection(conv.id);
6030
6058
  // Issue #539: make Cmd/Ctrl+Shift+R available as soon as the run is queued,
6031
6059
  // not only after the server responds with the run id.
6032
6060
  state.lastRun = { job, instructions, employeeId };
6033
- persistConversations();
6034
6061
  renderRail();
6035
6062
  renderActive();
6036
6063
 
@@ -6907,14 +6934,12 @@ function renderDelegateButton(personaKey) {
6907
6934
 
6908
6935
  function wireEvents() {
6909
6936
  els['project-button'].addEventListener('click', pickProject);
6910
- els['new-conv-btn'].addEventListener('click', openModal);
6911
- if (els['teach-btn']) {
6912
- els['teach-btn'].addEventListener('click', () => {
6913
- openModal({});
6914
- const q = document.getElementById('cp-search');
6915
- if (q) { q.value = 'teach'; q.dispatchEvent(new Event('input')); }
6916
- });
6917
- }
6937
+ els['new-conv-btn'].addEventListener('click', openModal);
6938
+ if (els['teach-btn']) {
6939
+ els['teach-btn'].addEventListener('click', () => {
6940
+ startTeachFlow({ seedSource: 'description' });
6941
+ });
6942
+ }
6918
6943
  els['cancel1'].addEventListener('click', closeModal);
6919
6944
  // Issue #385: hire-required notice back button.
6920
6945
  if (els['hire-notice-back']) {
@@ -7471,7 +7496,7 @@ async function autoOnboardProject() {
7471
7496
  foldRunIntoConversation(conv, run);
7472
7497
  upsertConversation(conv);
7473
7498
  state.activeId = conv.id;
7474
- persistConversations();
7499
+ persistActiveConversationSelection(conv.id);
7475
7500
  renderRail();
7476
7501
  renderActive();
7477
7502
  startPolling();
@@ -7629,7 +7654,7 @@ function renderFirstRunLanding(mode) {
7629
7654
  foldRunIntoConversation(conv, run);
7630
7655
  upsertConversation(conv);
7631
7656
  state.activeId = conv.id;
7632
- persistConversations();
7657
+ persistActiveConversationSelection(conv.id);
7633
7658
  renderRail();
7634
7659
  renderActive();
7635
7660
  startPolling();