@unson/brainbase-mcp 0.22.6 → 0.23.1
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/dist/judgment-frame-read.d.ts +34 -0
- package/dist/judgment-frame-read.js +223 -0
- package/dist/judgment-frame.d.ts +2 -0
- package/dist/judgment-frame.js +1 -0
- package/dist/knowledge-continuation.d.ts +2 -0
- package/dist/knowledge-continuation.js +63 -3
- package/package.json +2 -2
- package/ui/objective-editor.css +2 -0
- package/ui/objective-editor.js +1 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { type JudgmentFrameKind, type JudgmentFrameRecordsPage } from './judgment-frame.js';
|
|
3
|
+
type JudgmentFrameReadResult = {
|
|
4
|
+
readonly status: 'ok';
|
|
5
|
+
readonly data: {
|
|
6
|
+
readonly schema_version: 'brainbase-judgment-frame-read-v1';
|
|
7
|
+
readonly catalog_digest: `sha256:${string}`;
|
|
8
|
+
readonly records: readonly JudgmentFrameReadRecord[];
|
|
9
|
+
};
|
|
10
|
+
} | {
|
|
11
|
+
readonly status: 'unavailable' | 'error';
|
|
12
|
+
readonly error: {
|
|
13
|
+
readonly code: string;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
readonly details?: unknown;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export type JudgmentFrameReadRecord = {
|
|
19
|
+
readonly kind: JudgmentFrameKind;
|
|
20
|
+
readonly ref: string;
|
|
21
|
+
readonly body: string;
|
|
22
|
+
readonly body_digest: `sha256:${string}`;
|
|
23
|
+
readonly version: string;
|
|
24
|
+
readonly version_source: 'graph' | 'body_digest';
|
|
25
|
+
readonly applicability_scope: string;
|
|
26
|
+
readonly applicability_scope_source: 'graph' | 'unspecified';
|
|
27
|
+
};
|
|
28
|
+
export type JudgmentFrameReadDependencies = {
|
|
29
|
+
/** Read every record of one kind visible to the caller, at most `limit`. */
|
|
30
|
+
readonly loadRecords: (kind: JudgmentFrameKind, limit: number) => Promise<JudgmentFrameRecordsPage>;
|
|
31
|
+
};
|
|
32
|
+
export declare const judgmentFrameReadTools: Tool[];
|
|
33
|
+
export declare function handleJudgmentFrameReadToolCall(name: string, args: Record<string, unknown>, dependencies: JudgmentFrameReadDependencies): Promise<JudgmentFrameReadResult | null>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { buildJudgmentFrameCatalog, } from './judgment-frame.js';
|
|
3
|
+
const KINDS = ['philosophy', 'objective', 'model'];
|
|
4
|
+
const FRAME_PAGE_LIMIT = 500;
|
|
5
|
+
const MAX_SELECTIONS = 24;
|
|
6
|
+
const DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/u;
|
|
7
|
+
const MAX_REF_LENGTH = 1_000;
|
|
8
|
+
const UNSPECIFIED_APPLICABILITY_SCOPE = 'Graphに適用範囲の記載なし';
|
|
9
|
+
const isRecord = (value) => (Boolean(value) && typeof value === 'object' && !Array.isArray(value));
|
|
10
|
+
const text = (value) => (typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined);
|
|
11
|
+
const versionText = (value) => (text(value) ?? (typeof value === 'number' && Number.isFinite(value) ? String(value) : undefined));
|
|
12
|
+
function canonicalJson(value) {
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
15
|
+
if (isRecord(value)) {
|
|
16
|
+
return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(',')}}`;
|
|
17
|
+
}
|
|
18
|
+
const serialized = JSON.stringify(value);
|
|
19
|
+
return serialized === undefined ? 'null' : serialized;
|
|
20
|
+
}
|
|
21
|
+
function scopeText(value) {
|
|
22
|
+
const stringValue = text(value);
|
|
23
|
+
if (stringValue)
|
|
24
|
+
return stringValue;
|
|
25
|
+
if (!isRecord(value) && !Array.isArray(value))
|
|
26
|
+
return undefined;
|
|
27
|
+
const serialized = canonicalJson(value);
|
|
28
|
+
return serialized && serialized !== '{}' && serialized !== '[]' ? serialized : undefined;
|
|
29
|
+
}
|
|
30
|
+
function itemBody(kind, payload) {
|
|
31
|
+
const foundation = isRecord(payload.foundation) ? payload.foundation : {};
|
|
32
|
+
if (kind === 'philosophy') {
|
|
33
|
+
const statement = text(payload.statement) ?? text(foundation.meaning);
|
|
34
|
+
const name = text(payload.display_name) ?? text(payload.title);
|
|
35
|
+
if (!statement)
|
|
36
|
+
return undefined;
|
|
37
|
+
return name && name !== statement ? `${name}:${statement}` : statement;
|
|
38
|
+
}
|
|
39
|
+
if (kind === 'objective') {
|
|
40
|
+
return text(foundation.desiredState) ?? text(foundation.meaning) ?? text(payload.statement);
|
|
41
|
+
}
|
|
42
|
+
// A model title is a label, not a readable world-model body. Require a
|
|
43
|
+
// meaning/statement so a name-only catalog item cannot become evidence.
|
|
44
|
+
return text(foundation.meaning) ?? text(payload.statement);
|
|
45
|
+
}
|
|
46
|
+
function recordScope(kind, record) {
|
|
47
|
+
const payload = isRecord(record.payload) ? record.payload : {};
|
|
48
|
+
const foundation = isRecord(payload.foundation) ? payload.foundation : {};
|
|
49
|
+
const recordMetadata = record;
|
|
50
|
+
const candidates = kind === 'model'
|
|
51
|
+
? [
|
|
52
|
+
payload.applicability_text,
|
|
53
|
+
payload.applicability_scope,
|
|
54
|
+
foundation.applicability_text,
|
|
55
|
+
foundation.applicability_scope,
|
|
56
|
+
payload.applicability,
|
|
57
|
+
foundation.applicability,
|
|
58
|
+
]
|
|
59
|
+
: [
|
|
60
|
+
payload.applicability_scope,
|
|
61
|
+
payload.applicability_text,
|
|
62
|
+
foundation.applicability_scope,
|
|
63
|
+
foundation.applicability_text,
|
|
64
|
+
payload.applicability,
|
|
65
|
+
foundation.applicability,
|
|
66
|
+
];
|
|
67
|
+
candidates.push(recordMetadata.applicability_scope, recordMetadata.applicability_text);
|
|
68
|
+
for (const candidate of candidates) {
|
|
69
|
+
const value = scopeText(candidate);
|
|
70
|
+
if (value)
|
|
71
|
+
return { value, source: 'graph' };
|
|
72
|
+
}
|
|
73
|
+
return { value: UNSPECIFIED_APPLICABILITY_SCOPE, source: 'unspecified' };
|
|
74
|
+
}
|
|
75
|
+
function invalidInput() {
|
|
76
|
+
return {
|
|
77
|
+
status: 'error',
|
|
78
|
+
error: {
|
|
79
|
+
code: 'judgment_frame_read_input_invalid',
|
|
80
|
+
message: 'Provide one current catalog digest and one or more unique philosophy, objective, or model references.',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function toolError(status, code, message, details) {
|
|
85
|
+
return { status, error: { code, message, ...(details === undefined ? {} : { details }) } };
|
|
86
|
+
}
|
|
87
|
+
function isSelection(value) {
|
|
88
|
+
if (!isRecord(value) || Object.keys(value).length !== 2
|
|
89
|
+
|| !Object.hasOwn(value, 'kind') || !Object.hasOwn(value, 'ref'))
|
|
90
|
+
return false;
|
|
91
|
+
return KINDS.includes(value.kind)
|
|
92
|
+
&& typeof value.ref === 'string'
|
|
93
|
+
&& value.ref.trim() === value.ref
|
|
94
|
+
&& value.ref.length > 0
|
|
95
|
+
&& value.ref.length <= MAX_REF_LENGTH;
|
|
96
|
+
}
|
|
97
|
+
function parseInput(args) {
|
|
98
|
+
if (!isRecord(args)
|
|
99
|
+
|| Object.keys(args).length !== 2
|
|
100
|
+
|| !Object.hasOwn(args, 'catalog_digest')
|
|
101
|
+
|| !Object.hasOwn(args, 'selections')
|
|
102
|
+
|| typeof args.catalog_digest !== 'string'
|
|
103
|
+
|| !DIGEST_PATTERN.test(args.catalog_digest)
|
|
104
|
+
|| !Array.isArray(args.selections)
|
|
105
|
+
|| args.selections.length === 0
|
|
106
|
+
|| args.selections.length > MAX_SELECTIONS
|
|
107
|
+
|| !args.selections.every(isSelection))
|
|
108
|
+
return null;
|
|
109
|
+
const selections = args.selections;
|
|
110
|
+
const keys = selections.map((selection) => `${selection.kind}:${selection.ref}`);
|
|
111
|
+
if (new Set(keys).size !== keys.length)
|
|
112
|
+
return null;
|
|
113
|
+
return { catalogDigest: args.catalog_digest, selections };
|
|
114
|
+
}
|
|
115
|
+
async function loadFrameCatalog(dependencies) {
|
|
116
|
+
const records = [];
|
|
117
|
+
for (const kind of KINDS) {
|
|
118
|
+
let page;
|
|
119
|
+
try {
|
|
120
|
+
page = await dependencies.loadRecords(kind, FRAME_PAGE_LIMIT);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
return toolError('unavailable', 'judgment_frame_catalog_unavailable', `Graph ${kind} records could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
124
|
+
}
|
|
125
|
+
if (page.status !== 'ok')
|
|
126
|
+
return toolError(page.status, page.code, page.message);
|
|
127
|
+
if (page.records.length >= FRAME_PAGE_LIMIT) {
|
|
128
|
+
return toolError('error', 'judgment_frame_catalog_truncated', `Graph returned ${page.records.length} ${kind} records; the catalog may be incomplete`);
|
|
129
|
+
}
|
|
130
|
+
records.push(...page.records);
|
|
131
|
+
}
|
|
132
|
+
return { ...buildJudgmentFrameCatalog(records), records };
|
|
133
|
+
}
|
|
134
|
+
export const judgmentFrameReadTools = [{
|
|
135
|
+
name: 'brainbase_judgment_frame_read',
|
|
136
|
+
description: 'On every confirmed turn, after reading the judgment frame catalog, retrieve the full canonical Graph body, version, and applicability scope for every selected philosophy, objective, and world model reference. Pass the exact catalog_digest and selected kind/ref pairs. The read is authorized against the caller-visible Graph and fails on a stale catalog, unknown reference, or empty body. If Graph metadata has no applicability scope, the result says so explicitly; authorization scope is never presented as applicability scope.',
|
|
137
|
+
inputSchema: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
additionalProperties: false,
|
|
140
|
+
properties: {
|
|
141
|
+
catalog_digest: { type: 'string', pattern: '^sha256:[a-f0-9]{64}$' },
|
|
142
|
+
selections: {
|
|
143
|
+
type: 'array',
|
|
144
|
+
minItems: 1,
|
|
145
|
+
maxItems: MAX_SELECTIONS,
|
|
146
|
+
items: {
|
|
147
|
+
type: 'object',
|
|
148
|
+
additionalProperties: false,
|
|
149
|
+
properties: {
|
|
150
|
+
kind: { type: 'string', enum: [...KINDS] },
|
|
151
|
+
ref: { type: 'string', minLength: 1, maxLength: MAX_REF_LENGTH },
|
|
152
|
+
},
|
|
153
|
+
required: ['kind', 'ref'],
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
required: ['catalog_digest', 'selections'],
|
|
158
|
+
},
|
|
159
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
160
|
+
}];
|
|
161
|
+
export async function handleJudgmentFrameReadToolCall(name, args, dependencies) {
|
|
162
|
+
if (name !== 'brainbase_judgment_frame_read')
|
|
163
|
+
return null;
|
|
164
|
+
const input = parseInput(args);
|
|
165
|
+
if (!input)
|
|
166
|
+
return invalidInput();
|
|
167
|
+
const loaded = await loadFrameCatalog(dependencies);
|
|
168
|
+
if ('status' in loaded)
|
|
169
|
+
return loaded;
|
|
170
|
+
const { catalog, records: sourceRecords } = loaded;
|
|
171
|
+
if (input.catalogDigest !== catalog.digest) {
|
|
172
|
+
return toolError('error', 'judgment_frame_catalog_mismatch', 'The supplied catalog_digest is stale or was produced from a different visible Graph.', { current_catalog_digest: catalog.digest });
|
|
173
|
+
}
|
|
174
|
+
const records = [];
|
|
175
|
+
const sourceByKey = new Map();
|
|
176
|
+
for (const record of sourceRecords) {
|
|
177
|
+
const key = `${record.entity_type}:${record.id}`;
|
|
178
|
+
if (!sourceByKey.has(key))
|
|
179
|
+
sourceByKey.set(key, record);
|
|
180
|
+
}
|
|
181
|
+
for (const selection of input.selections) {
|
|
182
|
+
const item = catalog.items.find((candidate) => candidate.kind === selection.kind && candidate.id === selection.ref);
|
|
183
|
+
if (!item) {
|
|
184
|
+
return toolError('error', 'judgment_frame_ref_unknown', `${selection.kind}/${selection.ref} is not in the current judgment frame catalog`);
|
|
185
|
+
}
|
|
186
|
+
const source = sourceByKey.get(`${selection.kind}:${selection.ref}`);
|
|
187
|
+
if (!source) {
|
|
188
|
+
return toolError('error', 'judgment_frame_body_unavailable', `${selection.kind}/${selection.ref} is no longer available in the authorized Graph`);
|
|
189
|
+
}
|
|
190
|
+
const payload = isRecord(source.payload) ? source.payload : {};
|
|
191
|
+
const semanticBody = itemBody(selection.kind, payload);
|
|
192
|
+
if (!semanticBody)
|
|
193
|
+
return toolError('error', 'judgment_frame_body_unavailable', `${selection.kind}/${selection.ref} has no readable body`);
|
|
194
|
+
// The catalog intentionally clips a compact summary. Return the full
|
|
195
|
+
// canonical payload so model predictions, constraints, and measurements
|
|
196
|
+
// are not silently discarded by the read step.
|
|
197
|
+
const body = canonicalJson(payload);
|
|
198
|
+
const bodyDigest = `sha256:${createHash('sha256').update(body, 'utf8').digest('hex')}`;
|
|
199
|
+
const foundation = isRecord(payload.foundation) ? payload.foundation : {};
|
|
200
|
+
const graphVersion = versionText(foundation.revision) ?? versionText(source.version);
|
|
201
|
+
const version = graphVersion ?? bodyDigest;
|
|
202
|
+
const versionSource = graphVersion ? 'graph' : 'body_digest';
|
|
203
|
+
const applicabilityScope = recordScope(selection.kind, source);
|
|
204
|
+
records.push({
|
|
205
|
+
kind: selection.kind,
|
|
206
|
+
ref: selection.ref,
|
|
207
|
+
body,
|
|
208
|
+
body_digest: bodyDigest,
|
|
209
|
+
version,
|
|
210
|
+
version_source: versionSource,
|
|
211
|
+
applicability_scope: applicabilityScope.value,
|
|
212
|
+
applicability_scope_source: applicabilityScope.source,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
status: 'ok',
|
|
217
|
+
data: {
|
|
218
|
+
schema_version: 'brainbase-judgment-frame-read-v1',
|
|
219
|
+
catalog_digest: catalog.digest,
|
|
220
|
+
records,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
}
|
package/dist/judgment-frame.d.ts
CHANGED
|
@@ -125,3 +125,5 @@ export type JudgmentFrameToolResult = {
|
|
|
125
125
|
};
|
|
126
126
|
};
|
|
127
127
|
export declare function handleJudgmentFrameToolCall(name: string, args: Record<string, unknown>, dependencies: JudgmentFrameToolDependencies): Promise<JudgmentFrameToolResult | null>;
|
|
128
|
+
export { judgmentFrameReadTools, handleJudgmentFrameReadToolCall, } from './judgment-frame-read.js';
|
|
129
|
+
export type { JudgmentFrameReadDependencies, JudgmentFrameReadRecord, } from './judgment-frame-read.js';
|
package/dist/judgment-frame.js
CHANGED
|
@@ -97,6 +97,8 @@ export interface KnowledgeLookupState {
|
|
|
97
97
|
revision: number;
|
|
98
98
|
status: KnowledgeStatus;
|
|
99
99
|
started_at: number;
|
|
100
|
+
/** Set when the first validated retrieval action is reserved. Old states may omit it. */
|
|
101
|
+
retrieval_started_at?: number | null;
|
|
100
102
|
limits: KnowledgeLookupLimits;
|
|
101
103
|
attempts: KnowledgeAttempt[];
|
|
102
104
|
required_fields: string[] | null;
|
|
@@ -20,6 +20,7 @@ export function createKnowledgeLookup({ lookupId, question, now = Date.now(), li
|
|
|
20
20
|
revision: 0,
|
|
21
21
|
status: 'resolving',
|
|
22
22
|
started_at: now,
|
|
23
|
+
retrieval_started_at: null,
|
|
23
24
|
limits: {
|
|
24
25
|
attempts: bounded(limits.attempts, 8, 16),
|
|
25
26
|
replans: bounded(limits.replans, 4, 8),
|
|
@@ -42,8 +43,51 @@ function terminate(state, reason, status = 'unresolved') {
|
|
|
42
43
|
state.pending = null;
|
|
43
44
|
return state;
|
|
44
45
|
}
|
|
46
|
+
const hasPendingRetrieval = (state) => state.pending !== null && state.pending.kind !== 'finish';
|
|
47
|
+
const retrievalStartedAt = (state) => {
|
|
48
|
+
if (typeof state.retrieval_started_at === 'number' && Number.isFinite(state.retrieval_started_at)) {
|
|
49
|
+
return state.retrieval_started_at;
|
|
50
|
+
}
|
|
51
|
+
// States written before retrieval_started_at was introduced can still be
|
|
52
|
+
// bounded safely once they have evidence of a retrieval reservation.
|
|
53
|
+
if (state.attempts.length > 0 || hasPendingRetrieval(state))
|
|
54
|
+
return state.started_at;
|
|
55
|
+
return null;
|
|
56
|
+
};
|
|
57
|
+
const retrievalPhase = (state) => {
|
|
58
|
+
if (hasPendingRetrieval(state))
|
|
59
|
+
return 'in_flight';
|
|
60
|
+
if (state.attempts.length > 0)
|
|
61
|
+
return 'attempted';
|
|
62
|
+
return 'not_started';
|
|
63
|
+
};
|
|
64
|
+
function recordTimedOutPendingRetrieval(state) {
|
|
65
|
+
const pending = state.pending;
|
|
66
|
+
if (!pending)
|
|
67
|
+
return;
|
|
68
|
+
state.pending = null;
|
|
69
|
+
if (pending.kind === 'finish')
|
|
70
|
+
return;
|
|
71
|
+
if (state.attempts.some((attempt) => attempt.attempt_id === pending.tool_use_id))
|
|
72
|
+
return;
|
|
73
|
+
// The host cannot tell whether a timed-out reservation reached the tool.
|
|
74
|
+
// Keep the reservation as an unknown transport attempt rather than treating
|
|
75
|
+
// a late or missing result as a successful read.
|
|
76
|
+
state.attempts.push({
|
|
77
|
+
attempt_id: pending.tool_use_id,
|
|
78
|
+
kind: pending.kind,
|
|
79
|
+
fingerprint: pending.fingerprint,
|
|
80
|
+
outcome: 'transport_error',
|
|
81
|
+
references: [],
|
|
82
|
+
searched_scope: {},
|
|
83
|
+
missing_fields: state.required_fields ?? []
|
|
84
|
+
});
|
|
85
|
+
state.revision += 1;
|
|
86
|
+
}
|
|
45
87
|
function expire(state, now) {
|
|
46
|
-
|
|
88
|
+
const startedAt = retrievalStartedAt(state);
|
|
89
|
+
if (startedAt !== null && !isKnowledgeLookupTerminal(state) && now - startedAt >= state.limits.milliseconds) {
|
|
90
|
+
recordTimedOutPendingRetrieval(state);
|
|
47
91
|
terminate(state, 'time_budget_exhausted');
|
|
48
92
|
}
|
|
49
93
|
return state;
|
|
@@ -196,8 +240,10 @@ function validFinish(state, action) {
|
|
|
196
240
|
}
|
|
197
241
|
export function prepareKnowledgeAction(previous, input, toolUseId, now = Date.now()) {
|
|
198
242
|
const state = expire(structuredClone(previous), now);
|
|
199
|
-
if (isKnowledgeLookupTerminal(state))
|
|
243
|
+
if (isKnowledgeLookupTerminal(state)) {
|
|
244
|
+
recordTimedOutPendingRetrieval(state);
|
|
200
245
|
return { state, allowed: false, reason: state.termination_reason ?? state.status };
|
|
246
|
+
}
|
|
201
247
|
if (state.pending)
|
|
202
248
|
return { state, allowed: false, reason: 'attempt_in_flight' };
|
|
203
249
|
if (!record(input))
|
|
@@ -312,6 +358,9 @@ export function prepareKnowledgeAction(previous, input, toolUseId, now = Date.no
|
|
|
312
358
|
}
|
|
313
359
|
state.replans += 1;
|
|
314
360
|
}
|
|
361
|
+
if (state.retrieval_started_at == null && state.attempts.length === 0 && !hasPendingRetrieval(state)) {
|
|
362
|
+
state.retrieval_started_at = now;
|
|
363
|
+
}
|
|
315
364
|
state.pending = {
|
|
316
365
|
tool_use_id: toolUseId,
|
|
317
366
|
kind: action.kind,
|
|
@@ -323,6 +372,14 @@ export function prepareKnowledgeAction(previous, input, toolUseId, now = Date.no
|
|
|
323
372
|
}
|
|
324
373
|
export function recordKnowledgeResult(previous, input, result, toolUseId, now = Date.now()) {
|
|
325
374
|
const state = structuredClone(previous);
|
|
375
|
+
if (isKnowledgeLookupTerminal(state)) {
|
|
376
|
+
// A late PostToolUse must never reopen a finished lookup. Preserve a stale
|
|
377
|
+
// retrieval reservation as unknown while keeping the terminal state.
|
|
378
|
+
recordTimedOutPendingRetrieval(state);
|
|
379
|
+
return state;
|
|
380
|
+
}
|
|
381
|
+
if (isKnowledgeLookupTerminal(expire(state, now)))
|
|
382
|
+
return state;
|
|
326
383
|
if (typeof toolUseId === 'string' && state.finished_calls.includes(toolUseId))
|
|
327
384
|
return state;
|
|
328
385
|
if (!state.pending ||
|
|
@@ -411,8 +468,10 @@ export function recordKnowledgeResult(previous, input, result, toolUseId, now =
|
|
|
411
468
|
}
|
|
412
469
|
export function stopKnowledgeLookup(previous, now = Date.now()) {
|
|
413
470
|
const state = expire(structuredClone(previous), now);
|
|
414
|
-
if (isKnowledgeLookupTerminal(state))
|
|
471
|
+
if (isKnowledgeLookupTerminal(state)) {
|
|
472
|
+
recordTimedOutPendingRetrieval(state);
|
|
415
473
|
return { state, block: false, reason: knowledgeLookupContext(state) };
|
|
474
|
+
}
|
|
416
475
|
state.stop_requests += 1;
|
|
417
476
|
if (state.stop_requests > state.limits.replans) {
|
|
418
477
|
terminate(state, 'model_did_not_produce_executable_plan');
|
|
@@ -444,6 +503,7 @@ export function knowledgeLookupContext(state) {
|
|
|
444
503
|
revision: state.revision,
|
|
445
504
|
question: state.question,
|
|
446
505
|
status: state.status,
|
|
506
|
+
retrieval_phase: retrievalPhase(state),
|
|
447
507
|
required_fields: state.required_fields,
|
|
448
508
|
attempts: state.attempts,
|
|
449
509
|
remaining_attempts: Math.max(0, state.limits.attempts - state.attempts.length),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.23.1",
|
|
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": "
|
|
410
|
+
"gitHead": "da34b87b5890ae3bc4c89bffe6faa54a302e3276"
|
|
411
411
|
}
|
package/ui/objective-editor.css
CHANGED
|
@@ -243,6 +243,8 @@ textarea.objective-editor-input { min-height: 5.4rem; resize: vertical; }
|
|
|
243
243
|
.bb-objective-rail { display: block; }
|
|
244
244
|
.bb-objective-rail > .bb-ws-notice { margin: var(--bb-space-4) 0 0; }
|
|
245
245
|
.bb-objective-rail > .bb-ws-actions { padding-top: 22px; }
|
|
246
|
+
.bb-objective-rail > .bb-objective-revision-note { margin: var(--bb-space-4) 0 0; }
|
|
247
|
+
.bb-objective-rail > .bb-objective-revision-note + .bb-ws-actions { padding-top: var(--bb-space-3); }
|
|
246
248
|
.bb-objective-rail .bb-ws-rail-block > p + p,
|
|
247
249
|
.bb-objective-rail .bb-objective-note { margin-top: var(--bb-space-2); }
|
|
248
250
|
.bb-objective-rail .bb-objective-note { color: var(--bb-color-muted); font-size: 12px; }
|
package/ui/objective-editor.js
CHANGED
|
@@ -1121,6 +1121,7 @@ function renderObjectiveDetail(doc, state, callbacks) {
|
|
|
1121
1121
|
parts.push(workspaceRailBlock(doc, { title: '制約の参照', className: 'bb-objective-constraints-block', content: constraintContent(doc, state, callbacks) }));
|
|
1122
1122
|
parts.push(workspaceRailBlock(doc, { title: '判断に使えるか', className: 'bb-objective-readiness-block', content: readinessContent(doc, state.readiness) }));
|
|
1123
1123
|
if (callbacks.canEdit) {
|
|
1124
|
+
parts.push(makeElement('p', { className: 'objective-editor-muted bb-objective-revision-note', text: '直して保存すると同じ目的の新しい版を作ります。保存結果を読み戻して確認し、確認できない場合はその状態を表示します。' }));
|
|
1124
1125
|
parts.push(workspaceActions(doc, [{ text: '目的を直す', variant: 'primary', onClick: callbacks.onEdit }]));
|
|
1125
1126
|
}
|
|
1126
1127
|
return parts;
|