synomem 0.3.0 → 0.5.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 +82 -1
- package/README.md +54 -14
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +8 -2
- package/dist/backend.js.map +1 -1
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +451 -24
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +34 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +162 -11
- package/dist/client.js.map +1 -1
- package/dist/cloud.d.ts +16 -0
- package/dist/cloud.d.ts.map +1 -0
- package/dist/cloud.js +19 -0
- package/dist/cloud.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -1
- package/dist/config.js.map +1 -1
- package/dist/configure.d.ts +55 -0
- package/dist/configure.d.ts.map +1 -0
- package/dist/configure.js +193 -0
- package/dist/configure.js.map +1 -0
- package/dist/credentials.d.ts +15 -2
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +9 -1
- package/dist/credentials.js.map +1 -1
- package/dist/discover.d.ts +48 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +106 -0
- package/dist/discover.js.map +1 -0
- package/dist/import.d.ts +58 -43
- package/dist/import.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +15 -6
- package/dist/mcp/index.js.map +1 -1
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +8 -1
- package/dist/oauth.js.map +1 -1
- package/dist/projections.d.ts.map +1 -1
- package/dist/projections.js +19 -11
- package/dist/projections.js.map +1 -1
- package/dist/prompt.d.ts +28 -0
- package/dist/prompt.d.ts.map +1 -0
- package/dist/prompt.js +72 -0
- package/dist/prompt.js.map +1 -0
- package/dist/remote.d.ts +4 -0
- package/dist/remote.d.ts.map +1 -1
- package/dist/remote.js +4 -0
- package/dist/remote.js.map +1 -1
- package/dist/schemas.d.ts +101 -60
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +57 -5
- package/dist/schemas.js.map +1 -1
- package/dist/service.d.ts +6 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/storage.d.ts +19 -1
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +61 -14
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +50 -2
- package/dist/types.d.ts.map +1 -1
- package/docs/cli.md +68 -4
- package/docs/examples.md +1 -1
- package/docs/mcp.md +1 -1
- package/docs/skill.md +1 -1
- package/docs/storage-format.md +1 -1
- package/package.json +8 -8
- package/src/backend.ts +8 -2
- package/src/cli.ts +597 -31
- package/src/client.ts +176 -12
- package/src/cloud.ts +19 -0
- package/src/config.ts +8 -1
- package/src/configure.ts +249 -0
- package/src/credentials.ts +26 -3
- package/src/discover.ts +155 -0
- package/src/index.ts +9 -1
- package/src/mcp/index.ts +19 -5
- package/src/oauth.ts +8 -1
- package/src/projections.ts +21 -11
- package/src/prompt.ts +88 -0
- package/src/remote.ts +24 -0
- package/src/schemas.ts +60 -5
- package/src/service.ts +12 -0
- package/src/storage.ts +79 -13
- package/src/types.ts +45 -2
package/src/storage.ts
CHANGED
|
@@ -255,6 +255,21 @@ CREATE TABLE post_acknowledgments (
|
|
|
255
255
|
CREATE INDEX post_acknowledgments_post ON post_acknowledgments(post_id);
|
|
256
256
|
`;
|
|
257
257
|
|
|
258
|
+
const migrationV7 = `
|
|
259
|
+
-- Opaque canonical IDs, with the handle as a separate mutable name.
|
|
260
|
+
--
|
|
261
|
+
-- Existing agents keep their name-shaped ID and take it as their handle too.
|
|
262
|
+
-- Rewriting the actor ID inside stored events to tidy the format would be
|
|
263
|
+
-- exactly the rewrite an append-only log exists to prevent, so history stays
|
|
264
|
+
-- as written and only NEW agents get a generated opaque ID.
|
|
265
|
+
ALTER TABLE agents ADD COLUMN handle TEXT;
|
|
266
|
+
UPDATE agents SET handle = id WHERE handle IS NULL;
|
|
267
|
+
CREATE UNIQUE INDEX agents_handle ON agents(handle);
|
|
268
|
+
|
|
269
|
+
-- Archived agents keep their records and stop being able to act.
|
|
270
|
+
ALTER TABLE agents ADD COLUMN status TEXT NOT NULL DEFAULT 'active';
|
|
271
|
+
`;
|
|
272
|
+
|
|
258
273
|
const migrationV3 = `
|
|
259
274
|
DROP TRIGGER IF EXISTS events_append_only_update;
|
|
260
275
|
DROP TRIGGER IF EXISTS events_append_only_delete;
|
|
@@ -430,7 +445,8 @@ export class SynomemStorage implements SynomemRepository {
|
|
|
430
445
|
|
|
431
446
|
constructor(options: StorageOptions) {
|
|
432
447
|
this.home = resolve(options.home);
|
|
433
|
-
|
|
448
|
+
// The home is the storage directory; see configLocation in backend.ts.
|
|
449
|
+
this.storageDirectory = this.home;
|
|
434
450
|
this.databasePath = join(this.storageDirectory, 'synomem.sqlite3');
|
|
435
451
|
this.configPath = join(this.storageDirectory, 'config.json');
|
|
436
452
|
this.readOnly = options.readOnly;
|
|
@@ -518,7 +534,7 @@ export class SynomemStorage implements SynomemRepository {
|
|
|
518
534
|
const version = Number(
|
|
519
535
|
(db.prepare('PRAGMA user_version').get() as { user_version: number }).user_version,
|
|
520
536
|
);
|
|
521
|
-
if (version >
|
|
537
|
+
if (version > 7) {
|
|
522
538
|
throw new SynomemError(
|
|
523
539
|
'UNSUPPORTED_SCHEMA',
|
|
524
540
|
`Database schema version ${version} is newer than this package supports.`,
|
|
@@ -598,14 +614,26 @@ export class SynomemStorage implements SynomemRepository {
|
|
|
598
614
|
db.exec('PRAGMA user_version = 6');
|
|
599
615
|
});
|
|
600
616
|
}
|
|
617
|
+
const afterV6 = Number(
|
|
618
|
+
(db.prepare('PRAGMA user_version').get() as { user_version: number }).user_version,
|
|
619
|
+
);
|
|
620
|
+
if (afterV6 === 6) {
|
|
621
|
+
this.transactionSync(() => {
|
|
622
|
+
db.exec(migrationV7);
|
|
623
|
+
db.prepare(
|
|
624
|
+
'INSERT OR IGNORE INTO schema_migrations(version, applied_at) VALUES (?, ?)',
|
|
625
|
+
).run(7, new Date().toISOString());
|
|
626
|
+
db.exec('PRAGMA user_version = 7');
|
|
627
|
+
});
|
|
628
|
+
}
|
|
601
629
|
}
|
|
602
630
|
|
|
603
631
|
private assertSchemaSupported(): void {
|
|
604
632
|
const version = Number(
|
|
605
633
|
(this.db().prepare('PRAGMA user_version').get() as { user_version: number }).user_version,
|
|
606
634
|
);
|
|
607
|
-
if (version !==
|
|
608
|
-
if (version >= 1 && version <=
|
|
635
|
+
if (version !== 7) {
|
|
636
|
+
if (version >= 1 && version <= 6) {
|
|
609
637
|
throw new SynomemError(
|
|
610
638
|
'UNSUPPORTED_SCHEMA',
|
|
611
639
|
`Database schema version ${version} requires migration. Open this home once with readOnly: false, then retry the read-only client.`,
|
|
@@ -1530,12 +1558,23 @@ export class SynomemStorage implements SynomemRepository {
|
|
|
1530
1558
|
return { schemaVersion, appliedVersions };
|
|
1531
1559
|
}
|
|
1532
1560
|
|
|
1561
|
+
/**
|
|
1562
|
+
* Aliases that also name an agent directly.
|
|
1563
|
+
*
|
|
1564
|
+
* Matches against the HANDLE as well as the canonical ID. With opaque IDs an
|
|
1565
|
+
* alias can no longer accidentally equal one, but it can easily equal another
|
|
1566
|
+
* agent's handle — which is the collision that actually makes a lookup
|
|
1567
|
+
* ambiguous now.
|
|
1568
|
+
*/
|
|
1533
1569
|
aliasIdentityConflicts(): Array<{ alias: string; agentId: string }> {
|
|
1534
1570
|
return this.db()
|
|
1535
1571
|
.prepare(
|
|
1536
1572
|
`SELECT x.alias, x.agent_id AS agentId
|
|
1537
|
-
FROM aliases x
|
|
1538
|
-
|
|
1573
|
+
FROM aliases x
|
|
1574
|
+
JOIN agents a ON lower(a.id) = x.normalized_alias
|
|
1575
|
+
OR lower(a.handle) = x.normalized_alias
|
|
1576
|
+
WHERE a.id != x.agent_id
|
|
1577
|
+
ORDER BY x.alias`,
|
|
1539
1578
|
)
|
|
1540
1579
|
.all() as unknown as Array<{ alias: string; agentId: string }>;
|
|
1541
1580
|
}
|
|
@@ -1727,10 +1766,13 @@ export class SynomemStorage implements SynomemRepository {
|
|
|
1727
1766
|
const parsed = profileSchema.parse(profile);
|
|
1728
1767
|
this.db()
|
|
1729
1768
|
.prepare(
|
|
1730
|
-
|
|
1769
|
+
`INSERT INTO agents(id, handle, status, display_name, profile_json, created_at, updated_at)
|
|
1770
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
1731
1771
|
)
|
|
1732
1772
|
.run(
|
|
1733
1773
|
parsed.id,
|
|
1774
|
+
parsed.handle,
|
|
1775
|
+
parsed.status,
|
|
1734
1776
|
parsed.displayName,
|
|
1735
1777
|
JSON.stringify(parsed),
|
|
1736
1778
|
parsed.createdAt,
|
|
@@ -1742,8 +1784,18 @@ export class SynomemStorage implements SynomemRepository {
|
|
|
1742
1784
|
updateAgent(profile: AgentProfile, updatedAt: string): void {
|
|
1743
1785
|
const parsed = profileSchema.parse(profile);
|
|
1744
1786
|
this.db()
|
|
1745
|
-
.prepare(
|
|
1746
|
-
|
|
1787
|
+
.prepare(
|
|
1788
|
+
`UPDATE agents SET handle = ?, status = ?, display_name = ?, profile_json = ?,
|
|
1789
|
+
updated_at = ? WHERE id = ?`,
|
|
1790
|
+
)
|
|
1791
|
+
.run(
|
|
1792
|
+
parsed.handle,
|
|
1793
|
+
parsed.status,
|
|
1794
|
+
parsed.displayName,
|
|
1795
|
+
JSON.stringify(parsed),
|
|
1796
|
+
updatedAt,
|
|
1797
|
+
parsed.id,
|
|
1798
|
+
);
|
|
1747
1799
|
this.db().prepare('DELETE FROM aliases WHERE agent_id = ?').run(parsed.id);
|
|
1748
1800
|
this.insertAliases(parsed.id, parsed.aliases ?? []);
|
|
1749
1801
|
}
|
|
@@ -1770,10 +1822,10 @@ export class SynomemStorage implements SynomemRepository {
|
|
|
1770
1822
|
`SELECT DISTINCT a.profile_json
|
|
1771
1823
|
FROM agents a
|
|
1772
1824
|
LEFT JOIN aliases x ON x.agent_id = a.id
|
|
1773
|
-
WHERE lower(a.id) = ? OR x.normalized_alias = ?
|
|
1825
|
+
WHERE lower(a.id) = ? OR lower(a.handle) = ? OR x.normalized_alias = ?
|
|
1774
1826
|
ORDER BY a.id ASC`,
|
|
1775
1827
|
)
|
|
1776
|
-
.all(normalized, normalized) as unknown as ProfileRow[];
|
|
1828
|
+
.all(normalized, normalized, normalized) as unknown as ProfileRow[];
|
|
1777
1829
|
const candidates = rows.map((row) => profileSchema.parse(JSON.parse(row.profile_json)));
|
|
1778
1830
|
return candidates.length === 1 ? { match: candidates[0]!, candidates } : { candidates };
|
|
1779
1831
|
}
|
|
@@ -1964,11 +2016,16 @@ export class SynomemStorage implements SynomemRepository {
|
|
|
1964
2016
|
});
|
|
1965
2017
|
}
|
|
1966
2018
|
|
|
1967
|
-
|
|
2019
|
+
/**
|
|
2020
|
+
* @param directory the agent's projection directory name, which is its
|
|
2021
|
+
* handle rather than its canonical ID: these rows are keyed by the path on
|
|
2022
|
+
* disk, and projections are named for people to read.
|
|
2023
|
+
*/
|
|
2024
|
+
replaceAgentProjectionManifest(directory: string, paths: string[], generatedAt: string): void {
|
|
1968
2025
|
this.transactionSync(() => {
|
|
1969
2026
|
this.db()
|
|
1970
2027
|
.prepare('DELETE FROM projection_manifest WHERE path LIKE ? OR path LIKE ?')
|
|
1971
|
-
.run(`${
|
|
2028
|
+
.run(`${directory}/%`, `${directory}\\%`);
|
|
1972
2029
|
const insert = this.db().prepare(
|
|
1973
2030
|
'INSERT INTO projection_manifest(path, generated_at) VALUES (?, ?)',
|
|
1974
2031
|
);
|
|
@@ -1984,6 +2041,15 @@ export class SynomemStorage implements SynomemRepository {
|
|
|
1984
2041
|
).map((row) => row.path);
|
|
1985
2042
|
}
|
|
1986
2043
|
|
|
2044
|
+
/** The manifest with the time each path was written, newest first. */
|
|
2045
|
+
projectionManifestEntries(): { path: string; generatedAt: string }[] {
|
|
2046
|
+
return (
|
|
2047
|
+
this.db()
|
|
2048
|
+
.prepare('SELECT path, generated_at FROM projection_manifest ORDER BY generated_at DESC')
|
|
2049
|
+
.all() as unknown as { path: string; generated_at: string }[]
|
|
2050
|
+
).map((row) => ({ path: row.path, generatedAt: row.generated_at }));
|
|
2051
|
+
}
|
|
2052
|
+
|
|
1987
2053
|
integrityCheck(): string[] {
|
|
1988
2054
|
const rows = this.db().prepare('PRAGMA integrity_check').all() as unknown as Record<
|
|
1989
2055
|
string,
|
package/src/types.ts
CHANGED
|
@@ -28,10 +28,22 @@ export interface EvidenceReference {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface AgentProfile {
|
|
31
|
+
/**
|
|
32
|
+
* Canonical, opaque, immutable. Every event references this, so it can never
|
|
33
|
+
* change — which is exactly why the handle exists separately.
|
|
34
|
+
*/
|
|
31
35
|
id: string;
|
|
36
|
+
/** The human-friendly name, unique in the workspace and safe to rename. */
|
|
37
|
+
handle: string;
|
|
32
38
|
displayName: string;
|
|
33
39
|
aliases?: string[];
|
|
34
40
|
description?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Archived agents keep their history and stop being able to act. Events
|
|
43
|
+
* reference the actor permanently, so deletion would leave history pointing
|
|
44
|
+
* at nothing.
|
|
45
|
+
*/
|
|
46
|
+
status: 'active' | 'archived';
|
|
35
47
|
createdAt: string;
|
|
36
48
|
metadata?: Record<string, JsonValue>;
|
|
37
49
|
}
|
|
@@ -684,13 +696,15 @@ export interface BindRuntimeInput {
|
|
|
684
696
|
}
|
|
685
697
|
|
|
686
698
|
export interface CreateAgentInput {
|
|
687
|
-
|
|
699
|
+
/** The handle. The canonical ID is generated, never supplied. */
|
|
700
|
+
handle: string;
|
|
688
701
|
displayName: string;
|
|
689
702
|
aliases?: string[];
|
|
690
703
|
description?: string;
|
|
691
704
|
metadata?: Record<string, JsonValue>;
|
|
692
705
|
}
|
|
693
706
|
export interface UpdateAgentInput {
|
|
707
|
+
handle?: string;
|
|
694
708
|
displayName?: string;
|
|
695
709
|
aliases?: string[];
|
|
696
710
|
description?: string;
|
|
@@ -706,7 +720,15 @@ export interface KudosStats {
|
|
|
706
720
|
byTag: Record<string, number>;
|
|
707
721
|
}
|
|
708
722
|
export interface Diagnostic {
|
|
709
|
-
|
|
723
|
+
/**
|
|
724
|
+
* `skipped` is not a failure.
|
|
725
|
+
*
|
|
726
|
+
* A check the caller lacks permission to run says so and leaves the overall
|
|
727
|
+
* result healthy. Failing the whole diagnostic because an ordinary agent
|
|
728
|
+
* cannot read workspace administration would make `doctor` useless to the
|
|
729
|
+
* callers who need it most.
|
|
730
|
+
*/
|
|
731
|
+
level: 'ok' | 'warning' | 'error' | 'skipped';
|
|
710
732
|
code: string;
|
|
711
733
|
message: string;
|
|
712
734
|
path?: string;
|
|
@@ -715,6 +737,27 @@ export interface DoctorResult {
|
|
|
715
737
|
healthy: boolean;
|
|
716
738
|
diagnostics: Diagnostic[];
|
|
717
739
|
}
|
|
740
|
+
/**
|
|
741
|
+
* What the projected files on disk look like next to what they should be.
|
|
742
|
+
*
|
|
743
|
+
* Projections are derived, never canonical, so this reports drift rather than
|
|
744
|
+
* damage: `missing` and `unexpected` are both repaired by a rebuild, and
|
|
745
|
+
* neither means an event was lost.
|
|
746
|
+
*/
|
|
747
|
+
export interface ProjectionStatus {
|
|
748
|
+
/** Where projected files live. Absent on a backend that projects nothing. */
|
|
749
|
+
directory?: string;
|
|
750
|
+
settings: SynomemConfig['projection'];
|
|
751
|
+
/** True when the manifest matches what a rebuild would produce. */
|
|
752
|
+
current: boolean;
|
|
753
|
+
/** Recorded by the manifest, not the filesystem; absent before any rebuild. */
|
|
754
|
+
lastRebuiltAt?: string;
|
|
755
|
+
counts: { expected: number; manifest: number; missing: number; unexpected: number };
|
|
756
|
+
/** Expected but not on disk. Capped, because a large workspace has many. */
|
|
757
|
+
missing: string[];
|
|
758
|
+
/** On disk and in the manifest, but no longer expected. Capped likewise. */
|
|
759
|
+
unexpected: string[];
|
|
760
|
+
}
|
|
718
761
|
|
|
719
762
|
export type SynomemBackendConfig =
|
|
720
763
|
{ kind: 'local' } | { kind: 'remote'; baseUrl: string; workspaceId: string };
|