@jbarket/loomai 0.10.0 → 0.12.0
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/CHANGELOG.md +57 -1
- package/README.md +36 -0
- package/dist/backends/migrations.js +9 -0
- package/dist/backends/people-household.d.ts +77 -0
- package/dist/backends/people-household.js +305 -0
- package/dist/backends/people.d.ts +37 -1
- package/dist/backends/people.js +42 -7
- package/dist/bundle.d.ts +80 -0
- package/dist/bundle.js +377 -0
- package/dist/cli/bundle.d.ts +3 -0
- package/dist/cli/bundle.js +107 -0
- package/dist/cli/household.js +22 -2
- package/dist/cli/index.js +10 -0
- package/dist/cli/subcommands.d.ts +1 -1
- package/dist/cli/subcommands.js +1 -1
- package/dist/config.d.ts +5 -0
- package/dist/config.js +12 -2
- package/dist/household.d.ts +16 -0
- package/dist/household.js +70 -0
- package/dist/server.js +4 -2
- package/dist/tools/person.d.ts +1 -0
- package/dist/tools/person.js +19 -6
- package/dist/transport/http-server.js +10 -1
- package/package.json +1 -1
package/dist/cli/household.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* neither is a remote verb, so they never run through /cli. They read and
|
|
4
4
|
* write $LOOM_HOME (default ~/.config/loom) directly.
|
|
5
5
|
*/
|
|
6
|
-
import { addAgent, createToken, householdHome, HouseholdError, initHousehold, loadHousehold, loadTokens, parseRole, revokeToken, } from '../household.js';
|
|
6
|
+
import { addAgent, agentDir, moveKnowledgeToCommons, createToken, householdHome, HouseholdError, initHousehold, loadHousehold, loadTokens, parseRole, revokeToken, } from '../household.js';
|
|
7
|
+
import { splitPeopleIntoCommons } from '../backends/people-household.js';
|
|
7
8
|
const HOUSEHOLD_USAGE = `Usage: loom household <command>
|
|
8
9
|
|
|
9
10
|
show The household at $LOOM_HOME: agents, the default, the owner
|
|
@@ -11,6 +12,10 @@ const HOUSEHOLD_USAGE = `Usage: loom household <command>
|
|
|
11
12
|
Make $LOOM_HOME/<agent> (a context dir) the first agent of a new
|
|
12
13
|
household: it moves to agents/<agent>, and a symlink stays behind
|
|
13
14
|
add <agent> Add an agent whose directory already exists under agents/
|
|
15
|
+
commons Move the default agent's knowledge.db to commons/knowledge.db and
|
|
16
|
+
split every agent's person cards (handles and facts to the commons
|
|
17
|
+
directory, the rest stays as its relationships). Once, with
|
|
18
|
+
every loom daemon on this household stopped
|
|
14
19
|
|
|
15
20
|
$LOOM_HOME defaults to ~/.config/loom.
|
|
16
21
|
`;
|
|
@@ -52,7 +57,22 @@ export async function runHousehold(argv, io) {
|
|
|
52
57
|
return 2;
|
|
53
58
|
}
|
|
54
59
|
const h = initHousehold(householdHome(io.env), agent, { owner: flag(rest, '--owner') });
|
|
55
|
-
|
|
60
|
+
const cards = splitPeopleIntoCommons(h, agent, agentDir(h, agent));
|
|
61
|
+
io.stdout(`Household at ${h.home}: ${agent} moved to agents/${agent}; ${h.home}/${agent} is now a symlink to it. ${cards} person card${cards === 1 ? '' : 's'} split into the commons directory.\n`);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
if (sub === 'commons') {
|
|
65
|
+
const h = need(io);
|
|
66
|
+
if (!h)
|
|
67
|
+
return 1;
|
|
68
|
+
const moved = moveKnowledgeToCommons(h, h.default);
|
|
69
|
+
io.stdout(moved ? `Knowledge moved to ${moved}; every agent shares it now.\n` : `agents/${h.default} has no knowledge.db to move.\n`);
|
|
70
|
+
// Every agent's cards, so the daemon's "haven't been split" refusal can
|
|
71
|
+
// always be cleared here.
|
|
72
|
+
for (const a of h.agents) {
|
|
73
|
+
const cards = splitPeopleIntoCommons(h, a, agentDir(h, a));
|
|
74
|
+
io.stdout(`${a}: ${cards} person card${cards === 1 ? '' : 's'} split (handles and facts in the commons directory, the rest as ${a}'s relationships).\n`);
|
|
75
|
+
}
|
|
56
76
|
return 0;
|
|
57
77
|
}
|
|
58
78
|
if (sub === 'add') {
|
package/dist/cli/index.js
CHANGED
|
@@ -34,6 +34,8 @@ Commands:
|
|
|
34
34
|
pieces List installed pieces (loom-<name> on PATH)
|
|
35
35
|
household The household: agents and the default (docs/specs/household.md)
|
|
36
36
|
token Mint, list and revoke the household's tokens
|
|
37
|
+
export Write the whole household to one encrypted bundle
|
|
38
|
+
restore Lay a bundle down in an empty LOOM_HOME, or --drill it
|
|
37
39
|
|
|
38
40
|
Pieces: \`loom <name> ...\` runs an installed extension, loom-<name> on PATH
|
|
39
41
|
(e.g. loom tasks list). Core commands always win. See docs/extensions.md.
|
|
@@ -208,6 +210,14 @@ async function dispatch(sub, rest, io) {
|
|
|
208
210
|
const { runToken } = await import('./household.js');
|
|
209
211
|
return runToken(rest, io);
|
|
210
212
|
}
|
|
213
|
+
case 'export': {
|
|
214
|
+
const { runExport } = await import('./bundle.js');
|
|
215
|
+
return runExport(rest, io);
|
|
216
|
+
}
|
|
217
|
+
case 'restore': {
|
|
218
|
+
const { runRestore } = await import('./bundle.js');
|
|
219
|
+
return runRestore(rest, io);
|
|
220
|
+
}
|
|
211
221
|
default:
|
|
212
222
|
io.stderr(`Subcommand not implemented yet: ${sub}\n`);
|
|
213
223
|
return 2;
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* (src/cli/index.ts) and the CLI-vs-MCP routing gate (src/index.ts) so
|
|
4
4
|
* the set never drifts between entry points.
|
|
5
5
|
*/
|
|
6
|
-
export declare const SUBCOMMANDS: readonly ['wake', 'recall', 'remember', 'forget', 'update', 'memory', 'knowledge', 'person', 'update-identity', 'bootstrap', 'serve', 'inject', 'harness', 'install', 'doctor', 'migrate', 'pieces', 'household', 'token'];
|
|
6
|
+
export declare const SUBCOMMANDS: readonly ['wake', 'recall', 'remember', 'forget', 'update', 'memory', 'knowledge', 'person', 'update-identity', 'bootstrap', 'serve', 'inject', 'harness', 'install', 'doctor', 'migrate', 'pieces', 'household', 'token', 'export', 'restore'];
|
|
7
7
|
export type Subcommand = typeof SUBCOMMANDS[number];
|
package/dist/cli/subcommands.js
CHANGED
|
@@ -7,5 +7,5 @@ export const SUBCOMMANDS = [
|
|
|
7
7
|
'wake', 'recall', 'remember', 'forget', 'update',
|
|
8
8
|
'memory', 'knowledge', 'person', 'update-identity', 'bootstrap', 'serve',
|
|
9
9
|
'inject', 'harness',
|
|
10
|
-
'install', 'doctor', 'migrate', 'pieces', 'household', 'token',
|
|
10
|
+
'install', 'doctor', 'migrate', 'pieces', 'household', 'token', 'export', 'restore',
|
|
11
11
|
];
|
package/dist/config.d.ts
CHANGED
|
@@ -24,6 +24,11 @@ export declare function assertContextBootable(contextDir: string, defaultPath?:
|
|
|
24
24
|
*/
|
|
25
25
|
export declare function resolveRepoRoot(): string;
|
|
26
26
|
export declare function resolveSqliteDbPath(contextDir: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Knowledge is facts about the world. In a household it's the commons, shared
|
|
29
|
+
* by every agent (docs/specs/household.md); otherwise it lives in the
|
|
30
|
+
* agent's own context dir, as it always has.
|
|
31
|
+
*/
|
|
27
32
|
export declare function resolveKnowledgeDbPath(contextDir: string): string;
|
|
28
33
|
/**
|
|
29
34
|
* Resolve the acting identity's name for actor attribution (knowledge
|
package/dist/config.js
CHANGED
|
@@ -18,7 +18,7 @@ import { resolve, dirname, basename } from 'node:path';
|
|
|
18
18
|
import { homedir } from 'node:os';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
20
|
import { existsSync, readFileSync, writeFileSync, statSync, realpathSync } from 'node:fs';
|
|
21
|
-
import { contextFromHome } from './household.js';
|
|
21
|
+
import { commonsKnowledgePath, contextFromHome, householdOf } from './household.js';
|
|
22
22
|
/**
|
|
23
23
|
* The single canonical helper for the default context path. Every call
|
|
24
24
|
* site that needs `~/.config/loom/default` must go through this —
|
|
@@ -72,8 +72,18 @@ export function resolveRepoRoot() {
|
|
|
72
72
|
export function resolveSqliteDbPath(contextDir) {
|
|
73
73
|
return process.env.LOOM_SQLITE_DB_PATH ?? resolve(contextDir, 'memories.db');
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* Knowledge is facts about the world. In a household it's the commons, shared
|
|
77
|
+
* by every agent (docs/specs/household.md); otherwise it lives in the
|
|
78
|
+
* agent's own context dir, as it always has.
|
|
79
|
+
*/
|
|
75
80
|
export function resolveKnowledgeDbPath(contextDir) {
|
|
76
|
-
|
|
81
|
+
if (process.env.LOOM_KNOWLEDGE_DB_PATH)
|
|
82
|
+
return process.env.LOOM_KNOWLEDGE_DB_PATH;
|
|
83
|
+
const hh = householdOf(contextDir);
|
|
84
|
+
if (hh)
|
|
85
|
+
return commonsKnowledgePath(hh.household);
|
|
86
|
+
return resolve(contextDir, 'knowledge.db');
|
|
77
87
|
}
|
|
78
88
|
/**
|
|
79
89
|
* Resolve the acting identity's name for actor attribution (knowledge
|
package/dist/household.d.ts
CHANGED
|
@@ -99,3 +99,19 @@ export declare function addAgent(h: Household, agent: string): Household;
|
|
|
99
99
|
* `<home>/agents/<name>` serves that household; see householdOf.)
|
|
100
100
|
*/
|
|
101
101
|
export declare function contextFromHome(env?: NodeJS.ProcessEnv): string | undefined;
|
|
102
|
+
export declare function commonsDir(h: Household): string;
|
|
103
|
+
/** The household's shared knowledge store. */
|
|
104
|
+
export declare function commonsKnowledgePath(h: Household): string;
|
|
105
|
+
/**
|
|
106
|
+
* Why this household can't serve yet, or null. An agent's own knowledge.db
|
|
107
|
+
* beside the commons would quietly fork the knowledge base, so both "only the
|
|
108
|
+
* agent's" (not moved yet) and "both" (needs a merge) are refusals.
|
|
109
|
+
*/
|
|
110
|
+
export declare function commonsProblem(h: Household): string | null;
|
|
111
|
+
/**
|
|
112
|
+
* Move one agent's knowledge.db to the commons: checkpoint its WAL into the
|
|
113
|
+
* main file, then rename it (and any sidecars). Refuses when the commons
|
|
114
|
+
* already has a knowledge store. Run with every daemon on this household
|
|
115
|
+
* stopped.
|
|
116
|
+
*/
|
|
117
|
+
export declare function moveKnowledgeToCommons(h: Household, agent: string): string | null;
|
package/dist/household.js
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
|
|
17
17
|
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
18
18
|
import { homedir } from 'node:os';
|
|
19
|
+
import BetterSqlite3 from 'better-sqlite3';
|
|
19
20
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
20
21
|
export const AGENT_NAME = /^[a-z][a-z0-9-]{0,39}$/;
|
|
21
22
|
const HOUSEHOLD_FILE = 'household.json';
|
|
@@ -333,6 +334,21 @@ export function initHousehold(home, agent, opts = {}) {
|
|
|
333
334
|
renameSync(from, to);
|
|
334
335
|
symlinkSync(join('agents', agent), from);
|
|
335
336
|
const h = { home, agents: [agent], default: agent, owner: opts.owner };
|
|
337
|
+
// The first agent's knowledge becomes the household's commons.
|
|
338
|
+
if (existsSync(join(to, 'knowledge.db')) && !existsSync(join(home, 'commons', 'knowledge.db'))) {
|
|
339
|
+
const kdb = new BetterSqlite3(join(to, 'knowledge.db'));
|
|
340
|
+
try {
|
|
341
|
+
kdb.pragma('wal_checkpoint(TRUNCATE)');
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
kdb.close();
|
|
345
|
+
}
|
|
346
|
+
mkdirSync(join(home, 'commons'), { recursive: true, mode: 0o700 });
|
|
347
|
+
for (const ext of SQLITE_SIDECARS) {
|
|
348
|
+
if (existsSync(join(to, 'knowledge.db') + ext))
|
|
349
|
+
renameSync(join(to, 'knowledge.db') + ext, join(home, 'commons', 'knowledge.db') + ext);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
336
352
|
withLock(home, () => writePrivate(join(home, HOUSEHOLD_FILE), JSON.stringify({ v: 1, agents: h.agents, default: h.default, ...(h.owner ? { owner: h.owner } : {}) }, null, 2) + '\n'));
|
|
337
353
|
return h;
|
|
338
354
|
}
|
|
@@ -387,3 +403,57 @@ export function contextFromHome(env = process.env) {
|
|
|
387
403
|
throw new HouseholdError(`LOOM_AGENT=${agent} isn't in the household at ${h.home} (${h.agents.join(', ')})`);
|
|
388
404
|
return agentDir(h, agent);
|
|
389
405
|
}
|
|
406
|
+
// ─── The commons ─────────────────────────────────────────────────────────────
|
|
407
|
+
export function commonsDir(h) {
|
|
408
|
+
return join(h.home, 'commons');
|
|
409
|
+
}
|
|
410
|
+
/** The household's shared knowledge store. */
|
|
411
|
+
export function commonsKnowledgePath(h) {
|
|
412
|
+
return join(commonsDir(h), 'knowledge.db');
|
|
413
|
+
}
|
|
414
|
+
const SQLITE_SIDECARS = ['', '-wal', '-shm'];
|
|
415
|
+
/**
|
|
416
|
+
* Why this household can't serve yet, or null. An agent's own knowledge.db
|
|
417
|
+
* beside the commons would quietly fork the knowledge base, so both "only the
|
|
418
|
+
* agent's" (not moved yet) and "both" (needs a merge) are refusals.
|
|
419
|
+
*/
|
|
420
|
+
export function commonsProblem(h) {
|
|
421
|
+
const commons = existsSync(commonsKnowledgePath(h));
|
|
422
|
+
for (const a of h.agents) {
|
|
423
|
+
if (!existsSync(join(agentDir(h, a), 'knowledge.db')))
|
|
424
|
+
continue;
|
|
425
|
+
return commons
|
|
426
|
+
? `agents/${a}/knowledge.db exists beside commons/knowledge.db; merging an agent's knowledge into the commons isn't automatic yet, so move one aside`
|
|
427
|
+
: `agents/${a}/knowledge.db hasn't moved to the commons; with every loom daemon stopped, run: loom household commons`;
|
|
428
|
+
}
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Move one agent's knowledge.db to the commons: checkpoint its WAL into the
|
|
433
|
+
* main file, then rename it (and any sidecars). Refuses when the commons
|
|
434
|
+
* already has a knowledge store. Run with every daemon on this household
|
|
435
|
+
* stopped.
|
|
436
|
+
*/
|
|
437
|
+
export function moveKnowledgeToCommons(h, agent) {
|
|
438
|
+
const from = join(agentDir(h, agent), 'knowledge.db');
|
|
439
|
+
if (!existsSync(from))
|
|
440
|
+
return null;
|
|
441
|
+
const to = commonsKnowledgePath(h);
|
|
442
|
+
if (existsSync(to))
|
|
443
|
+
throw new HouseholdError(`${to} already exists; merging another agent's knowledge into it isn't automatic yet`);
|
|
444
|
+
return withLock(h.home, () => {
|
|
445
|
+
const db = new BetterSqlite3(from);
|
|
446
|
+
try {
|
|
447
|
+
db.pragma('wal_checkpoint(TRUNCATE)');
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
db.close();
|
|
451
|
+
}
|
|
452
|
+
mkdirSync(commonsDir(h), { recursive: true, mode: 0o700 });
|
|
453
|
+
for (const ext of SQLITE_SIDECARS) {
|
|
454
|
+
if (existsSync(from + ext))
|
|
455
|
+
renameSync(from + ext, to + ext);
|
|
456
|
+
}
|
|
457
|
+
return to;
|
|
458
|
+
});
|
|
459
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -643,10 +643,12 @@ export function createLoomServer(config) {
|
|
|
643
643
|
inputSchema: z.object({
|
|
644
644
|
slug: slugParam,
|
|
645
645
|
revision_id: z.number().int().positive().describe('Revision id from person_history.'),
|
|
646
|
+
store: z.enum(['directory', 'relationship']).optional().describe('In a household, which half the revision is from (person_history says): the shared directory ' +
|
|
647
|
+
'entry, or this agent\'s relationship (the default). Ignored outside a household.'),
|
|
646
648
|
}).strict(),
|
|
647
649
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
648
|
-
}, async ({ slug, revision_id }) => {
|
|
649
|
-
const result = await personRevisionRestore(contextDir, { slug, revision_id });
|
|
650
|
+
}, async ({ slug, revision_id, store }) => {
|
|
651
|
+
const result = await personRevisionRestore(contextDir, { slug, revision_id, store });
|
|
650
652
|
return { content: [{ type: 'text', text: result }] };
|
|
651
653
|
});
|
|
652
654
|
// MCP-App render probe — env-gated, throwaway. Not part of the tool surface
|
package/dist/tools/person.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare function personRestore(contextDir: string, input: {
|
|
|
16
16
|
export declare function personRevisionRestore(contextDir: string, input: {
|
|
17
17
|
slug: string;
|
|
18
18
|
revision_id: number;
|
|
19
|
+
store?: 'directory' | 'relationship';
|
|
19
20
|
}): Promise<string>;
|
|
20
21
|
/** The `person` read. Throws on an invalid slug or an unreadable row; the MCP wrapper turns that into text. */
|
|
21
22
|
export declare function readPerson(contextDir: string, slug: string, includeArchived?: boolean): PersonReadResult;
|
package/dist/tools/person.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
* loom tool. The CLI (`loom person`) calls the read functions here too.
|
|
10
10
|
*/
|
|
11
11
|
import { resolveIdentityName } from '../config.js';
|
|
12
|
-
import {
|
|
12
|
+
import { householdOf } from '../household.js';
|
|
13
|
+
import { openHouseholdPeople } from '../backends/people-household.js';
|
|
14
|
+
import { openPeopleStore, PeopleStore, personSlugError, PersonCardError, UnreadableCardError, } from '../backends/people.js';
|
|
13
15
|
const json = (v) => JSON.stringify(v, null, 2);
|
|
14
16
|
/** Slugs validate before anything else, store or no store (criterion 11). */
|
|
15
17
|
function assertSlug(slug) {
|
|
@@ -22,9 +24,20 @@ function errorText(e) {
|
|
|
22
24
|
return `Error: ${e.message}`;
|
|
23
25
|
return `Error: ${e.message ?? String(e)}`;
|
|
24
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The people a context dir sees. In a household: the commons directory plus
|
|
29
|
+
* this agent's relationships (backends/people-household.ts). Otherwise the
|
|
30
|
+
* one store in the agent's memories.db, as before.
|
|
31
|
+
*/
|
|
32
|
+
function openPeople(contextDir, opts = {}) {
|
|
33
|
+
const hh = householdOf(contextDir);
|
|
34
|
+
if (hh)
|
|
35
|
+
return openHouseholdPeople(hh.household, hh.agent, contextDir, opts);
|
|
36
|
+
return openPeopleStore(contextDir, opts);
|
|
37
|
+
}
|
|
25
38
|
function withWritable(contextDir, fn) {
|
|
26
39
|
try {
|
|
27
|
-
const store =
|
|
40
|
+
const store = openPeople(contextDir);
|
|
28
41
|
try {
|
|
29
42
|
return json(fn(store));
|
|
30
43
|
}
|
|
@@ -50,12 +63,12 @@ export async function personRestore(contextDir, input) {
|
|
|
50
63
|
return withWritable(contextDir, (s) => s.restore(input));
|
|
51
64
|
}
|
|
52
65
|
export async function personRevisionRestore(contextDir, input) {
|
|
53
|
-
return withWritable(contextDir, (s) => s.revisionRestore(input));
|
|
66
|
+
return withWritable(contextDir, (s) => (s instanceof PeopleStore ? s.revisionRestore({ slug: input.slug, revision_id: input.revision_id }) : s.revisionRestore(input)));
|
|
54
67
|
}
|
|
55
68
|
/** The `person` read. Throws on an invalid slug or an unreadable row; the MCP wrapper turns that into text. */
|
|
56
69
|
export function readPerson(contextDir, slug, includeArchived = false) {
|
|
57
70
|
assertSlug(slug);
|
|
58
|
-
const store =
|
|
71
|
+
const store = openPeople(contextDir, { readonly: true });
|
|
59
72
|
if (!store)
|
|
60
73
|
return { found: false };
|
|
61
74
|
try {
|
|
@@ -66,7 +79,7 @@ export function readPerson(contextDir, slug, includeArchived = false) {
|
|
|
66
79
|
}
|
|
67
80
|
}
|
|
68
81
|
export function listPeople(contextDir, includeArchived = false) {
|
|
69
|
-
const store =
|
|
82
|
+
const store = openPeople(contextDir, { readonly: true });
|
|
70
83
|
if (!store)
|
|
71
84
|
return [];
|
|
72
85
|
try {
|
|
@@ -95,7 +108,7 @@ export async function personList(contextDir, input) {
|
|
|
95
108
|
export async function personHistory(contextDir, input) {
|
|
96
109
|
try {
|
|
97
110
|
assertSlug(input.slug);
|
|
98
|
-
const store =
|
|
111
|
+
const store = openPeople(contextDir, { readonly: true });
|
|
99
112
|
if (!store)
|
|
100
113
|
return json([]);
|
|
101
114
|
try {
|
|
@@ -41,7 +41,8 @@ import { createBackend } from '../backends/index.js';
|
|
|
41
41
|
import { handleCliBody } from './cli-endpoint.js';
|
|
42
42
|
import { handleUi, isUiRequest, legacyUiAuth } from '../ui/handler.js';
|
|
43
43
|
import { createKnowledgeOnlyServer } from './knowledge-server.js';
|
|
44
|
-
import {
|
|
44
|
+
import { hasUnsplitPeople } from '../backends/people-household.js';
|
|
45
|
+
import { agentDir, assertRealAgentDir, commonsProblem, roleString, TokenResolver } from '../household.js';
|
|
45
46
|
import { createHash, timingSafeEqual as cryptoTimingSafeEqual } from 'node:crypto';
|
|
46
47
|
import { assertSafeBind, checkBearer, checkBearerMulti, checkPayloadSize, DEFAULT_MAX_BODY_BYTES, } from './guards.js';
|
|
47
48
|
/**
|
|
@@ -142,6 +143,14 @@ export async function startHttpServer(opts) {
|
|
|
142
143
|
}
|
|
143
144
|
for (const a of household.agents)
|
|
144
145
|
assertRealAgentDir(household, a);
|
|
146
|
+
const problem = commonsProblem(household);
|
|
147
|
+
if (problem)
|
|
148
|
+
throw new Error(`startHttpServer: ${problem}`);
|
|
149
|
+
for (const a of household.agents) {
|
|
150
|
+
if (hasUnsplitPeople(agentDir(household, a))) {
|
|
151
|
+
throw new Error(`startHttpServer: agents/${a}'s person cards haven't been split into the commons; with every loom daemon stopped, run: loom household commons`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
145
154
|
}
|
|
146
155
|
const resolver = household ? new TokenResolver(household) : undefined;
|
|
147
156
|
const ownerDigest = opts.token ? createHash('sha256').update(opts.token).digest() : undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jbarket/loomai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "MCP server for persistent AI agent identity and memory. Single-file sqlite-vec + local ONNX embeddings; zero external services.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|