@xingular/harness 0.0.0-stage → 0.1.1
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 +11510 -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.test.ts +95 -0
- package/src/accounts/env.ts +338 -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 +299 -0
- package/src/index.ts +70 -0
- package/src/mcp/connections.ts +614 -0
- package/src/mcp/manager.ts +130 -0
- package/src/mcp/oauth.ts +195 -0
- package/src/mcp/scopeMigration.ts +204 -0
- package/src/mcp/service.test.ts +376 -0
- package/src/mcp/service.ts +549 -0
- package/src/mcp/types.ts +108 -0
- package/src/memory/api.test.ts +82 -0
- package/src/memory/api.ts +289 -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 +141 -0
- package/src/plugins/builtin.ts +82 -0
- package/src/plugins/catalogue.test.ts +254 -0
- package/src/plugins/catalogue.ts +251 -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 +76 -0
- package/src/plugins/plugins.test.ts +205 -0
- package/src/plugins/plugins.ts +189 -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 +149 -0
- package/src/prompt/compile.test.ts +261 -0
- package/src/prompt/compile.ts +732 -0
- package/src/server/methods.ts +314 -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 +407 -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
package/src/harness.ts
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
// The harness, assembled: Pi sessions on isolated accounts, a compiled prompt, a memory
|
|
2
|
+
// lifecycle, tools, connected apps, and skills. An application embeds one of these and adds
|
|
3
|
+
// its own prompt sections, tools, and policy; nothing here knows what that application is.
|
|
4
|
+
import { mkdirSync } from 'node:fs'
|
|
5
|
+
import { join } from 'node:path'
|
|
6
|
+
|
|
7
|
+
import { Accounts } from './accounts/accounts'
|
|
8
|
+
import * as capabilities from './capabilities/api'
|
|
9
|
+
import { ensureSharedDir, isolateProcess } from './accounts/env'
|
|
10
|
+
import { fileSecretStore, type SecretStore } from './accounts/secrets'
|
|
11
|
+
import { Bus, log } from './core/bus'
|
|
12
|
+
import { DEFAULT_PRODUCT_NAME, EMPTY_DIRECTORY, type Directory, type HarnessEnv } from './core/env'
|
|
13
|
+
import { HarnessError } from './core/errors'
|
|
14
|
+
import { readJsonOr } from './core/fs'
|
|
15
|
+
import { closeIndex } from './core/index'
|
|
16
|
+
import { projectsOrNone } from './core/projects'
|
|
17
|
+
import { ConnectedApps } from './mcp/service'
|
|
18
|
+
import { migrateConnectionScopes } from './mcp/scopeMigration'
|
|
19
|
+
import type { MaintainerFactory } from './memory/maintainer'
|
|
20
|
+
import { Maintenance } from './memory/maintenance'
|
|
21
|
+
import * as migrate from './memory/migrate'
|
|
22
|
+
import { writeIndex } from './memory/proposals'
|
|
23
|
+
import * as recall from './memory/recall'
|
|
24
|
+
import * as store from './memory/store'
|
|
25
|
+
import { countPages } from './memory/wiki'
|
|
26
|
+
import * as wikiIndex from './memory/wikiIndex'
|
|
27
|
+
import * as pluginApi from './plugins/api'
|
|
28
|
+
import * as attachments from './plugins/attachments'
|
|
29
|
+
import { HARNESS_PLUGINS } from './plugins/builtin'
|
|
30
|
+
import type { Resolver } from './plugins/discover'
|
|
31
|
+
import { Hooks } from './plugins/hooks'
|
|
32
|
+
import { packageFolders, Plugins } from './plugins/plugins'
|
|
33
|
+
import type { PluginDescriptor } from './plugins/types'
|
|
34
|
+
import { approvalKey, Approvals } from './sessions/approvals'
|
|
35
|
+
import type { ToolPolicy } from './sessions/policy'
|
|
36
|
+
import { RunRegistry } from './sessions/registry'
|
|
37
|
+
import { markInterruptedRuns } from './sessions/runStore'
|
|
38
|
+
import type { SessionFactory } from './sessions/runner'
|
|
39
|
+
import { Sessions } from './sessions/sessions'
|
|
40
|
+
import * as registry from './skills/registry'
|
|
41
|
+
import { Terminals, type TerminalAssets } from './terminal/terminals'
|
|
42
|
+
|
|
43
|
+
export interface HarnessOptions {
|
|
44
|
+
/** Where knowledge and history live. May be set later with `open`. */
|
|
45
|
+
dataRoot?: string | null
|
|
46
|
+
/** Device-local state that never belongs in the data root: accounts, approvals, connections, sockets. */
|
|
47
|
+
stateDir: string
|
|
48
|
+
/** Where credentials are kept. Defaults to an owner-only file in the state directory. */
|
|
49
|
+
secrets?: SecretStore
|
|
50
|
+
/** How the harness names itself to a model. */
|
|
51
|
+
productName?: string
|
|
52
|
+
directory?: Directory
|
|
53
|
+
policy?: ToolPolicy
|
|
54
|
+
/** Replaces Pi, for tests. */
|
|
55
|
+
sessionFactory?: SessionFactory
|
|
56
|
+
maintainerFactory?: MaintainerFactory
|
|
57
|
+
/** How long the maintenance queue stays empty before full memory blocks are condensed. */
|
|
58
|
+
idleMs?: number
|
|
59
|
+
/** Where the built commands a terminal session runs are, when they are not beside this package. */
|
|
60
|
+
assets?: TerminalAssets
|
|
61
|
+
/** The `pi` executable a terminal session runs. Defaults to the one on the search path. */
|
|
62
|
+
piProgram?: () => string | null
|
|
63
|
+
/** Folders of built-in skills, prompts, and rules, each laid out like `registry/`. */
|
|
64
|
+
builtins?: string[]
|
|
65
|
+
/**
|
|
66
|
+
* The application's own parts, described as plugins: which tools, prompt sections and tool
|
|
67
|
+
* groups each one provides. Data only; it changes what is listed, never what runs.
|
|
68
|
+
*/
|
|
69
|
+
plugins?: PluginDescriptor[]
|
|
70
|
+
/** Replaces Pi's own search for extensions, skills, prompts and themes, for tests. */
|
|
71
|
+
resolver?: Resolver
|
|
72
|
+
/** Replaces how a plugin's hooks file is read as a module, for tests. */
|
|
73
|
+
hookImporter?: (address: string) => Promise<unknown>
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** What starting on a data root found and repaired. */
|
|
77
|
+
export interface Recovery {
|
|
78
|
+
interruptedRuns: number
|
|
79
|
+
reindexedPages: number
|
|
80
|
+
/** Set when the storage migration failed; the old data is left as it was. */
|
|
81
|
+
migrationError: string | null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class Env implements HarnessEnv {
|
|
85
|
+
dataRoot: string | null
|
|
86
|
+
readonly bus: Bus
|
|
87
|
+
|
|
88
|
+
constructor(
|
|
89
|
+
dataRoot: string | null,
|
|
90
|
+
readonly directory: Directory,
|
|
91
|
+
readonly productName: string,
|
|
92
|
+
bus: Bus
|
|
93
|
+
) {
|
|
94
|
+
this.dataRoot = dataRoot
|
|
95
|
+
this.bus = bus
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get root(): string {
|
|
99
|
+
if (!this.dataRoot) throw new HarnessError('data_root_missing', 'Choose a data location first.', 'Open Settings → Projects and Storage.')
|
|
100
|
+
return this.dataRoot
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export class Harness {
|
|
105
|
+
readonly bus = new Bus()
|
|
106
|
+
readonly env: Env
|
|
107
|
+
readonly stateDir: string
|
|
108
|
+
readonly policy: ToolPolicy
|
|
109
|
+
readonly accounts: Accounts
|
|
110
|
+
readonly approvals: Approvals
|
|
111
|
+
readonly runs: RunRegistry
|
|
112
|
+
readonly apps: ConnectedApps
|
|
113
|
+
readonly plugins: Plugins
|
|
114
|
+
/** Code plugins gave the harness to run after memory, the Wiki or history changed. */
|
|
115
|
+
readonly hooks: Hooks
|
|
116
|
+
/** Every part described as a plugin: the harness's own, then the application's. */
|
|
117
|
+
descriptors: readonly PluginDescriptor[] = HARNESS_PLUGINS
|
|
118
|
+
applicationPlugins: ReadonlySet<string> = new Set()
|
|
119
|
+
readonly sessions: Sessions
|
|
120
|
+
readonly maintenance: Maintenance
|
|
121
|
+
readonly terminals: Terminals
|
|
122
|
+
/** Resolves when what was loaded in the background at construction is in place. */
|
|
123
|
+
readonly ready: Promise<void>
|
|
124
|
+
recovery: Recovery = { interruptedRuns: 0, reindexedPages: 0, migrationError: null }
|
|
125
|
+
|
|
126
|
+
constructor(options: HarnessOptions) {
|
|
127
|
+
this.stateDir = options.stateDir
|
|
128
|
+
mkdirSync(this.stateDir, { recursive: true })
|
|
129
|
+
// Before anything can start a session: no inherited credential, no `~/.pi`.
|
|
130
|
+
isolateProcess(join(this.stateDir, 'accounts'))
|
|
131
|
+
this.env = new Env(options.dataRoot || null, options.directory ?? EMPTY_DIRECTORY, options.productName ?? DEFAULT_PRODUCT_NAME, this.bus)
|
|
132
|
+
this.policy = options.policy ?? {}
|
|
133
|
+
registry.configureBuiltins(options.builtins ?? [])
|
|
134
|
+
const secrets = options.secrets ?? fileSecretStore(join(this.stateDir, 'secrets.json'))
|
|
135
|
+
this.accounts = new Accounts({ dir: join(this.stateDir, 'accounts'), secrets, bus: this.bus })
|
|
136
|
+
this.approvals = new Approvals(this.stateDir, this.bus)
|
|
137
|
+
this.runs = new RunRegistry({
|
|
138
|
+
root: () => this.env.root,
|
|
139
|
+
automatic: async (run, target, operation, standing) => {
|
|
140
|
+
if (standing === 'switch') return this.approvals.isAuto(approvalKey(run)) ? 'always' : null
|
|
141
|
+
return this.approvals.automatic(run, target, (await this.policy.approval?.({ run, target, operation })) ?? {})
|
|
142
|
+
},
|
|
143
|
+
redact: (event) => this.apps.connections.redactEvent(event),
|
|
144
|
+
ended: (runId, sessionId) => this.apps.revokeRun(runId, sessionId)
|
|
145
|
+
})
|
|
146
|
+
this.apps = new ConnectedApps({ env: this.env, stateDir: this.stateDir, secrets, bus: this.bus, runs: this.runs, policy: this.policy })
|
|
147
|
+
this.plugins = new Plugins({ stateDir: this.stateDir, bus: this.bus, resolver: options.resolver, attachments: () => (this.env.dataRoot ? attachments.load(this.env.dataRoot) : null), packagesDir: () => (this.env.dataRoot ? join(this.env.dataRoot, 'plugins') : null) })
|
|
148
|
+
this.hooks = new Hooks({
|
|
149
|
+
stateDir: this.stateDir,
|
|
150
|
+
bus: this.bus,
|
|
151
|
+
// A hook runs for everyone or not at all, so only what is on everywhere and trusted counts.
|
|
152
|
+
// The shared folder stands in for an account: packages are the same for all of them.
|
|
153
|
+
wanted: async () => {
|
|
154
|
+
if (!this.env.dataRoot) return []
|
|
155
|
+
const shared = ensureSharedDir(join(this.stateDir, 'accounts'))
|
|
156
|
+
// A hook comes only with a package: one kept beside the data, or one the shared settings
|
|
157
|
+
// name. With neither there is nothing to look for, and Pi is not loaded to find that out.
|
|
158
|
+
const named = readJsonOr<{ packages?: unknown }>(join(shared, 'settings.json'), {}).packages
|
|
159
|
+
if (!packageFolders(join(this.env.dataRoot, 'plugins')).length && !(Array.isArray(named) && named.length)) return []
|
|
160
|
+
return (await this.plugins.loading({ cwd: this.stateDir, agentDir: shared, caller: {} })).hooks
|
|
161
|
+
},
|
|
162
|
+
call: (connection, tool, args) => this.apps.callUnattended(connection, tool, args ?? {}),
|
|
163
|
+
importFile: options.hookImporter
|
|
164
|
+
})
|
|
165
|
+
let reloadTimer: ReturnType<typeof setTimeout> | null = null
|
|
166
|
+
this.bus.on((notice) => {
|
|
167
|
+
if (notice.type !== 'plugins.changed' || reloadTimer) return
|
|
168
|
+
// Several changes arrive together when a plugin is installed or switched; one reload covers them.
|
|
169
|
+
reloadTimer = setTimeout(() => {
|
|
170
|
+
reloadTimer = null
|
|
171
|
+
void this.hooks.reload()
|
|
172
|
+
}, 50)
|
|
173
|
+
reloadTimer.unref?.()
|
|
174
|
+
})
|
|
175
|
+
this.describe(options.plugins ?? [])
|
|
176
|
+
this.sessions = new Sessions({
|
|
177
|
+
env: this.env,
|
|
178
|
+
accounts: this.accounts,
|
|
179
|
+
runs: this.runs,
|
|
180
|
+
policy: this.policy,
|
|
181
|
+
broker: this.apps,
|
|
182
|
+
plugins: this.plugins,
|
|
183
|
+
resolveCapabilities: (caller, tools, cwd) => capabilities.resolve(this, { agentId: caller.agentId ?? null, projectId: caller.projectId ?? null, cwd, tools }),
|
|
184
|
+
factory: options.sessionFactory
|
|
185
|
+
})
|
|
186
|
+
this.maintenance = new Maintenance({ env: this.env, bus: this.bus, accounts: this.accounts, factory: options.maintainerFactory, idleMs: options.idleMs })
|
|
187
|
+
this.terminals = new Terminals({ env: this.env, stateDir: this.stateDir, accounts: this.accounts, runs: this.runs, apps: this.apps, policy: this.policy, bus: this.bus, plugins: this.plugins, pluginControl: { list: (scope) => pluginApi.list(this, scope), attach: (params, on) => pluginApi.attach(this, params, on), trust: (scope) => pluginApi.trust(this, scope), untrust: (scope) => pluginApi.untrust(this, scope) }, assets: options.assets, program: options.piProgram })
|
|
188
|
+
this.ready = this.apps.start().catch((error) => log('connected apps', error instanceof Error ? error.message : String(error)))
|
|
189
|
+
this.runs.listen((event, run) => {
|
|
190
|
+
this.accounts.onRunEvent(event, run)
|
|
191
|
+
// Each run is indexed when it ends, so the next turn can already recall it.
|
|
192
|
+
if (event.type === 'completed' || event.type === 'failed') recall.indexRun(this.env.root, event.runId)
|
|
193
|
+
if ((event.type === 'completed' || event.type === 'failed') && run) {
|
|
194
|
+
this.hooks.emit({ type: 'history.run.ended', runId: run.id, sessionId: run.sessionId, status: run.status, goal: run.goal ?? null, agentId: run.agentId ?? null, projectId: run.projectId ?? null, startedAt: run.startedAt, endedAt: run.endedAt ?? null, at: event.timestamp })
|
|
195
|
+
}
|
|
196
|
+
this.maintenance.onRunEvent(event)
|
|
197
|
+
})
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
get dataRoot(): string | null {
|
|
201
|
+
return this.env.dataRoot
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Sets the application's own parts, described as plugins. One with a harness plugin's id is ignored. */
|
|
205
|
+
describe(plugins: readonly PluginDescriptor[]): void {
|
|
206
|
+
const own = new Set(HARNESS_PLUGINS.map((plugin) => plugin.id))
|
|
207
|
+
const supplied = plugins.filter((plugin) => plugin && typeof plugin.id === 'string' && !own.has(plugin.id))
|
|
208
|
+
this.descriptors = [...HARNESS_PLUGINS, ...supplied]
|
|
209
|
+
this.applicationPlugins = new Set(supplied.map((plugin) => plugin.id))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Opens a data root: brings its Wiki to the current layout, settles runs that never ended,
|
|
214
|
+
* and refreshes the index. `agentIds` are the agents that get their own memory blocks when an
|
|
215
|
+
* older Wiki is migrated.
|
|
216
|
+
*/
|
|
217
|
+
open(dataRoot: string, agentIds: readonly string[] = []): Recovery {
|
|
218
|
+
if (this.env.dataRoot && this.env.dataRoot !== dataRoot) closeIndex(this.env.dataRoot)
|
|
219
|
+
this.env.dataRoot = dataRoot
|
|
220
|
+
const root = dataRoot
|
|
221
|
+
this.hooks.watch(store.wikiDir(root))
|
|
222
|
+
void this.hooks.reload()
|
|
223
|
+
for (const folder of ['registry/skills', 'registry/prompts', 'registry/rules', 'config', 'runs']) mkdirSync(join(root, folder), { recursive: true })
|
|
224
|
+
let migrationError: string | null = null
|
|
225
|
+
let wikiMigrationError: string | null = null
|
|
226
|
+
try {
|
|
227
|
+
if (migrate.run(this.env, agentIds)) log(`started Wiki storage version ${migrate.WIKI_VERSION}; the earlier Wiki is kept as a backup in the data root`)
|
|
228
|
+
} catch (error) {
|
|
229
|
+
migrationError = error instanceof Error ? error.message : String(error)
|
|
230
|
+
wikiMigrationError = migrationError
|
|
231
|
+
log('Wiki migration failed', migrationError)
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
migrateConnectionScopes(root, this.stateDir)
|
|
235
|
+
} catch (error) {
|
|
236
|
+
const detail = error instanceof Error ? error.message : String(error)
|
|
237
|
+
migrationError = migrationError ? `${migrationError}; connector scope migration: ${detail}` : detail
|
|
238
|
+
log('connector scope migration failed', detail)
|
|
239
|
+
}
|
|
240
|
+
if (!wikiMigrationError) {
|
|
241
|
+
try {
|
|
242
|
+
store.ensureLayout(root, this.env.productName)
|
|
243
|
+
// Every project has its fixed pages, and the generated indexes match the files.
|
|
244
|
+
for (const project of projectsOrNone(root)) store.ensureProjectNamespace(root, project.slug, project.name)
|
|
245
|
+
writeIndex(store.wikiDir(root))
|
|
246
|
+
} catch (error) {
|
|
247
|
+
log('Wiki layout', error instanceof Error ? error.message : String(error))
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
let interruptedRuns = 0
|
|
251
|
+
try {
|
|
252
|
+
interruptedRuns = markInterruptedRuns(root, `${this.env.productName} stopped before the run settled.`)
|
|
253
|
+
} catch (error) {
|
|
254
|
+
log('run recovery', error instanceof Error ? error.message : String(error))
|
|
255
|
+
}
|
|
256
|
+
let reindexedPages: number
|
|
257
|
+
try {
|
|
258
|
+
reindexedPages = wikiIndex.ensure(root)
|
|
259
|
+
} catch (error) {
|
|
260
|
+
log('index', error instanceof Error ? error.message : String(error))
|
|
261
|
+
reindexedPages = countPages(root)
|
|
262
|
+
}
|
|
263
|
+
try {
|
|
264
|
+
// Runs recorded while nothing was running, or before recall existed, become searchable.
|
|
265
|
+
recall.sync(root)
|
|
266
|
+
} catch (error) {
|
|
267
|
+
log('recall', error instanceof Error ? error.message : String(error))
|
|
268
|
+
}
|
|
269
|
+
this.recovery = { interruptedRuns, reindexedPages, migrationError }
|
|
270
|
+
this.maintenance.start()
|
|
271
|
+
return this.recovery
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Deletes the index and rebuilds it from the files. Returns the pages indexed. */
|
|
275
|
+
rebuildIndex(): number {
|
|
276
|
+
const pages = wikiIndex.rebuild(this.env.root)
|
|
277
|
+
recall.sync(this.env.root)
|
|
278
|
+
this.recovery = { ...this.recovery, reindexedPages: pages }
|
|
279
|
+
return pages
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/** Stops live runs and releases the index. */
|
|
283
|
+
async close(): Promise<void> {
|
|
284
|
+
this.maintenance.stop()
|
|
285
|
+
await this.terminals.close()
|
|
286
|
+
await this.runs.interruptAll()
|
|
287
|
+
await this.maintenance.settled()
|
|
288
|
+
await this.hooks.settled().catch(() => undefined)
|
|
289
|
+
this.hooks.close()
|
|
290
|
+
await this.apps.close()
|
|
291
|
+
if (this.env.dataRoot) closeIndex(this.env.dataRoot)
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function createHarness(options: HarnessOptions): Harness {
|
|
296
|
+
const harness = new Harness(options)
|
|
297
|
+
if (options.dataRoot) harness.open(options.dataRoot)
|
|
298
|
+
return harness
|
|
299
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// @xingular/harness: a Pi agent harness. Multi-account Pi sessions, a compiled system prompt
|
|
2
|
+
// with stable-prefix delivery, a memory lifecycle (Wiki, blocks, facts, recall, sleep-time
|
|
3
|
+
// consolidation), tools with a gate and standing approvals, connected apps over MCP, and
|
|
4
|
+
// skills (a person's own and built-in).
|
|
5
|
+
//
|
|
6
|
+
// An application embeds it with `createHarness` and extends it through prompt sections, tool
|
|
7
|
+
// providers, a tool policy, a secret store, a directory of its own names, and a description of
|
|
8
|
+
// its own parts as plugins. Everything else is reachable by path, e.g. `@xingular/harness/memory/wiki`.
|
|
9
|
+
export { createHarness, Harness, type HarnessOptions, type Recovery } from './harness'
|
|
10
|
+
|
|
11
|
+
export { Bus, log, type Notice } from './core/bus'
|
|
12
|
+
export { EMPTY_DIRECTORY, type Directory, type HarnessEnv } from './core/env'
|
|
13
|
+
export { HarnessError, invalid, notFound, toErrorShape, type ErrorShape } from './core/errors'
|
|
14
|
+
export { findBinary, searchPath } from './core/path'
|
|
15
|
+
|
|
16
|
+
export { fileSecretStore, memorySecretStore, secretKey, type SecretStore } from './accounts/secrets'
|
|
17
|
+
export type { Account, AccountsOverview, Pool } from './accounts/store'
|
|
18
|
+
export type { ModelOption } from './accounts/accounts'
|
|
19
|
+
export type { Capability, CapabilityCatalogue, CapabilityKind, CapabilityScope, CapabilitySet } from './capabilities/types'
|
|
20
|
+
export type { RunCapabilitySnapshot } from './capabilities/runSnapshot'
|
|
21
|
+
|
|
22
|
+
export type { CallerContext } from './memory/caller'
|
|
23
|
+
export { dispatch as memoryCall } from './memory/dispatch'
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
compile,
|
|
27
|
+
render,
|
|
28
|
+
SECTION_ORDER,
|
|
29
|
+
CONTEXT_MESSAGE_TYPE,
|
|
30
|
+
CONTEXT_SNAPSHOT_KEY,
|
|
31
|
+
type ContextPack,
|
|
32
|
+
type ContextProfile,
|
|
33
|
+
type HostSection,
|
|
34
|
+
type HostToolGroup,
|
|
35
|
+
type Launch,
|
|
36
|
+
type Reference,
|
|
37
|
+
type Section
|
|
38
|
+
} from './prompt/compile'
|
|
39
|
+
|
|
40
|
+
export { runEventSchema, runRecordSchema, isActiveStatus, type RunEvent, type RunEventType, type RunRecord } from './sessions/contracts'
|
|
41
|
+
export type { ApprovalFacts, ApprovalPolicy, AutomaticReason } from './sessions/approvals'
|
|
42
|
+
export type { ScopeDecision, ToolCall, ToolPolicy } from './sessions/policy'
|
|
43
|
+
export type { ActiveRun, ApprovalDecision, ApprovalInput, QuestionAnswer, QuestionInput } from './sessions/registry'
|
|
44
|
+
export type { RunFields, SessionRunInput } from './sessions/sessions'
|
|
45
|
+
export type { Effort } from './sessions/runner'
|
|
46
|
+
|
|
47
|
+
export { describeTool, WIKI_TOOLS, WIKI_TOOL_NAMES, type WikiCall, type WikiToolDefinition } from './tools/definitions'
|
|
48
|
+
export type { ToolProvider, ToolResult, ToolSession, ToolSpec } from './tools/types'
|
|
49
|
+
export { webTools, WEB_PLUGIN, WEB_SEARCH_KEY, type WebToolOptions } from './web/tools'
|
|
50
|
+
export { safeRequest, type WebRequest, type WebResponse } from './web/safeRequest'
|
|
51
|
+
|
|
52
|
+
export type { BrokeredTools, BrokerSession, BrokerTool } from './mcp/service'
|
|
53
|
+
export type { Connection, ConnectionInput } from './mcp/types'
|
|
54
|
+
|
|
55
|
+
export { configureBuiltins, type RegistryItem, type RegistryState, type SkillBundleInput } from './skills/registry'
|
|
56
|
+
|
|
57
|
+
export type { TerminalAssets, TerminalLaunch, TerminalPrepareInput } from './terminal/terminals'
|
|
58
|
+
|
|
59
|
+
export { attach as attachPlugin, inventoryFor as pluginInventory, list as listPlugins, type AttachParams, type InventoryParams, type PluginScope } from './plugins/api'
|
|
60
|
+
export type { Attachment, AttachScope } from './plugins/attachments'
|
|
61
|
+
export { HARNESS_PLUGINS } from './plugins/builtin'
|
|
62
|
+
export { FIRST_PARTY_PLUGINS } from './plugins/firstParty'
|
|
63
|
+
export type { Found, Resolver } from './plugins/discover'
|
|
64
|
+
export type { Loading, Registered } from './plugins/plugins'
|
|
65
|
+
export { CONTRIBUTION_KINDS, PLUGIN_SOURCES, type ConnectorTemplate, type Contribution, type ContributionKind, type ContributionState, type Delivery, type Inventory, type InventoryItem, type Plugin, type PluginDescriptor, type PluginManage, type PluginSetting, type PluginSource } from './plugins/types'
|
|
66
|
+
|
|
67
|
+
export { methods } from './server/methods'
|
|
68
|
+
export { remoteDirectory, remotePolicy, remoteSecrets, remoteTools } from './server/remote'
|
|
69
|
+
export { PROTOCOL_VERSION, RpcPeer, stdioTransport, type Handler, type Transport } from './server/rpc'
|
|
70
|
+
export { attach, serve, type Served } from './server/serve'
|