@xingular/harness 0.0.0-stage → 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.
- package/README.md +269 -2
- package/dist/cli.js +11046 -0
- package/dist/terminal-pi.js +326 -0
- package/package.json +41 -4
- package/src/accounts/accounts.test.ts +223 -0
- package/src/accounts/accounts.ts +522 -0
- package/src/accounts/env.ts +204 -0
- package/src/accounts/secrets.ts +50 -0
- package/src/accounts/store.ts +338 -0
- package/src/capabilities/api.ts +75 -0
- package/src/capabilities/runSnapshot.ts +59 -0
- package/src/capabilities/types.ts +34 -0
- package/src/cli.ts +110 -0
- package/src/core/bus.ts +34 -0
- package/src/core/changes.ts +33 -0
- package/src/core/env.ts +27 -0
- package/src/core/errors.ts +32 -0
- package/src/core/frontmatter.ts +47 -0
- package/src/core/fs.test.ts +38 -0
- package/src/core/fs.ts +92 -0
- package/src/core/index.ts +193 -0
- package/src/core/path.ts +45 -0
- package/src/core/projects.ts +90 -0
- package/src/core/text.ts +39 -0
- package/src/harness.ts +289 -0
- package/src/index.ts +69 -0
- package/src/mcp/connections.ts +441 -0
- package/src/mcp/manager.ts +130 -0
- package/src/mcp/oauth.ts +195 -0
- package/src/mcp/service.test.ts +376 -0
- package/src/mcp/service.ts +567 -0
- package/src/mcp/types.ts +105 -0
- package/src/memory/api.test.ts +82 -0
- package/src/memory/api.ts +287 -0
- package/src/memory/blocks.test.ts +78 -0
- package/src/memory/blocks.ts +406 -0
- package/src/memory/caller.ts +57 -0
- package/src/memory/dispatch.ts +59 -0
- package/src/memory/facts.test.ts +41 -0
- package/src/memory/facts.ts +248 -0
- package/src/memory/graph.ts +103 -0
- package/src/memory/health.test.ts +130 -0
- package/src/memory/health.ts +74 -0
- package/src/memory/maintainer.ts +272 -0
- package/src/memory/maintenance.test.ts +176 -0
- package/src/memory/maintenance.ts +330 -0
- package/src/memory/migrate.ts +79 -0
- package/src/memory/operations.ts +138 -0
- package/src/memory/proposals.test.ts +168 -0
- package/src/memory/proposals.ts +576 -0
- package/src/memory/recall.test.ts +104 -0
- package/src/memory/recall.ts +271 -0
- package/src/memory/references.ts +77 -0
- package/src/memory/runContext.ts +93 -0
- package/src/memory/secrets.ts +6 -0
- package/src/memory/store.test.ts +66 -0
- package/src/memory/store.ts +385 -0
- package/src/memory/wiki.test.ts +47 -0
- package/src/memory/wiki.ts +422 -0
- package/src/memory/wikiIndex.test.ts +88 -0
- package/src/memory/wikiIndex.ts +116 -0
- package/src/plugins/api.ts +181 -0
- package/src/plugins/attachments.test.ts +84 -0
- package/src/plugins/attachments.ts +128 -0
- package/src/plugins/builtin.ts +82 -0
- package/src/plugins/catalogue.test.ts +254 -0
- package/src/plugins/catalogue.ts +250 -0
- package/src/plugins/cli.ts +184 -0
- package/src/plugins/discover.ts +151 -0
- package/src/plugins/firstParty.test.ts +143 -0
- package/src/plugins/firstParty.ts +129 -0
- package/src/plugins/hooks.test.ts +250 -0
- package/src/plugins/hooks.ts +239 -0
- package/src/plugins/identity.ts +37 -0
- package/src/plugins/install.test.ts +107 -0
- package/src/plugins/install.ts +106 -0
- package/src/plugins/inventory.ts +123 -0
- package/src/plugins/manifest.ts +44 -0
- package/src/plugins/plugins.test.ts +205 -0
- package/src/plugins/plugins.ts +184 -0
- package/src/plugins/proposal.ts +121 -0
- package/src/plugins/runtime.test.ts +40 -0
- package/src/plugins/runtime.ts +82 -0
- package/src/plugins/trust.ts +142 -0
- package/src/plugins/types.ts +130 -0
- package/src/prompt/compile.test.ts +261 -0
- package/src/prompt/compile.ts +732 -0
- package/src/server/methods.ts +308 -0
- package/src/server/remote.ts +77 -0
- package/src/server/rpc.ts +116 -0
- package/src/server/serve.ts +28 -0
- package/src/server/server.test.ts +174 -0
- package/src/sessions/approvals.ts +118 -0
- package/src/sessions/contracts.ts +299 -0
- package/src/sessions/conversation.test.ts +150 -0
- package/src/sessions/extensionSession.test.ts +74 -0
- package/src/sessions/extensionUi.test.ts +65 -0
- package/src/sessions/extensionUi.ts +102 -0
- package/src/sessions/history.ts +51 -0
- package/src/sessions/lineageJoin.ts +75 -0
- package/src/sessions/policy.ts +32 -0
- package/src/sessions/pricing.ts +60 -0
- package/src/sessions/reasoningSummaries.ts +59 -0
- package/src/sessions/registry.ts +294 -0
- package/src/sessions/runIndex.ts +99 -0
- package/src/sessions/runStore.test.ts +152 -0
- package/src/sessions/runStore.ts +332 -0
- package/src/sessions/runner.ts +556 -0
- package/src/sessions/runtimeResources.test.ts +61 -0
- package/src/sessions/sessions.test.ts +434 -0
- package/src/sessions/sessions.ts +295 -0
- package/src/skills/registry.test.ts +104 -0
- package/src/skills/registry.ts +394 -0
- package/src/terminal/cli.ts +149 -0
- package/src/terminal/client.ts +74 -0
- package/src/terminal/controller.ts +227 -0
- package/src/terminal/piExtension.ts +208 -0
- package/src/terminal/pluginControl.test.ts +146 -0
- package/src/terminal/pluginMenu.ts +112 -0
- package/src/terminal/socket.ts +182 -0
- package/src/terminal/standalone.test.ts +115 -0
- package/src/terminal/standalone.ts +126 -0
- package/src/terminal/terminal.test.ts +302 -0
- package/src/terminal/terminals.ts +429 -0
- package/src/tools/askUser.ts +45 -0
- package/src/tools/definitions.ts +187 -0
- package/src/tools/memoryTools.ts +22 -0
- package/src/tools/pluginPropose.ts +41 -0
- package/src/tools/types.ts +36 -0
- package/src/web/safeRequest.ts +171 -0
- package/src/web/text.ts +53 -0
- package/src/web/tools.ts +134 -0
- package/src/web/web.test.ts +141 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
import { expect, test } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { dataRoot } from '../../tests/helpers'
|
|
6
|
+
import * as blocks from './blocks'
|
|
7
|
+
import { findMemory } from './facts'
|
|
8
|
+
import { listInbox, undo } from './operations'
|
|
9
|
+
import { accept, apply, captureHashes, parkRegistryProposal, parseProposalSet, quotedIn, type Evidence, type Trigger } from './proposals'
|
|
10
|
+
import { readManifest, wikiDir } from './store'
|
|
11
|
+
|
|
12
|
+
function setup(): { root: string; wiki: string } {
|
|
13
|
+
const root = dataRoot()
|
|
14
|
+
return { root, wiki: wikiDir(root) }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const proposal = (id: string, refs: [string, string][]) => ({
|
|
18
|
+
id,
|
|
19
|
+
title: `Title ${id}`,
|
|
20
|
+
type: 'decision',
|
|
21
|
+
body: `Body of ${id}.`,
|
|
22
|
+
sourceRefs: refs.map(([kind, ref]) => ({ kind, ref, excerpt: 'we decided to use Tauri' }))
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const memoryProposal = (id: string, confidence: string) => ({
|
|
26
|
+
id,
|
|
27
|
+
title: `Memory ${id}`,
|
|
28
|
+
kind: 'preference',
|
|
29
|
+
body: 'Prefer small commits.',
|
|
30
|
+
confidence,
|
|
31
|
+
sourceRefs: [{ kind: 'run', ref: 'run-1', excerpt: 'prefer small commits' as string | undefined }]
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
/** What run-1 recorded: the text excerpts are checked against. */
|
|
35
|
+
const ev = (): Evidence => ({ runText: 'User: which shell?\nAssistant: We decided to use Tauri. I prefer small commits.\n' })
|
|
36
|
+
const agent = (): Trigger => ({ kind: 'agent', runId: 'run-1', actor: 'Chat' })
|
|
37
|
+
const set = (value: unknown) => parseProposalSet(value)
|
|
38
|
+
const inboxItems = (wiki: string) => listInbox(wiki).sort((left, right) => (left.kind < right.kind ? -1 : left.kind > right.kind ? 1 : 0))
|
|
39
|
+
|
|
40
|
+
test('applies cited pages into the namespace and parks uncited ones', () => {
|
|
41
|
+
const { wiki } = setup()
|
|
42
|
+
const withKey = { ...proposal('with-key', [['run', 'run-1']]), body: 'token sk-ant-abc123' }
|
|
43
|
+
const proposals = set({ pages: [proposal('use-tauri', [['run', 'run-1']]), proposal('rumor', [['run', 'other-run']]), withKey], flags: [{ claim: 'x', kind: 'uncited', reason: 'none' }] })
|
|
44
|
+
const outcome = apply(wiki, '/projects/app', agent(), proposals, new Map(), ev())
|
|
45
|
+
const operation = outcome.operation!
|
|
46
|
+
expect(operation.pages).toHaveLength(1)
|
|
47
|
+
expect(outcome.parked).toBe(2)
|
|
48
|
+
expect(readFileSync(join(wiki, 'projects/app/use-tauri.md'), 'utf8')).toContain('run:run-1')
|
|
49
|
+
expect(readFileSync(join(wiki, 'index.md'), 'utf8')).toContain('[/projects/app](projects/app/index.md) — 1 page')
|
|
50
|
+
expect(readFileSync(join(wiki, 'projects/app/index.md'), 'utf8')).toContain('- [Title use-tauri](use-tauri.md) (decision, `use-tauri`)')
|
|
51
|
+
expect(readFileSync(join(wiki, 'log.md'), 'utf8')).toContain(operation.id)
|
|
52
|
+
const items = inboxItems(wiki)
|
|
53
|
+
expect(items.map((item) => item.kind)).toEqual(['applied', 'blocked', 'unresolved', 'unresolved'])
|
|
54
|
+
// The parked uncited page can be accepted; the credential one carries no payload.
|
|
55
|
+
expect(items.some((item) => item.kind === 'unresolved' && item.payload !== undefined)).toBe(true)
|
|
56
|
+
expect(items.every((item) => item.kind !== 'blocked' || item.payload === undefined)).toBe(true)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test('memories apply with evidence and low confidence waits for acceptance', () => {
|
|
60
|
+
const { root, wiki } = setup()
|
|
61
|
+
const outcome = apply(wiki, '/projects/app', { kind: 'agent', runId: 'run-1', actor: 'Code' }, set({ memories: [memoryProposal('small-commits', 'high'), memoryProposal('maybe', 'low')] }), new Map(), ev())
|
|
62
|
+
expect(outcome.operation!.pages).toHaveLength(1)
|
|
63
|
+
const saved = findMemory(wiki, 'small-commits')
|
|
64
|
+
expect(saved.front.namespace).toBe('/projects/app')
|
|
65
|
+
expect(saved.front.sourceIds).toEqual(['run:run-1'])
|
|
66
|
+
expect(readManifest(wiki, 'small-commits')[0].actor).toBe('Code')
|
|
67
|
+
const parked = listInbox(wiki).find((item) => item.payload !== undefined)!
|
|
68
|
+
accept(root, parked.id)
|
|
69
|
+
expect(findMemory(wiki, 'maybe').front.sourceIds).toContain('user:accepted')
|
|
70
|
+
expect(() => accept(root, parked.id)).toThrow()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test('superseding memory retires the old one', () => {
|
|
74
|
+
const { wiki } = setup()
|
|
75
|
+
const trigger: Trigger = { kind: 'run', runId: 'run-1' }
|
|
76
|
+
apply(wiki, '/library', trigger, set({ memories: [memoryProposal('old', 'high')] }), new Map(), ev())
|
|
77
|
+
apply(wiki, '/library', trigger, set({ memories: [{ ...memoryProposal('new', 'high'), supersedes: 'old' }] }), captureHashes(wiki), ev())
|
|
78
|
+
expect(findMemory(wiki, 'old').front.status).toBe('superseded')
|
|
79
|
+
expect(findMemory(wiki, 'new').front.supersedes).toBe('old')
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test('registry proposals always wait and accept writes a revision', () => {
|
|
83
|
+
const { root, wiki } = setup()
|
|
84
|
+
const id = parkRegistryProposal(wiki, { kind: 'agent', runId: null, actor: 'Chat' }, { kind: 'skill', name: 'Release notes', description: 'Write release notes', body: 'Steps.', reason: '' })
|
|
85
|
+
expect(id).toBe('skills/release-notes')
|
|
86
|
+
expect(existsSync(join(root, 'registry/skills/release-notes'))).toBe(false)
|
|
87
|
+
accept(root, listInbox(wiki).find((item) => item.kind === 'proposed')!.id)
|
|
88
|
+
const text = readFileSync(join(root, 'registry/skills/release-notes/SKILL.md'), 'utf8')
|
|
89
|
+
expect(text).toContain('Write release notes')
|
|
90
|
+
expect(text).toContain('Steps.')
|
|
91
|
+
expect(readManifest(wiki, 'skills/release-notes')).toHaveLength(1)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('never overwrites a page edited during the job', () => {
|
|
95
|
+
const { wiki } = setup()
|
|
96
|
+
apply(wiki, '/library', agent(), set({ pages: [proposal('p', [['run', 'run-1']])] }), new Map(), ev())
|
|
97
|
+
const captured = captureHashes(wiki)
|
|
98
|
+
const path = join(wiki, 'library/p.md')
|
|
99
|
+
appendFileSync(path, '\nUser edit.\n')
|
|
100
|
+
const outcome = apply(wiki, '/library', agent(), set({ pages: [proposal('p', [['run', 'run-1']])] }), captured, ev())
|
|
101
|
+
expect(outcome.operation).toBeNull()
|
|
102
|
+
expect(readFileSync(path, 'utf8')).toContain('User edit.')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('undo restores through new revisions', () => {
|
|
106
|
+
const { wiki } = setup()
|
|
107
|
+
apply(wiki, '/library', agent(), set({ pages: [proposal('p', [['run', 'run-1']])] }), new Map(), ev())
|
|
108
|
+
const path = join(wiki, 'library/p.md')
|
|
109
|
+
const original = readFileSync(path, 'utf8')
|
|
110
|
+
const second = apply(wiki, '/library', agent(), set({ pages: [{ ...proposal('p', [['run', 'run-1']]), body: 'Changed body.' }] }), captureHashes(wiki), ev()).operation!
|
|
111
|
+
const revisionsBefore = readManifest(wiki, 'p').length
|
|
112
|
+
undo(wiki, second.id)
|
|
113
|
+
expect(readFileSync(path, 'utf8')).toBe(original)
|
|
114
|
+
expect(readManifest(wiki, 'p').length).toBeGreaterThan(revisionsBefore)
|
|
115
|
+
expect(() => undo(wiki, second.id)).toThrow()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
test('a run reference counts only with an excerpt found in the run', () => {
|
|
119
|
+
expect(quotedIn('We decided to use\n Tauri.', 'decided to use tauri')).toBe(true)
|
|
120
|
+
expect(quotedIn('We decided to use Tauri.', 'Tauri')).toBe(false)
|
|
121
|
+
expect(quotedIn('We decided to use Tauri.', 'decided to use Electron')).toBe(false)
|
|
122
|
+
expect(quotedIn(null, 'decided to use tauri')).toBe(false)
|
|
123
|
+
const { wiki } = setup()
|
|
124
|
+
const bare = { ...memoryProposal('bare', 'high'), sourceRefs: [{ kind: 'run', ref: 'run-1' }] }
|
|
125
|
+
const invented = { ...memoryProposal('invented', 'high'), sourceRefs: [{ kind: 'run', ref: 'run-1', excerpt: 'the user loves large commits' }] }
|
|
126
|
+
const outcome = apply(wiki, '/library', agent(), set({ memories: [bare, invented, memoryProposal('quoted', 'high')] }), new Map(), ev())
|
|
127
|
+
expect(outcome.parked).toBe(2)
|
|
128
|
+
expect(findMemory(wiki, 'quoted').front.id).toBe('quoted')
|
|
129
|
+
expect(() => findMemory(wiki, 'bare')).toThrow()
|
|
130
|
+
expect(() => findMemory(wiki, 'invented')).toThrow()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('consolidating a run updates pages and blocks but creates no page', () => {
|
|
134
|
+
const { wiki } = setup()
|
|
135
|
+
apply(wiki, '/library', agent(), set({ pages: [proposal('shell', [['run', 'run-1']])] }), new Map(), ev())
|
|
136
|
+
const user = blocks.ensureUser(wiki)
|
|
137
|
+
const notes = blocks.ensureAgent(wiki, 'a1')[1]
|
|
138
|
+
const quoted = (block: string, body: string, excerpt: string) => ({ block, body, sourceRefs: [{ kind: 'run', ref: 'run-1', excerpt }] })
|
|
139
|
+
const proposals = set({
|
|
140
|
+
pages: [{ ...proposal('shell', [['run', 'run-1']]), body: 'We use Tauri.' }, proposal('observed-in-run', [['run', 'run-1']])],
|
|
141
|
+
blocks: [quoted(user.id, 'Prefers small commits.', 'I prefer small commits'), quoted(user.id, 'Made up.', 'something never said at all'), quoted(notes.id, 'Not attached.', 'I prefer small commits')]
|
|
142
|
+
})
|
|
143
|
+
const outcome = apply(wiki, '/library', { kind: 'run', runId: 'run-1' }, proposals, captureHashes(wiki), { ...ev(), blocks: [user.id] })
|
|
144
|
+
const operation = outcome.operation!
|
|
145
|
+
expect(operation.pages.map((page) => page.pageId)).toEqual(['shell', 'blocks/shared/user'])
|
|
146
|
+
expect(outcome.parked).toBe(3)
|
|
147
|
+
expect(existsSync(join(wiki, 'library/observed-in-run.md'))).toBe(false)
|
|
148
|
+
expect(blocks.find(wiki, user.id).body).toBe('Prefers small commits.')
|
|
149
|
+
expect(readManifest(wiki, user.id).at(-1)!.actor).toBe('Sleep-time')
|
|
150
|
+
expect(blocks.find(wiki, notes.id).body).toBe('')
|
|
151
|
+
// The whole consolidation undoes as one operation.
|
|
152
|
+
undo(wiki, operation.id)
|
|
153
|
+
expect(blocks.find(wiki, user.id).body).toBe('')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('idle consolidation only shortens a block', () => {
|
|
157
|
+
const { wiki } = setup()
|
|
158
|
+
const user = blocks.ensureUser(wiki)
|
|
159
|
+
writeFileSync(user.path, `${readFileSync(user.path, 'utf8')}Likes pnpm. Likes pnpm a lot. Uses pnpm everywhere.\n`)
|
|
160
|
+
const evidence: Evidence = { blocks: [user.id] }
|
|
161
|
+
const rewrite = (body: string) => set({ blocks: [{ block: user.id, body }] })
|
|
162
|
+
const longer = apply(wiki, '/library', { kind: 'consolidate' }, rewrite('Likes pnpm. Likes pnpm a lot. Uses pnpm everywhere. Also loves yarn and npm.'), new Map(), evidence)
|
|
163
|
+
expect(longer.operation).toBeNull()
|
|
164
|
+
expect(longer.parked).toBe(1)
|
|
165
|
+
const shorter = apply(wiki, '/library', { kind: 'consolidate' }, rewrite('Uses pnpm everywhere.'), new Map(), evidence)
|
|
166
|
+
expect(shorter.operation).not.toBeNull()
|
|
167
|
+
expect(blocks.find(wiki, user.id).body).toBe('Uses pnpm everywhere.')
|
|
168
|
+
})
|