lyra-core 0.1.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.
Files changed (131) hide show
  1. package/README.md +24 -0
  2. package/package.json +65 -0
  3. package/src/brain/compaction.test.ts +156 -0
  4. package/src/brain/compaction.ts +131 -0
  5. package/src/brain/demo-endpoint.ts +13 -0
  6. package/src/brain/frozen-prefix.test.ts +235 -0
  7. package/src/brain/frozen-prefix.ts +320 -0
  8. package/src/brain/history-persist.test.ts +129 -0
  9. package/src/brain/history-persist.ts +154 -0
  10. package/src/brain/index.ts +44 -0
  11. package/src/brain/openai-brain.test.ts +61 -0
  12. package/src/brain/openai-brain.ts +544 -0
  13. package/src/brain/sanitize.test.ts +27 -0
  14. package/src/brain/sanitize.ts +23 -0
  15. package/src/brain/stub.ts +20 -0
  16. package/src/brain/types.ts +129 -0
  17. package/src/chain.ts +35 -0
  18. package/src/claude-plugins/discovery.test.ts +71 -0
  19. package/src/claude-plugins/discovery.ts +152 -0
  20. package/src/claude-plugins/index.ts +6 -0
  21. package/src/claude-plugins/types.ts +38 -0
  22. package/src/commands/index.ts +16 -0
  23. package/src/commands/registry.test.ts +186 -0
  24. package/src/commands/registry.ts +255 -0
  25. package/src/config.ts +201 -0
  26. package/src/economy/index.ts +6 -0
  27. package/src/events/index.ts +4 -0
  28. package/src/events/listeners.ts +37 -0
  29. package/src/events/queue.test.ts +43 -0
  30. package/src/events/queue.ts +63 -0
  31. package/src/events/router.ts +42 -0
  32. package/src/events/types.ts +28 -0
  33. package/src/format.ts +13 -0
  34. package/src/index.test.ts +6 -0
  35. package/src/index.ts +314 -0
  36. package/src/locks.test.ts +259 -0
  37. package/src/locks.ts +233 -0
  38. package/src/mcp/discovery.test.ts +97 -0
  39. package/src/mcp/discovery.ts +150 -0
  40. package/src/mcp/index.ts +10 -0
  41. package/src/mcp/manager.test.ts +97 -0
  42. package/src/mcp/manager.ts +110 -0
  43. package/src/mcp/stdio-client.ts +154 -0
  44. package/src/mcp/types.ts +44 -0
  45. package/src/memory/edit.test.ts +41 -0
  46. package/src/memory/edit.ts +53 -0
  47. package/src/memory/encryption.test.ts +68 -0
  48. package/src/memory/encryption.ts +94 -0
  49. package/src/memory/fs-util.ts +15 -0
  50. package/src/memory/index-file.ts +74 -0
  51. package/src/memory/index-sync.test.ts +81 -0
  52. package/src/memory/index-sync.ts +99 -0
  53. package/src/memory/index.ts +58 -0
  54. package/src/memory/list-tool.ts +105 -0
  55. package/src/memory/pack-blob.test.ts +90 -0
  56. package/src/memory/pack-blob.ts +120 -0
  57. package/src/memory/pack-gather.test.ts +110 -0
  58. package/src/memory/pack-gather.ts +112 -0
  59. package/src/memory/parser.test.ts +29 -0
  60. package/src/memory/parser.ts +20 -0
  61. package/src/memory/path-drift.test.ts +71 -0
  62. package/src/memory/read-tool-fallback.test.ts +54 -0
  63. package/src/memory/read-tool.ts +198 -0
  64. package/src/memory/save-tool.test.ts +193 -0
  65. package/src/memory/save-tool.ts +189 -0
  66. package/src/memory/scan.test.ts +24 -0
  67. package/src/memory/scan.ts +63 -0
  68. package/src/memory/topic.ts +32 -0
  69. package/src/memory/types.ts +49 -0
  70. package/src/migration/index.ts +6 -0
  71. package/src/migration/option3-crypto.test.ts +106 -0
  72. package/src/migration/option3-crypto.ts +143 -0
  73. package/src/pairing.test.ts +212 -0
  74. package/src/pairing.ts +285 -0
  75. package/src/paths.ts +70 -0
  76. package/src/permission/dangerous.ts +105 -0
  77. package/src/permission/env-redact.ts +54 -0
  78. package/src/permission/index.ts +16 -0
  79. package/src/permission/path-guard.ts +114 -0
  80. package/src/permission/permission.test.ts +299 -0
  81. package/src/permission/service.ts +191 -0
  82. package/src/plugins/context.ts +226 -0
  83. package/src/plugins/hooks.ts +81 -0
  84. package/src/plugins/index.ts +24 -0
  85. package/src/plugins/plugins.test.ts +196 -0
  86. package/src/plugins/tool-search.ts +49 -0
  87. package/src/public/card.test.ts +70 -0
  88. package/src/public/card.ts +67 -0
  89. package/src/runtime/activity.ts +29 -0
  90. package/src/runtime/index.ts +7 -0
  91. package/src/runtime/runtime.test.ts +55 -0
  92. package/src/runtime/runtime.ts +126 -0
  93. package/src/sandbox/credentials.ts +25 -0
  94. package/src/sandbox/docker.ts +389 -0
  95. package/src/sandbox/factory.ts +99 -0
  96. package/src/sandbox/index.ts +15 -0
  97. package/src/sandbox/linux.ts +141 -0
  98. package/src/sandbox/local.ts +19 -0
  99. package/src/sandbox/macos.ts +71 -0
  100. package/src/sandbox/sandbox.test.ts +386 -0
  101. package/src/sandbox/seatbelt-profile.ts +139 -0
  102. package/src/sandbox/types.ts +129 -0
  103. package/src/skills/index.ts +8 -0
  104. package/src/skills/scanner.test.ts +137 -0
  105. package/src/skills/scanner.ts +257 -0
  106. package/src/skills/triggers.test.ts +77 -0
  107. package/src/skills/triggers.ts +78 -0
  108. package/src/skills/types.ts +37 -0
  109. package/src/storage/encryption.test.ts +30 -0
  110. package/src/storage/encryption.ts +87 -0
  111. package/src/storage/factory.ts +31 -0
  112. package/src/storage/index.ts +11 -0
  113. package/src/storage/local-stub.ts +70 -0
  114. package/src/storage/sqlite.test.ts +29 -0
  115. package/src/storage/sqlite.ts +95 -0
  116. package/src/storage/types.ts +21 -0
  117. package/src/tools/escalation.test.ts +348 -0
  118. package/src/tools/escalation.ts +200 -0
  119. package/src/tools/index.ts +11 -0
  120. package/src/tools/registry.test.ts +70 -0
  121. package/src/tools/registry.ts +152 -0
  122. package/src/tools/types.ts +65 -0
  123. package/src/tools/zod-helpers.test.ts +63 -0
  124. package/src/tools/zod-helpers.ts +36 -0
  125. package/src/tools/zod-schema.ts +99 -0
  126. package/src/wallet/drain.test.ts +41 -0
  127. package/src/wallet/drain.ts +76 -0
  128. package/src/wallet/eoa.ts +61 -0
  129. package/src/wallet/index.ts +14 -0
  130. package/src/wallet/keystore.test.ts +17 -0
  131. package/src/wallet/keystore.ts +50 -0
@@ -0,0 +1,67 @@
1
+ import matter from 'gray-matter'
2
+
3
+ export interface CardFrontmatter {
4
+ /** Display name, e.g. "Alice". */
5
+ name: string
6
+ /** Short one-line bio, <= 140 chars. */
7
+ bio?: string
8
+ /** Skills / domains the agent is competent in. */
9
+ skills?: string[]
10
+ /** Endpoints the agent exposes (URLs). */
11
+ endpoints?: string[]
12
+ /** Avatar: either a Sui Storage CID or an absolute URL. */
13
+ avatar?: string
14
+ /** Fully-qualified .0g subname, e.g. "alice.lyra.0g". */
15
+ subname?: string
16
+ /** iNFT pointer, CAIP-10-ish: eip155:<chainId>:<contract>:<tokenId> */
17
+ inft?: string
18
+ [key: string]: unknown
19
+ }
20
+
21
+ export interface Card {
22
+ frontmatter: CardFrontmatter
23
+ body: string
24
+ }
25
+
26
+ const DEFAULT_CARD: Card = {
27
+ frontmatter: {
28
+ name: '',
29
+ bio: '',
30
+ skills: [],
31
+ endpoints: [],
32
+ },
33
+ body: '',
34
+ }
35
+
36
+ export function parseCard(markdown: string): Card {
37
+ const parsed = matter(markdown)
38
+ const fm = (parsed.data ?? {}) as CardFrontmatter
39
+ if (typeof fm.name !== 'string') {
40
+ throw new Error('CARD.md requires a "name" frontmatter field')
41
+ }
42
+ return { frontmatter: fm, body: parsed.content ?? '' }
43
+ }
44
+
45
+ export function renderCard(card: Card): string {
46
+ return matter.stringify(card.body, card.frontmatter as Record<string, unknown>)
47
+ }
48
+
49
+ export function emptyCard(): Card {
50
+ return {
51
+ frontmatter: { ...DEFAULT_CARD.frontmatter },
52
+ body: DEFAULT_CARD.body,
53
+ }
54
+ }
55
+
56
+ /** Map a Card to the text-record key/value pairs we publish to .0g. */
57
+ export function cardToTextRecords(card: Card, agentEoa?: string): Record<string, string> {
58
+ const rec: Record<string, string> = {}
59
+ const fm = card.frontmatter
60
+ if (agentEoa) rec.address = agentEoa
61
+ if (fm.bio) rec['agent:bio'] = fm.bio
62
+ if (fm.skills?.length) rec['agent:skills'] = fm.skills.join(',')
63
+ if (fm.endpoints?.length) rec['agent:endpoints'] = fm.endpoints.join(',')
64
+ if (fm.avatar) rec.avatar = fm.avatar
65
+ if (fm.inft) rec['agent:inft'] = fm.inft
66
+ return rec
67
+ }
@@ -0,0 +1,29 @@
1
+ import { appendFile, mkdir } from 'node:fs/promises'
2
+ import { dirname } from 'node:path'
3
+
4
+ export interface ActivityEntry {
5
+ ts: number
6
+ kind:
7
+ | 'wake'
8
+ | 'tool-call'
9
+ | 'tool-result'
10
+ | 'brain-response'
11
+ | 'error'
12
+ | 'context-compacted'
13
+ | 'auto-topup'
14
+ data: unknown
15
+ }
16
+
17
+ export class ActivityLog {
18
+ private dirEnsured = false
19
+
20
+ constructor(private readonly path: string) {}
21
+
22
+ async append(entry: ActivityEntry): Promise<void> {
23
+ if (!this.dirEnsured) {
24
+ await mkdir(dirname(this.path), { recursive: true })
25
+ this.dirEnsured = true
26
+ }
27
+ await appendFile(this.path, `${JSON.stringify(entry)}\n`, 'utf8')
28
+ }
29
+ }
@@ -0,0 +1,7 @@
1
+ export {
2
+ Runtime,
3
+ type RuntimeDeps,
4
+ type AgentIdentity,
5
+ type IdentityProvider,
6
+ } from './runtime'
7
+ export { ActivityLog, type ActivityEntry } from './activity'
@@ -0,0 +1,55 @@
1
+ import { test } from 'bun:test'
2
+ import { mkdtempSync, rmSync } from 'node:fs'
3
+ import { tmpdir } from 'node:os'
4
+ import { join } from 'node:path'
5
+ import { StubBrain } from '../brain/stub'
6
+ import { defineConfig } from '../config'
7
+ import { LocalStubStorage } from '../storage/local-stub'
8
+ import { type IdentityProvider, Runtime } from './runtime'
9
+
10
+ /** Minimal in-memory identity for runtime tests. */
11
+ class StubIdentity implements IdentityProvider {
12
+ constructor(private readonly agentId: string) {}
13
+ current() {
14
+ return { agentId: this.agentId }
15
+ }
16
+ }
17
+
18
+ async function withTempRoot<T>(fn: (root: string) => Promise<T>): Promise<T> {
19
+ const prev = process.env.LYRA_ROOT
20
+ const tmp = mkdtempSync(join(tmpdir(), 'lyra-root-'))
21
+ process.env.LYRA_ROOT = tmp
22
+ try {
23
+ return await fn(tmp)
24
+ } finally {
25
+ process.env.LYRA_ROOT = prev
26
+ rmSync(tmp, { recursive: true, force: true })
27
+ }
28
+ }
29
+
30
+ test('runtime boots, seeds memory dir, routes stub brain echo', async () => {
31
+ await withTempRoot(async root => {
32
+ const agentAddr = `0x${'a'.repeat(64)}`
33
+ const identity = new StubIdentity(agentAddr)
34
+ const brain = new StubBrain()
35
+ const storage = new LocalStubStorage(join(root, 'storage-stub-test'))
36
+
37
+ const runtime = new Runtime({
38
+ config: defineConfig({ network: 'testnet' }),
39
+ identity,
40
+ brain,
41
+ storage,
42
+ })
43
+
44
+ await runtime.start()
45
+
46
+ await runtime.fire({
47
+ source: 'stdin',
48
+ payload: { label: 'hello', data: 'hello world' },
49
+ })
50
+
51
+ await new Promise(r => setTimeout(r, 50))
52
+
53
+ await runtime.stop()
54
+ })
55
+ })
@@ -0,0 +1,126 @@
1
+ import { mkdir } from 'node:fs/promises'
2
+ import type { Brain } from '../brain/types'
3
+ import type { LyraConfig } from '../config'
4
+ import { EventQueue, listeners, newEventId, routeLoop } from '../events'
5
+ import type { LyraEvent } from '../events/types'
6
+ import { addEntryLine, readIndexFile, writeIndexFile } from '../memory/index-file'
7
+ import { agentPaths } from '../paths'
8
+ import type { Storage } from '../storage/types'
9
+ import { ToolRegistry } from '../tools/registry'
10
+ import { type ActivityEntry, ActivityLog } from './activity'
11
+
12
+ /**
13
+ * Minimal agent identity the runtime needs: a stable on-chain id used to root
14
+ * the per-agent filesystem. On Sui this is the agent's address (or iNFT object
15
+ * id). Provided by the gateway/cli at construction time.
16
+ */
17
+ export interface AgentIdentity {
18
+ agentId: string
19
+ }
20
+
21
+ /** Source of the current agent identity. */
22
+ export interface IdentityProvider {
23
+ current(): Promise<AgentIdentity> | AgentIdentity
24
+ }
25
+
26
+ export interface RuntimeDeps {
27
+ config: LyraConfig
28
+ identity: IdentityProvider
29
+ brain: Brain
30
+ storage: Storage
31
+ }
32
+
33
+ export class Runtime {
34
+ readonly queue: EventQueue
35
+ readonly tools: ToolRegistry
36
+ private activity?: ActivityLog
37
+ private running = false
38
+ private routeTask?: Promise<void>
39
+
40
+ constructor(private readonly deps: RuntimeDeps) {
41
+ this.queue = new EventQueue()
42
+ this.tools = new ToolRegistry(deps.config.tools)
43
+ }
44
+
45
+ /** Ensure per-agent filesystem exists and boot the event loop. */
46
+ async start(): Promise<void> {
47
+ if (this.running) return
48
+ const id = (await this.deps.identity.current()).agentId
49
+ const paths = agentPaths.agent(id)
50
+
51
+ await mkdir(paths.memoryDir, { recursive: true })
52
+ await mkdir(paths.agentMemoryDir, { recursive: true })
53
+ await mkdir(paths.userMemoryDir, { recursive: true })
54
+ await mkdir(paths.publicDir, { recursive: true })
55
+ await mkdir(paths.cache, { recursive: true })
56
+
57
+ this.activity = new ActivityLog(paths.activityLog)
58
+
59
+ // Initialize MEMORY.md if missing.
60
+ let index = await readIndexFile(paths.memoryIndex)
61
+ if (index.lines.length === 0) {
62
+ index = {
63
+ lines: [
64
+ `# ${id} — Memory Index`,
65
+ '',
66
+ 'Self-contained memory for this agent. Topic files live under `agent/` (transfers with iNFT) and `user/` (purges on transfer).',
67
+ '',
68
+ '## Memories',
69
+ '',
70
+ ],
71
+ entries: new Map(),
72
+ }
73
+ index = addEntryLine(index, {
74
+ file: 'agent/identity.md',
75
+ title: 'Agent identity',
76
+ hook: 'Seed record of this agent — tokenId, creation block, operator history.',
77
+ })
78
+ await writeIndexFile(paths.memoryIndex, index)
79
+ }
80
+
81
+ this.routeTask = routeLoop(this.queue, {
82
+ brain: this.deps.brain,
83
+ tools: this.tools,
84
+ onTurn: async (ev, turn) => {
85
+ await this.activity?.append({
86
+ ts: Date.now(),
87
+ kind: 'brain-response',
88
+ data: {
89
+ event: { id: ev.id, source: ev.source },
90
+ content: turn.content,
91
+ toolCalls: turn.toolCalls,
92
+ finishReason: turn.finishReason,
93
+ usage: turn.usage,
94
+ },
95
+ })
96
+ },
97
+ })
98
+ this.running = true
99
+
100
+ await listeners.startAll(this.queue)
101
+ }
102
+
103
+ /** Push an event onto the queue from outside the listener system. */
104
+ async fire(event: Omit<LyraEvent, 'id' | 'ts'>): Promise<string> {
105
+ const ev: LyraEvent = { ...event, id: newEventId(), ts: Date.now() }
106
+ await this.activity?.append({
107
+ ts: ev.ts,
108
+ kind: 'wake',
109
+ data: { id: ev.id, source: ev.source, label: ev.payload.label },
110
+ })
111
+ this.queue.enqueue(ev)
112
+ return ev.id
113
+ }
114
+
115
+ async logActivity(entry: ActivityEntry): Promise<void> {
116
+ await this.activity?.append(entry)
117
+ }
118
+
119
+ async stop(): Promise<void> {
120
+ if (!this.running) return
121
+ this.running = false
122
+ this.queue.close()
123
+ await listeners.stopAll()
124
+ await this.routeTask
125
+ }
126
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared credential-dir blocklist used by every sandbox backend (macOS
3
+ * seatbelt, Linux bubblewrap). Centralized so the platforms don't drift —
4
+ * earlier the bwrap profile included `~/.config/anthropic` + `~/.gnupg`
5
+ * while the seatbelt profile didn't. Centralizing closes that gap.
6
+ */
7
+
8
+ /**
9
+ * Cross-platform credential paths to blackhole. Relative to homedir; backends
10
+ * format the absolute path. `Library/Keychains` is macOS-only but keeping it
11
+ * here is harmless on Linux (the path won't exist; `--tmpfs` no-ops).
12
+ */
13
+ export const CREDENTIAL_DIR_RELATIVE_PATHS: readonly string[] = [
14
+ '.ssh',
15
+ '.aws',
16
+ 'Library/Keychains',
17
+ '.config/gcloud',
18
+ '.config/anthropic', // claude-code config
19
+ '.gnupg',
20
+ ] as const
21
+
22
+ /** Build the absolute paths of credential dirs to deny under `homedir`. */
23
+ export function credentialDirs(homedir: string): string[] {
24
+ return CREDENTIAL_DIR_RELATIVE_PATHS.map(rel => `${homedir}/${rel}`)
25
+ }