monomind 2.3.3 → 2.3.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/README.md +37 -4
- package/package.json +1 -1
- package/packages/@monomind/cli/.claude/commands/mastermind/{approve.md → approvev1.md} +12 -9
- package/packages/@monomind/cli/.claude/commands/mastermind/help.md +18 -0
- package/packages/@monomind/cli/.claude/commands/mastermind/master.md +3 -3
- package/packages/@monomind/cli/.claude/commands/mastermind/runorg.md +19 -107
- package/packages/@monomind/cli/.claude/commands/mastermind/runorgv1.md +159 -0
- package/packages/@monomind/cli/.claude/helpers/handlers/route-handler.cjs +45 -2
- package/packages/@monomind/cli/.claude/helpers/handlers/session-restore-handler.cjs +25 -18
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/agents.md +5 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/{approve.md → approvev1.md} +8 -5
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/env.md +7 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/{heartbeat.md → heartbeatv1.md} +7 -4
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/inbox.md +14 -2
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/instance.md +3 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/org-settings.md +4 -1
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgs.md +14 -13
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/orgstatus.md +19 -8
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/projects.md +3 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/runorg.md +37 -725
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/runorgv1.md +731 -0
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/stoporg.md +10 -3
- package/packages/@monomind/cli/.claude/skills/mastermind-skills/tasks.md +4 -1
- package/packages/@monomind/cli/README.md +37 -4
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +30 -8
- package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +23 -10
- package/packages/@monomind/cli/dist/src/commands/org-observe.js +30 -26
- package/packages/@monomind/cli/dist/src/commands/org.js +52 -2
- package/packages/@monomind/cli/dist/src/knowledge/document-pipeline.js +0 -0
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +31 -12
- package/packages/@monomind/cli/dist/src/orgrt/daemon.js +45 -9
- package/packages/@monomind/cli/dist/src/orgrt/mailbox.d.ts +23 -0
- package/packages/@monomind/cli/dist/src/orgrt/mailbox.js +33 -1
- package/packages/@monomind/cli/dist/src/orgrt/migrate.d.ts +26 -0
- package/packages/@monomind/cli/dist/src/orgrt/migrate.js +111 -0
- package/packages/@monomind/cli/dist/src/orgrt/reporting.js +8 -1
- package/packages/@monomind/cli/dist/src/orgrt/session.js +8 -2
- package/packages/@monomind/cli/dist/src/ui/dashboard.html +44 -0
- package/packages/@monomind/cli/dist/src/ui/orgs.html +2 -2
- package/packages/@monomind/cli/dist/src/ui/server.mjs +12 -1
- package/packages/@monomind/cli/package.json +2 -2
|
@@ -17,9 +17,32 @@ export declare class Mailbox {
|
|
|
17
17
|
private queue;
|
|
18
18
|
private wake;
|
|
19
19
|
private closed;
|
|
20
|
+
/** Bumped when a new stream() starts; stale generators see the mismatch and exit. */
|
|
21
|
+
private generation;
|
|
20
22
|
push(text: string): void;
|
|
21
23
|
close(): void;
|
|
22
24
|
get isClosed(): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Detach the current waker WITHOUT resolving it. Called between sessions
|
|
27
|
+
* (maxTurns restart, crash backoff): the dead session's generator may still
|
|
28
|
+
* be parked on `wake` inside an abandoned next() — if a push() during that
|
|
29
|
+
* window resolved it, the stale generator would shift() the message and
|
|
30
|
+
* yield it into a promise nobody reads (silent loss, after deliver()
|
|
31
|
+
* already returned a "delivered" receipt). With the waker dropped, such a
|
|
32
|
+
* push only queues; the replacement session's stream() drains it. The
|
|
33
|
+
* parked stale generator is never resumed and becomes garbage with its
|
|
34
|
+
* session.
|
|
35
|
+
*/
|
|
36
|
+
detach(): void;
|
|
37
|
+
/**
|
|
38
|
+
* One live generator at a time: each stream() call bumps `generation`, and
|
|
39
|
+
* a stale generator that ever resumes exits immediately without touching
|
|
40
|
+
* the queue. Values are shift()ed at yield time — once the consumer's
|
|
41
|
+
* next() resolves with a message it counts as delivered (matching SDK
|
|
42
|
+
* behavior: a session may consume a message and end without ever resuming
|
|
43
|
+
* the generator; redelivering would duplicate work and can livelock the
|
|
44
|
+
* restart loop).
|
|
45
|
+
*/
|
|
23
46
|
stream(sessionId?: string): AsyncGenerator<OrgUserMessage>;
|
|
24
47
|
}
|
|
25
48
|
//# sourceMappingURL=mailbox.d.ts.map
|
|
@@ -7,6 +7,8 @@ export class Mailbox {
|
|
|
7
7
|
queue = [];
|
|
8
8
|
wake = null;
|
|
9
9
|
closed = false;
|
|
10
|
+
/** Bumped when a new stream() starts; stale generators see the mismatch and exit. */
|
|
11
|
+
generation = 0;
|
|
10
12
|
push(text) {
|
|
11
13
|
if (this.closed)
|
|
12
14
|
return;
|
|
@@ -20,19 +22,49 @@ export class Mailbox {
|
|
|
20
22
|
this.wake = null;
|
|
21
23
|
}
|
|
22
24
|
get isClosed() { return this.closed; }
|
|
25
|
+
/**
|
|
26
|
+
* Detach the current waker WITHOUT resolving it. Called between sessions
|
|
27
|
+
* (maxTurns restart, crash backoff): the dead session's generator may still
|
|
28
|
+
* be parked on `wake` inside an abandoned next() — if a push() during that
|
|
29
|
+
* window resolved it, the stale generator would shift() the message and
|
|
30
|
+
* yield it into a promise nobody reads (silent loss, after deliver()
|
|
31
|
+
* already returned a "delivered" receipt). With the waker dropped, such a
|
|
32
|
+
* push only queues; the replacement session's stream() drains it. The
|
|
33
|
+
* parked stale generator is never resumed and becomes garbage with its
|
|
34
|
+
* session.
|
|
35
|
+
*/
|
|
36
|
+
detach() { this.wake = null; }
|
|
37
|
+
/**
|
|
38
|
+
* One live generator at a time: each stream() call bumps `generation`, and
|
|
39
|
+
* a stale generator that ever resumes exits immediately without touching
|
|
40
|
+
* the queue. Values are shift()ed at yield time — once the consumer's
|
|
41
|
+
* next() resolves with a message it counts as delivered (matching SDK
|
|
42
|
+
* behavior: a session may consume a message and end without ever resuming
|
|
43
|
+
* the generator; redelivering would duplicate work and can livelock the
|
|
44
|
+
* restart loop).
|
|
45
|
+
*/
|
|
23
46
|
async *stream(sessionId = '') {
|
|
47
|
+
const gen = ++this.generation;
|
|
48
|
+
// Drop (never resolve) any stale waker — see detach().
|
|
49
|
+
this.wake = null;
|
|
24
50
|
while (true) {
|
|
25
51
|
while (this.queue.length > 0) {
|
|
52
|
+
if (gen !== this.generation)
|
|
53
|
+
return; // superseded — leave the queue for the live generator
|
|
26
54
|
yield {
|
|
27
55
|
type: 'user',
|
|
28
56
|
message: { role: 'user', content: this.queue.shift() },
|
|
29
57
|
parent_tool_use_id: null,
|
|
30
58
|
session_id: sessionId,
|
|
31
59
|
};
|
|
60
|
+
if (gen !== this.generation)
|
|
61
|
+
return;
|
|
32
62
|
}
|
|
33
|
-
if (this.closed)
|
|
63
|
+
if (this.closed || gen !== this.generation)
|
|
34
64
|
return;
|
|
35
65
|
await new Promise(r => { this.wake = r; });
|
|
66
|
+
if (gen !== this.generation)
|
|
67
|
+
return;
|
|
36
68
|
}
|
|
37
69
|
}
|
|
38
70
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type OrgDef } from './types.js';
|
|
2
|
+
/** The structural invariants the runtime assumes but the Zod schema can't
|
|
3
|
+
* express: unique role ids, exactly one root (reports_to: null), every
|
|
4
|
+
* non-root's reports_to resolving to another role (and not itself), and a
|
|
5
|
+
* parseable schedule. Shared by `org validate` (validateAction) and
|
|
6
|
+
* `migrateOrgFile`, which refuses to write a migrated config that still
|
|
7
|
+
* fails these checks. */
|
|
8
|
+
export declare function checkOrgStructure(def: Pick<OrgDef, 'roles' | 'schedule'>): string[];
|
|
9
|
+
export declare function migrateOrgConfig(raw: Record<string, unknown>): {
|
|
10
|
+
def: Record<string, unknown>;
|
|
11
|
+
dropped: string[];
|
|
12
|
+
notes: string[];
|
|
13
|
+
};
|
|
14
|
+
/** Reads `cfgPath`, migrates it, and (unless already v2) backs up the
|
|
15
|
+
* original to `backupPath` and overwrites `cfgPath` with the migrated def.
|
|
16
|
+
* Throws on an unmigratable config (schema failure inside migrateOrgConfig,
|
|
17
|
+
* or a structural violation caught here) — caller decides how to report it.
|
|
18
|
+
* Refuses to write when the migrated def fails checkOrgStructure, so a v1
|
|
19
|
+
* config with e.g. all-null reports_to fails cleanly instead of silently
|
|
20
|
+
* writing a config the runtime can't start. */
|
|
21
|
+
export declare function migrateOrgFile(cfgPath: string, backupPath: string): {
|
|
22
|
+
status: 'migrated' | 'already-v2';
|
|
23
|
+
dropped: string[];
|
|
24
|
+
notes: string[];
|
|
25
|
+
};
|
|
26
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// packages/@monomind/cli/src/orgrt/migrate.ts
|
|
2
|
+
/** v1 → v2 org config conversion. Pure transform + validation, plus the file
|
|
3
|
+
* IO orchestration for the `org migrate` subcommand — kept out of org.ts to
|
|
4
|
+
* stay under the 500-line file cap; org.ts's migrateAction is a thin wrapper
|
|
5
|
+
* that only does the name-validation / isOrgRunning guard and delegates here. */
|
|
6
|
+
import { existsSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
|
|
7
|
+
import { OrgDefSchema } from './types.js';
|
|
8
|
+
import { parseSchedule } from './scheduler.js';
|
|
9
|
+
const V1_TOP_LEVEL_KEYS = [
|
|
10
|
+
'topology', 'consensus', 'strategy', 'maxAgents', 'communication',
|
|
11
|
+
'board_id', 'todo_col_id', 'doing_col_id', 'done_col_id', 'loop',
|
|
12
|
+
'version', 'channels', 'differentiation', 'outputDir', 'runBehavior', 'created',
|
|
13
|
+
];
|
|
14
|
+
const V1_ROLE_KEYS = ['agent_type', 'delegates_to', 'board_id'];
|
|
15
|
+
/** The structural invariants the runtime assumes but the Zod schema can't
|
|
16
|
+
* express: unique role ids, exactly one root (reports_to: null), every
|
|
17
|
+
* non-root's reports_to resolving to another role (and not itself), and a
|
|
18
|
+
* parseable schedule. Shared by `org validate` (validateAction) and
|
|
19
|
+
* `migrateOrgFile`, which refuses to write a migrated config that still
|
|
20
|
+
* fails these checks. */
|
|
21
|
+
export function checkOrgStructure(def) {
|
|
22
|
+
const errors = [];
|
|
23
|
+
const ids = def.roles.map(r => r.id);
|
|
24
|
+
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
|
|
25
|
+
if (dupes.length)
|
|
26
|
+
errors.push(`duplicate role id(s): ${[...new Set(dupes)].join(', ')}`);
|
|
27
|
+
const roots = def.roles.filter(r => r.reports_to === null);
|
|
28
|
+
if (roots.length === 0)
|
|
29
|
+
errors.push('no root role — exactly one role must have reports_to: null');
|
|
30
|
+
if (roots.length > 1)
|
|
31
|
+
errors.push(`multiple root roles (${roots.map(r => r.id).join(', ')}) — exactly one may have reports_to: null`);
|
|
32
|
+
for (const r of def.roles) {
|
|
33
|
+
if (r.reports_to !== null && !ids.includes(r.reports_to))
|
|
34
|
+
errors.push(`role "${r.id}": reports_to "${r.reports_to}" matches no role id`);
|
|
35
|
+
if (r.reports_to === r.id)
|
|
36
|
+
errors.push(`role "${r.id}" reports to itself`);
|
|
37
|
+
}
|
|
38
|
+
if (def.schedule != null && parseSchedule(def.schedule) === null)
|
|
39
|
+
errors.push(`schedule "${def.schedule}" is not parseable — use "<N>s", "<N>m", or "<N>h"`);
|
|
40
|
+
return errors;
|
|
41
|
+
}
|
|
42
|
+
export function migrateOrgConfig(raw) {
|
|
43
|
+
const def = { ...raw };
|
|
44
|
+
const dropped = [];
|
|
45
|
+
const notes = [];
|
|
46
|
+
// v1 loop → v2 schedule ("<N>m"); only when no v2 schedule already set
|
|
47
|
+
const loop = def['loop'];
|
|
48
|
+
if (loop && typeof loop.poll_interval_minutes === 'number' && def['schedule'] == null) {
|
|
49
|
+
def['schedule'] = `${loop.poll_interval_minutes}m`;
|
|
50
|
+
notes.push(`loop.poll_interval_minutes=${loop.poll_interval_minutes} → schedule "${def['schedule']}"`);
|
|
51
|
+
}
|
|
52
|
+
for (const k of V1_TOP_LEVEL_KEYS) {
|
|
53
|
+
if (k in def) {
|
|
54
|
+
delete def[k];
|
|
55
|
+
dropped.push(k);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (Array.isArray(def['roles'])) {
|
|
59
|
+
const roleKeysDropped = new Set();
|
|
60
|
+
def['roles'] = def['roles'].map(role => {
|
|
61
|
+
const r = { ...role };
|
|
62
|
+
if (typeof r['agent_type'] === 'string') {
|
|
63
|
+
if (r['type'] == null || r['type'] === 'specialist') {
|
|
64
|
+
r['type'] = r['agent_type'];
|
|
65
|
+
notes.push(`role ${String(r['id'])}: agent_type → type`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
for (const k of V1_ROLE_KEYS) {
|
|
69
|
+
if (k in r) {
|
|
70
|
+
delete r[k];
|
|
71
|
+
roleKeysDropped.add(`roles[].${k}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return r;
|
|
75
|
+
});
|
|
76
|
+
dropped.push(...roleKeysDropped);
|
|
77
|
+
}
|
|
78
|
+
if (def['schedule'] === undefined)
|
|
79
|
+
def['schedule'] = null;
|
|
80
|
+
if (typeof def['status'] !== 'string')
|
|
81
|
+
def['status'] = 'stopped';
|
|
82
|
+
// Throws ZodError on an unmigratable config — caller surfaces it.
|
|
83
|
+
OrgDefSchema.parse(def);
|
|
84
|
+
return { def, dropped, notes };
|
|
85
|
+
}
|
|
86
|
+
/** Reads `cfgPath`, migrates it, and (unless already v2) backs up the
|
|
87
|
+
* original to `backupPath` and overwrites `cfgPath` with the migrated def.
|
|
88
|
+
* Throws on an unmigratable config (schema failure inside migrateOrgConfig,
|
|
89
|
+
* or a structural violation caught here) — caller decides how to report it.
|
|
90
|
+
* Refuses to write when the migrated def fails checkOrgStructure, so a v1
|
|
91
|
+
* config with e.g. all-null reports_to fails cleanly instead of silently
|
|
92
|
+
* writing a config the runtime can't start. */
|
|
93
|
+
export function migrateOrgFile(cfgPath, backupPath) {
|
|
94
|
+
const raw = JSON.parse(readFileSync(cfgPath, 'utf8'));
|
|
95
|
+
const result = migrateOrgConfig(raw);
|
|
96
|
+
const def = result.def;
|
|
97
|
+
const structuralErrors = checkOrgStructure(def);
|
|
98
|
+
if (structuralErrors.length) {
|
|
99
|
+
throw new Error(`migrated config still fails structural validation: ${structuralErrors.join('; ')}`);
|
|
100
|
+
}
|
|
101
|
+
if (result.dropped.length === 0 && result.notes.length === 0) {
|
|
102
|
+
return { status: 'already-v2', dropped: [], notes: [] };
|
|
103
|
+
}
|
|
104
|
+
if (!existsSync(backupPath))
|
|
105
|
+
writeFileSync(backupPath, JSON.stringify(raw, null, 2));
|
|
106
|
+
const tmpPath = `${cfgPath}.tmp`;
|
|
107
|
+
writeFileSync(tmpPath, JSON.stringify(result.def, null, 2));
|
|
108
|
+
renameSync(tmpPath, cfgPath);
|
|
109
|
+
return { status: 'migrated', dropped: result.dropped, notes: result.notes };
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=migrate.js.map
|
|
@@ -123,5 +123,12 @@ export function formatEvent(e) {
|
|
|
123
123
|
default: return `${t} ▪ ${from} ${e.type}: ${trim(e.msg ?? '')}`;
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
-
|
|
126
|
+
// Newlines stripped BEFORE truncation — the truncated branch previously kept
|
|
127
|
+
// them, so long multi-line messages broke the one-line-per-event log format.
|
|
128
|
+
const trim = (s, n = 120) => {
|
|
129
|
+
if (!s)
|
|
130
|
+
return '';
|
|
131
|
+
const oneLine = s.replace(/\s*\n\s*/g, ' ');
|
|
132
|
+
return oneLine.length > n ? `${oneLine.slice(0, n - 1)}…` : oneLine;
|
|
133
|
+
};
|
|
127
134
|
//# sourceMappingURL=reporting.js.map
|
|
@@ -39,6 +39,10 @@ export async function runAgentSession(opts) {
|
|
|
39
39
|
// would skip that drain entirely.
|
|
40
40
|
while (true) {
|
|
41
41
|
await runOneSession(opts);
|
|
42
|
+
// The dead session's generator may still hold the waker — drop it so a
|
|
43
|
+
// push() before the next stream() starts only queues instead of being
|
|
44
|
+
// consumed by the abandoned generator (silent message loss).
|
|
45
|
+
mailbox.detach();
|
|
42
46
|
if (mailbox.isClosed)
|
|
43
47
|
return;
|
|
44
48
|
opts.bus.emit({ type: 'status', from: opts.role.id, msg: 'session restarting (turn limit reached, mailbox still open)' });
|
|
@@ -48,7 +52,6 @@ export async function runAgentSession(opts) {
|
|
|
48
52
|
async function runOneSession(opts) {
|
|
49
53
|
const { org, role, bus, policy, mailbox, cwd, deliver } = opts;
|
|
50
54
|
const queryFn = opts.queryFn ?? query;
|
|
51
|
-
const isCoordinator = role.reports_to == null;
|
|
52
55
|
const orgServer = createSdkMcpServer({
|
|
53
56
|
name: 'org',
|
|
54
57
|
version: '1.0.0',
|
|
@@ -57,7 +60,10 @@ async function runOneSession(opts) {
|
|
|
57
60
|
const text = await opts.recall(role.id, args.query);
|
|
58
61
|
return { content: [{ type: 'text', text }] };
|
|
59
62
|
})] : []),
|
|
60
|
-
|
|
63
|
+
// Gate purely on onComplete: the daemon passes it only to the role its
|
|
64
|
+
// boss-selection rule picked, so tool availability always matches the
|
|
65
|
+
// kickoff instruction (reports_to may be non-null for a fallback boss).
|
|
66
|
+
...(opts.onComplete ? [tool('org_complete', 'Record the outcome of this run. Call exactly once, when the goal is achieved or clearly cannot be. The outcome and summary are persisted to the org run history and briefed to the next run.', { outcome: z.enum(['achieved', 'partial', 'failed']), summary: z.string() }, async (args) => {
|
|
61
67
|
opts.onComplete(role.id, args.outcome, args.summary);
|
|
62
68
|
return { content: [{ type: 'text', text: `outcome "${args.outcome}" recorded` }] };
|
|
63
69
|
})] : []),
|
|
@@ -1914,6 +1914,12 @@ select.pb-cfg-inp { padding: 4px 7px; cursor: pointer; }
|
|
|
1914
1914
|
<button class="btn chunk-build-btn" id="btn-build-docs" onclick="buildKnowledgeDocs()" title="Index .md/.mdx/.txt/.rst files">⊕ Build Docs</button>
|
|
1915
1915
|
<span class="chunk-build-status" id="chunk-build-status"></span>
|
|
1916
1916
|
</div>
|
|
1917
|
+
<div class="filter-bar" style="margin-bottom:6px">
|
|
1918
|
+
<input class="filter-input" id="sb-search-input" type="text" placeholder="Ask your Second Brain — semantic search by meaning…"
|
|
1919
|
+
onkeydown="if(event.key==='Enter')secondBrainSearch()">
|
|
1920
|
+
<button class="btn" onclick="secondBrainSearch()" title="Semantic search (warm local model)">⌕ Search</button>
|
|
1921
|
+
</div>
|
|
1922
|
+
<div id="sb-search-results" style="display:none;margin-bottom:12px"></div>
|
|
1917
1923
|
<div class="filter-bar">
|
|
1918
1924
|
<input class="filter-input" type="text" placeholder="Filter chunks…" oninput="filterChunks(this.value)">
|
|
1919
1925
|
<button class="btn" onclick="_chunksLoaded=false;loadChunksTab()" title="Refresh">↺</button>
|
|
@@ -13592,6 +13598,44 @@ async function cleanSwarmData() {
|
|
|
13592
13598
|
// ── chunks memory tab ──────────────────────────────────────
|
|
13593
13599
|
let _chunks = [], _chunksLoaded = false, _editChunkId = null;
|
|
13594
13600
|
|
|
13601
|
+
// Second Brain semantic search — hits the warm /api/knowledge/search endpoint
|
|
13602
|
+
// (the server holds the local embedding model hot; ~60ms per query).
|
|
13603
|
+
async function secondBrainSearch() {
|
|
13604
|
+
const input = document.getElementById('sb-search-input');
|
|
13605
|
+
const box = document.getElementById('sb-search-results');
|
|
13606
|
+
if (!input || !box) return;
|
|
13607
|
+
const q = input.value.trim();
|
|
13608
|
+
if (!q) { box.style.display = 'none'; return; }
|
|
13609
|
+
box.style.display = 'block';
|
|
13610
|
+
box.innerHTML = '<div class="loading-txt">Searching…</div>';
|
|
13611
|
+
try {
|
|
13612
|
+
const r = await fetch('/api/knowledge/search', {
|
|
13613
|
+
method: 'POST',
|
|
13614
|
+
headers: { 'Content-Type': 'application/json' },
|
|
13615
|
+
body: JSON.stringify({ query: q, limit: 5 }),
|
|
13616
|
+
});
|
|
13617
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
13618
|
+
const data = await r.json();
|
|
13619
|
+
const results = data.results || [];
|
|
13620
|
+
if (!results.length) {
|
|
13621
|
+
box.innerHTML = '<div class="empty" style="padding:10px">No matches (' + esc(data.method || '?') + '). Ingest documents with: <code>monomind doc ingest ./path</code></div>';
|
|
13622
|
+
return;
|
|
13623
|
+
}
|
|
13624
|
+
box.innerHTML =
|
|
13625
|
+
'<div style="font-size:10px;letter-spacing:0.07em;text-transform:uppercase;color:var(--text-xs);margin-bottom:6px">' +
|
|
13626
|
+
results.length + ' result(s) · ' + esc(data.method || '') + '</div>' +
|
|
13627
|
+
results.map(function (res) {
|
|
13628
|
+
return '<div style="border:1px solid var(--border);border-radius:8px;padding:8px 10px;margin-bottom:6px">' +
|
|
13629
|
+
'<div style="font-size:10px;color:var(--text-lo);margin-bottom:3px">' + esc(res.key || '') +
|
|
13630
|
+
' <span style="color:var(--accent)">' + (typeof res.score === 'number' ? res.score.toFixed(2) : '') + '</span></div>' +
|
|
13631
|
+
'<div style="font-size:12px;white-space:pre-wrap;max-height:120px;overflow:hidden">' + esc(String(res.content || '').slice(0, 600)) + '</div>' +
|
|
13632
|
+
'</div>';
|
|
13633
|
+
}).join('');
|
|
13634
|
+
} catch (e) {
|
|
13635
|
+
box.innerHTML = '<div class="empty" style="padding:10px">Search failed: ' + esc(e.message) + '</div>';
|
|
13636
|
+
}
|
|
13637
|
+
}
|
|
13638
|
+
|
|
13595
13639
|
async function loadChunksTab() {
|
|
13596
13640
|
if (_chunksLoaded) return;
|
|
13597
13641
|
const grid = document.getElementById('chunks-grid');
|
|
@@ -1782,12 +1782,12 @@ async function loadRunHistory() {
|
|
|
1782
1782
|
${runs.slice(0, 10).map(h => `
|
|
1783
1783
|
<div style="font-size:9px;padding:5px 0;border-bottom:1px solid oklch(50% 0 0 / 0.1);display:flex;gap:10px;align-items:baseline">
|
|
1784
1784
|
<span style="color:var(--dim);white-space:nowrap">${esc(h.run || '?')}</span>
|
|
1785
|
-
<span style="color:var(--muted);white-space:nowrap">${h.durationMs != null ? Math.round(h.durationMs / 1000) + 's' : '—'} · ${h.totalTokens || 0} tok</span>
|
|
1785
|
+
<span style="color:var(--muted);white-space:nowrap">${Number.isFinite(Number(h.durationMs)) && h.durationMs != null ? Math.round(Number(h.durationMs) / 1000) + 's' : '—'} · ${Number(h.totalTokens) || 0} tok</span>
|
|
1786
1786
|
${h.outcome
|
|
1787
1787
|
? `<span style="color:${outcomeColor(h.outcome.status)}">${esc(h.outcome.status.toUpperCase())}</span>
|
|
1788
1788
|
<span style="color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(h.outcome.summary || '')}</span>`
|
|
1789
1789
|
: `<span style="color:var(--dim)">NO OUTCOME</span>`}
|
|
1790
|
-
${h.crashes && h.crashes.length ? `<span style="color:var(--red)">✗ ${h.crashes.length} crash</span>` : ''}
|
|
1790
|
+
${Array.isArray(h.crashes) && h.crashes.length ? `<span style="color:var(--red)">✗ ${Number(h.crashes.length)} crash</span>` : ''}
|
|
1791
1791
|
</div>`).join('')}
|
|
1792
1792
|
</div>`;
|
|
1793
1793
|
} catch (_) { el.innerHTML = ''; }
|
|
@@ -5920,8 +5920,19 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
|
|
|
5920
5920
|
// when this endpoint is cold/absent. Fully local — model + store on disk.
|
|
5921
5921
|
if (req.method === 'POST' && url === '/api/knowledge/search') {
|
|
5922
5922
|
let body = '';
|
|
5923
|
-
|
|
5923
|
+
let overLimit = false;
|
|
5924
|
+
req.on('data', c => {
|
|
5925
|
+
if (overLimit) return;
|
|
5926
|
+
body += c;
|
|
5927
|
+
if (body.length > 64 * 1024) { // queries are prompts, not documents
|
|
5928
|
+
overLimit = true;
|
|
5929
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
5930
|
+
res.end('{"error":"request body too large"}');
|
|
5931
|
+
req.destroy();
|
|
5932
|
+
}
|
|
5933
|
+
});
|
|
5924
5934
|
req.on('end', async () => {
|
|
5935
|
+
if (overLimit) return;
|
|
5925
5936
|
try {
|
|
5926
5937
|
const payload = JSON.parse(body || '{}');
|
|
5927
5938
|
const query = String(payload.query || '').slice(0, 2000);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI engine for Monomind — an open-source MCP server that extends Claude Code with a codebase knowledge graph (tree-sitter + SQLite), persistent memory, multi-agent task coordination, and session hooks. MIT licensed, fully local.",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
},
|
|
106
106
|
"optionalDependencies": {
|
|
107
107
|
"@huggingface/transformers": "^3.8.1",
|
|
108
|
-
"@monoes/memory": "^1.0.
|
|
108
|
+
"@monoes/memory": "^1.0.8",
|
|
109
109
|
"@monomind/hooks": "*",
|
|
110
110
|
"@monomind/mcp": "*",
|
|
111
111
|
"@monomind/routing": "*",
|