@unson/brainbase-mcp 0.22.3 → 0.22.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unson/brainbase-mcp",
3
- "version": "0.22.3",
3
+ "version": "0.22.4",
4
4
  "description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -407,5 +407,5 @@
407
407
  "engines": {
408
408
  "node": ">=20"
409
409
  },
410
- "gitHead": "8e7c9b80910f862f297788c35096461e3359d465"
410
+ "gitHead": "8c472153bac9ae0d439f8275a2c640f46ae03699"
411
411
  }
@@ -298,6 +298,9 @@ export function createGraphProjectsView({
298
298
  let contextRequest = 0;
299
299
  let detailRequest = 0;
300
300
  let hostListRequest = 0;
301
+ // An explicitly selected project may be read in parallel with the host
302
+ // list, but the list remains authoritative for whether its detail is shown.
303
+ let initialSelectionGate = null;
301
304
  let viewDestroyed = false;
302
305
 
303
306
  const correction = createGraphCorrection({
@@ -415,7 +418,7 @@ export function createGraphProjectsView({
415
418
  }
416
419
 
417
420
  /** Reads supplementary sections independently of the Graph project detail. */
418
- async function loadProjectContextFor(id, { keep = false } = {}) {
421
+ async function loadProjectContextFor(id, { keep = false, deferUntilHostList = null } = {}) {
419
422
  if (viewDestroyed) return state.context;
420
423
  if (typeof loadProjectContext !== 'function') {
421
424
  if (state.context?.projectId !== id) state.context = null;
@@ -425,9 +428,16 @@ export function createGraphProjectsView({
425
428
  const request = ++contextRequest;
426
429
  state.context = normalizeProjectContext(id, { state: 'loading' });
427
430
  controller.render();
428
- const applyContext = (snapshot) => {
431
+ const applyContext = (snapshot, { flushDeferred = false } = {}) => {
429
432
  const normalized = normalizeProjectContext(id, snapshot);
430
433
  if (viewDestroyed || request !== contextRequest || state.selectedId !== id) return false;
434
+ if (deferUntilHostList && !deferUntilHostList.confirmed && !flushDeferred) {
435
+ if (!deferUntilHostList.cancelled && initialSelectionGate === deferUntilHostList) {
436
+ deferUntilHostList.pendingContext = normalized;
437
+ deferUntilHostList.flushContext = () => applyContext(normalized, { flushDeferred: true });
438
+ }
439
+ return false;
440
+ }
431
441
  state.context = normalized;
432
442
  // Context progress must not rebuild the workspace: rebuilding destroys
433
443
  // Sigma's viewport and resets the user's active tab/selection. Detail
@@ -796,6 +806,37 @@ export function createGraphProjectsView({
796
806
  return children;
797
807
  }
798
808
 
809
+ function cancelInitialSelectionGate(gate, { reset = true } = {}) {
810
+ if (!gate || gate.cancelled || gate.confirmed) return;
811
+ gate.cancelled = true;
812
+ gate.pendingContext = null;
813
+ gate.pendingDetail = null;
814
+ gate.flushContext = null;
815
+ gate.flushDetail = null;
816
+ if (initialSelectionGate === gate) initialSelectionGate = null;
817
+ // A caller may have selected another project while this gate was pending.
818
+ // Do not invalidate that newer project's reads in that case.
819
+ if (!reset || state.selectedId !== gate.id) return;
820
+ detailRequest += 1;
821
+ contextRequest += 1;
822
+ if (state.detail?.id === gate.id && state.detail.state === 'loading') state.detail = null;
823
+ if (state.context?.projectId === gate.id && state.context.state === 'loading') state.context = null;
824
+ }
825
+
826
+ function confirmInitialSelectionGate(gate) {
827
+ if (!gate || gate.cancelled) return;
828
+ gate.confirmed = true;
829
+ if (initialSelectionGate === gate) initialSelectionGate = null;
830
+ // Responses which finished before the list are safe to expose now. A
831
+ // response still in flight will apply through its normal completion path.
832
+ gate.flushDetail?.();
833
+ gate.flushContext?.();
834
+ gate.pendingDetail = null;
835
+ gate.pendingContext = null;
836
+ gate.flushDetail = null;
837
+ gate.flushContext = null;
838
+ }
839
+
799
840
  /** Reads the host's project list (see `load`). */
800
841
  async function loadHostList({ keep = false } = {}) {
801
842
  const request = ++hostListRequest;
@@ -804,14 +845,45 @@ export function createGraphProjectsView({
804
845
  state.list = { state: 'loading' };
805
846
  controller.render();
806
847
  }
848
+ let selectionGate = null;
849
+ let selectionDetailReading = null;
850
+ const selectedBeforeList = state.selectedId;
851
+ if (selectedBeforeList) {
852
+ if (initialSelectionGate?.id === selectedBeforeList && !initialSelectionGate.cancelled && !initialSelectionGate.confirmed) {
853
+ selectionGate = initialSelectionGate;
854
+ selectionDetailReading = selectionGate.detailReading;
855
+ } else if (state.detail === null) {
856
+ selectionGate = {
857
+ id: selectedBeforeList,
858
+ confirmed: false,
859
+ cancelled: false,
860
+ pendingContext: null,
861
+ pendingDetail: null,
862
+ flushContext: null,
863
+ flushDetail: null,
864
+ detailReading: null,
865
+ };
866
+ initialSelectionGate = selectionGate;
867
+ selectionDetailReading = controller.loadDetail(selectedBeforeList, {
868
+ keep,
869
+ deferUntilHostList: selectionGate,
870
+ });
871
+ selectionGate.detailReading = selectionDetailReading;
872
+ }
873
+ }
807
874
  const result = await client.read('/projects');
808
875
  if (viewDestroyed || request !== hostListRequest) return state.list;
809
876
  state.list = normalizeProjectList(result);
810
877
  if (state.list.state !== 'ok') {
878
+ cancelInitialSelectionGate(selectionGate);
811
879
  controller.render();
812
880
  return state.list;
813
881
  }
814
882
  const { projects } = state.list.payload;
883
+ if (selectionGate && (selectionGate.cancelled || state.selectedId !== selectionGate.id)) {
884
+ cancelInitialSelectionGate(selectionGate, { reset: false });
885
+ selectionGate = null;
886
+ }
815
887
  if (!state.selectedId) {
816
888
  if (correction.form) correction.close();
817
889
  state.panelAt = null;
@@ -832,6 +904,7 @@ export function createGraphProjectsView({
832
904
  // never be replaced with the first row when the refreshed list no
833
905
  // longer contains it. Keeping the id makes the missing target visible
834
906
  // and lets the host decide whether its identity or access changed.
907
+ cancelInitialSelectionGate(selectionGate);
835
908
  if (correction.form) correction.close();
836
909
  detailRequest += 1;
837
910
  state.panelAt = null;
@@ -842,6 +915,11 @@ export function createGraphProjectsView({
842
915
  };
843
916
  state.context = null;
844
917
  contextRequest += 1;
918
+ } else if (selectionGate && selectionGate.id === state.selectedId) {
919
+ confirmInitialSelectionGate(selectionGate);
920
+ if (selectionDetailReading) await selectionDetailReading;
921
+ if (viewDestroyed || request !== hostListRequest) return state.list;
922
+ return state.list;
845
923
  } else if (state.detail?.id !== state.selectedId || state.detail?.state === 'not_found') {
846
924
  // The project the host asked for is listed; read it for the rail.
847
925
  if (viewDestroyed || request !== hostListRequest) return state.list;
@@ -876,6 +954,7 @@ export function createGraphProjectsView({
876
954
  hostListRequest += 1;
877
955
  detailRequest += 1;
878
956
  contextRequest += 1;
957
+ initialSelectionGate = null;
879
958
  knowledgeWorkspace?.destroy();
880
959
  knowledgeWorkspace = null;
881
960
  correction.close();
@@ -925,7 +1004,7 @@ export function createGraphProjectsView({
925
1004
  return state.own.detail;
926
1005
  },
927
1006
  /** `keep` shows the current detail until the new one has been read. */
928
- async loadDetail(id, { keep = false } = {}) {
1007
+ async loadDetail(id, { keep = false, deferUntilHostList = null } = {}) {
929
1008
  if (viewDestroyed) return state.detail;
930
1009
  const request = ++detailRequest;
931
1010
  const previous = state.detail;
@@ -933,13 +1012,26 @@ export function createGraphProjectsView({
933
1012
  state.detail = { id, state: 'loading' };
934
1013
  controller.render();
935
1014
  }
936
- const contextReading = loadProjectContextFor(id, { keep });
1015
+ const contextReading = loadProjectContextFor(id, { keep, deferUntilHostList });
937
1016
  const result = await client.read(`/projects/${encodeURIComponent(id)}`);
938
1017
  if (viewDestroyed || request !== detailRequest || state.selectedId !== id) return state.detail;
939
- state.detail = result.state === 'error' && result.status === 404
1018
+ const detail = result.state === 'error' && result.status === 404
940
1019
  ? { id, state: 'not_found', reason: 'このプロジェクトは見つかりません。一覧を読み直してください。' }
941
1020
  : { id, ...normalizeProjectDetail(result) };
942
- controller.render();
1021
+ const applyDetail = () => {
1022
+ if (viewDestroyed || request !== detailRequest || state.selectedId !== id) return false;
1023
+ state.detail = detail;
1024
+ controller.render();
1025
+ return true;
1026
+ };
1027
+ if (deferUntilHostList && !deferUntilHostList.confirmed) {
1028
+ if (!deferUntilHostList.cancelled && initialSelectionGate === deferUntilHostList) {
1029
+ deferUntilHostList.pendingDetail = detail;
1030
+ deferUntilHostList.flushDetail = applyDetail;
1031
+ }
1032
+ return state.detail;
1033
+ }
1034
+ applyDetail();
943
1035
  // The context is intentionally independent. Its completion may trigger
944
1036
  // one more render, but never changes the Graph detail result.
945
1037
  void contextReading;
@@ -947,6 +1039,10 @@ export function createGraphProjectsView({
947
1039
  },
948
1040
  /** Selects a row: the rail shows that project, and an open correction is closed. */
949
1041
  async selectProject(id) {
1042
+ // A host selection supersedes a prefetched initial detail. Cancel the
1043
+ // gate before changing the id so A -> B -> A cannot make a later list
1044
+ // response wait for the first A read.
1045
+ cancelInitialSelectionGate(initialSelectionGate, { reset: false });
950
1046
  if (correction.form) correction.close();
951
1047
  state.panelAt = null;
952
1048
  state.selectedId = id;