@xingular/harness 2.1.2 → 3.0.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 +12 -0
- package/NOTICE.md +22 -0
- package/README.md +67 -36
- package/dist/cli.js +6023 -6621
- package/dist/terminal-pi.js +19 -24
- package/docs/terminal-tui.md +27 -7
- package/package.json +2 -2
- package/src/accounts/accounts.ts +4 -3
- package/src/capabilities/api.ts +1 -1
- package/src/cli.ts +7 -3
- package/src/core/caller.ts +34 -0
- package/src/core/fs.ts +17 -1
- package/src/harness.ts +55 -66
- package/src/{memory → history}/recall.test.ts +1 -1
- package/src/{memory → history}/recall.ts +1 -1
- package/src/{memory → history}/trajectory.test.ts +4 -4
- package/src/{memory → history}/trajectory.ts +1 -1
- package/src/hub/hub.test.ts +4 -5
- package/src/hub/hub.ts +2 -2
- package/src/index.ts +28 -14
- package/src/mcp/connections.ts +1 -1
- package/src/mcp/service.ts +1 -1
- package/src/pi/proxy.test.ts +68 -0
- package/src/pi/runtime.test.ts +61 -0
- package/src/pi/runtime.ts +50 -0
- package/src/plugins/api.ts +11 -9
- package/src/plugins/builtin.ts +22 -40
- package/src/plugins/catalogue.test.ts +13 -41
- package/src/plugins/catalogue.ts +3 -32
- package/src/plugins/discover.ts +2 -1
- package/src/plugins/firstParty.test.ts +1 -1
- package/src/plugins/hooks.test.ts +21 -71
- package/src/plugins/hooks.ts +6 -7
- package/src/plugins/identity.ts +16 -2
- package/src/plugins/install.test.ts +1 -1
- package/src/plugins/install.ts +2 -1
- package/src/plugins/inventory.ts +19 -18
- package/src/plugins/plugins.ts +38 -2
- package/src/prompt/compile.test.ts +82 -193
- package/src/prompt/compile.ts +98 -363
- package/src/refine/cli.ts +114 -0
- package/src/refine/demo.ts +124 -0
- package/src/refine/evidence.ts +349 -0
- package/src/refine/host.ts +102 -0
- package/src/refine/ledger.ts +104 -0
- package/src/refine/library.ts +192 -0
- package/src/refine/pi.test.ts +90 -0
- package/src/refine/pi.ts +218 -0
- package/src/refine/prompts.ts +430 -0
- package/src/refine/recursion.ts +353 -0
- package/src/refine/refine.test.ts +140 -0
- package/src/refine/runner.ts +205 -0
- package/src/refine/selection.ts +291 -0
- package/src/refine/service.ts +194 -0
- package/src/refine/trajectory.ts +71 -0
- package/src/refine/types.ts +91 -0
- package/src/server/methods.ts +137 -123
- package/src/server/server.test.ts +37 -30
- package/src/sessions/contracts.ts +6 -1
- package/src/sessions/conversation.test.ts +2 -27
- package/src/sessions/policy.ts +1 -1
- package/src/sessions/pricing.ts +3 -1
- package/src/sessions/registry.ts +5 -1
- package/src/sessions/runStore.ts +3 -1
- package/src/sessions/runner.ts +10 -5
- package/src/sessions/sessions.test.ts +45 -6
- package/src/sessions/sessions.ts +40 -17
- package/src/sessions/subagentService.test.ts +22 -1
- package/src/sessions/subagentService.ts +14 -1
- package/src/sessions/subagents.ts +2 -0
- package/src/terminal/cli.ts +24 -48
- package/src/terminal/pluginControl.test.ts +7 -8
- package/src/terminal/socket.ts +3 -3
- package/src/terminal/terminal.test.ts +21 -39
- package/src/terminal/terminals.ts +20 -13
- package/src/terminal/tui/application.test.ts +58 -0
- package/src/terminal/tui/application.ts +10 -4
- package/src/terminal/tui/clipboardFiles.test.ts +94 -0
- package/src/terminal/tui/clipboardFiles.ts +86 -0
- package/src/terminal/tui/piAdapter.ts +7 -4
- package/src/terminal/tui/runtime.ts +2 -0
- package/src/tools/definitions.ts +49 -132
- package/src/tools/dispatch.ts +78 -0
- package/src/tools/harnessTools.ts +23 -0
- package/src/tools/spawnSubagent.ts +3 -3
- package/src/tools/types.ts +1 -1
- package/src/tree/components.ts +221 -0
- package/src/tree/materialise.ts +122 -0
- package/src/tree/seed.ts +123 -0
- package/src/tree/store.ts +193 -0
- package/src/tree/surface.ts +135 -0
- package/src/tree/tree.test.ts +126 -0
- package/src/tree/tree.ts +346 -0
- package/src/tree/trust.ts +86 -0
- package/src/wiki/notes.ts +313 -0
- package/src/memory/api.test.ts +0 -82
- package/src/memory/api.ts +0 -303
- package/src/memory/blocks.test.ts +0 -78
- package/src/memory/blocks.ts +0 -408
- package/src/memory/caller.ts +0 -69
- package/src/memory/curation.test.ts +0 -206
- package/src/memory/curation.ts +0 -64
- package/src/memory/dispatch.ts +0 -64
- package/src/memory/facts.test.ts +0 -41
- package/src/memory/facts.ts +0 -248
- package/src/memory/graph.ts +0 -103
- package/src/memory/health.test.ts +0 -130
- package/src/memory/health.ts +0 -74
- package/src/memory/maintainer.ts +0 -272
- package/src/memory/maintenance.test.ts +0 -192
- package/src/memory/maintenance.ts +0 -679
- package/src/memory/migrate.ts +0 -79
- package/src/memory/operations.ts +0 -150
- package/src/memory/proposals.test.ts +0 -168
- package/src/memory/proposals.ts +0 -584
- package/src/memory/references.ts +0 -77
- package/src/memory/runContext.ts +0 -93
- package/src/memory/store.test.ts +0 -66
- package/src/memory/store.ts +0 -385
- package/src/memory/wiki.test.ts +0 -47
- package/src/memory/wiki.ts +0 -423
- package/src/memory/wikiIndex.test.ts +0 -88
- package/src/memory/wikiIndex.ts +0 -117
- package/src/plugins/proposal.ts +0 -121
- package/src/skills/registry.test.ts +0 -104
- package/src/skills/registry.ts +0 -413
- package/src/tools/maintenance.ts +0 -29
- package/src/tools/memoryTools.ts +0 -22
- package/src/tools/pluginPropose.ts +0 -41
- package/src/tools/registryWrite.test.ts +0 -101
- package/src/tools/registryWrite.ts +0 -182
- /package/src/{memory → core}/secrets.ts +0 -0
package/dist/terminal-pi.js
CHANGED
|
@@ -18,7 +18,7 @@ function connectionFromEnv(env = process.env) {
|
|
|
18
18
|
if (!socket || !token) throw new SocketError("not_configured", `This command only works inside a terminal session the harness started (${SOCKET_ENV} and ${TOKEN_ENV} are not set).`);
|
|
19
19
|
return { socket, token };
|
|
20
20
|
}
|
|
21
|
-
function request(connection,
|
|
21
|
+
function request(connection, body, what, timeoutMs) {
|
|
22
22
|
return new Promise((resolve, reject) => {
|
|
23
23
|
const stream = createConnection(connection.socket);
|
|
24
24
|
let buffer = "";
|
|
@@ -34,7 +34,7 @@ function request(connection, body2, what, timeoutMs) {
|
|
|
34
34
|
stream.destroy();
|
|
35
35
|
finish(() => reject(new SocketError("timeout", `The harness did not answer ${what}.`)));
|
|
36
36
|
}, timeoutMs);
|
|
37
|
-
stream.on("connect", () => stream.write(`${JSON.stringify({ token: connection.token, ...
|
|
37
|
+
stream.on("connect", () => stream.write(`${JSON.stringify({ token: connection.token, ...body })}
|
|
38
38
|
`));
|
|
39
39
|
stream.on("data", (chunk) => {
|
|
40
40
|
buffer += chunk.toString("utf8");
|
|
@@ -278,16 +278,16 @@ async function followLogin(ui, call, accountId, loginId) {
|
|
|
278
278
|
const deadline = Date.now() + LOGIN_TIMEOUT_MS;
|
|
279
279
|
let shownRevision = -1;
|
|
280
280
|
for (; ; ) {
|
|
281
|
-
const
|
|
282
|
-
if (!
|
|
283
|
-
if (
|
|
284
|
-
if (
|
|
285
|
-
await answerPrompt(ui, call, loginId,
|
|
281
|
+
const snapshot = await call("accounts.loginStatus", { accountId });
|
|
282
|
+
if (!snapshot || snapshot.loginId !== loginId) return;
|
|
283
|
+
if (snapshot.status !== "running") return reportLogin(ui, snapshot);
|
|
284
|
+
if (snapshot.prompt) {
|
|
285
|
+
await answerPrompt(ui, call, loginId, snapshot.prompt);
|
|
286
286
|
continue;
|
|
287
287
|
}
|
|
288
|
-
const step = [...
|
|
289
|
-
if (step &&
|
|
290
|
-
shownRevision =
|
|
288
|
+
const step = [...snapshot.events].reverse().map(loginStep).find((text3) => text3 !== null);
|
|
289
|
+
if (step && snapshot.revision !== shownRevision) {
|
|
290
|
+
shownRevision = snapshot.revision;
|
|
291
291
|
const choice = await ui.select(`${step}
|
|
292
292
|
|
|
293
293
|
Finish in your browser, then choose Check status.`, [CHECK_STATUS, CANCEL_SIGN_IN]);
|
|
@@ -306,10 +306,10 @@ Finish in your browser, then choose Check status.`, [CHECK_STATUS, CANCEL_SIGN_I
|
|
|
306
306
|
await new Promise((resolve) => setTimeout(resolve, POLL_MS));
|
|
307
307
|
}
|
|
308
308
|
}
|
|
309
|
-
function reportLogin(ui,
|
|
310
|
-
if (
|
|
311
|
-
else if (
|
|
312
|
-
else ui.notify(
|
|
309
|
+
function reportLogin(ui, snapshot) {
|
|
310
|
+
if (snapshot.status === "completed") ui.notify("Signed in.", "info");
|
|
311
|
+
else if (snapshot.status === "cancelled") ui.notify("Sign-in was cancelled.", "warning");
|
|
312
|
+
else ui.notify(snapshot.error ?? "Sign-in failed.", "error");
|
|
313
313
|
}
|
|
314
314
|
async function answerPrompt(ui, call, loginId, prompt) {
|
|
315
315
|
const promptId = String(prompt.promptId ?? "");
|
|
@@ -764,7 +764,9 @@ var runRecordSchema = z.object({
|
|
|
764
764
|
handoffId: text,
|
|
765
765
|
accountId: text,
|
|
766
766
|
agentId: text,
|
|
767
|
-
trigger: text
|
|
767
|
+
trigger: text,
|
|
768
|
+
harnessScope: text.optional(),
|
|
769
|
+
harnessVersion: text.optional()
|
|
768
770
|
});
|
|
769
771
|
|
|
770
772
|
// src/core/index.ts
|
|
@@ -851,18 +853,11 @@ function askUserTool(run2) {
|
|
|
851
853
|
// src/core/frontmatter.ts
|
|
852
854
|
import { parse as parseYaml, stringify } from "yaml";
|
|
853
855
|
|
|
854
|
-
// src/memory/store.ts
|
|
855
|
-
import { stringify as stringify2 } from "yaml";
|
|
856
|
-
|
|
857
|
-
// src/skills/registry.ts
|
|
858
|
-
var MAX_SKILL_IMPORT_BYTES = 10 * 1024 * 1024;
|
|
859
|
-
|
|
860
856
|
// src/prompt/compile.ts
|
|
861
|
-
var SECTION_ORDER = {
|
|
857
|
+
var SECTION_ORDER = { system: 25, guardrails: 40, memory: 45, references: 50, subagents: 64, scripts: 66, wiki: 80, tools: 90 };
|
|
862
858
|
var BUILT_IN_DYNAMIC = [
|
|
863
|
-
{ id: "blocks", order: SECTION_ORDER.blocks },
|
|
864
859
|
{ id: "references", order: SECTION_ORDER.references },
|
|
865
|
-
{ id: "
|
|
860
|
+
{ id: "wiki", order: SECTION_ORDER.wiki }
|
|
866
861
|
];
|
|
867
862
|
|
|
868
863
|
// src/sessions/planMode.ts
|
package/docs/terminal-tui.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Harness terminal frontend
|
|
2
2
|
|
|
3
|
-
Status:
|
|
3
|
+
Status: published as `@xingular/harness` 2.1.2. Last updated: 2026-10-19.
|
|
4
4
|
Decision: [ADR-0001](decisions/ADR-0001-owned-terminal.md).
|
|
5
5
|
|
|
6
6
|
## Native subagents (2026-10-18)
|
|
@@ -148,11 +148,17 @@ Full upstream copyright and MIT permission notices are in [NOTICE.md](../NOTICE.
|
|
|
148
148
|
## Runtime compatibility adapter
|
|
149
149
|
|
|
150
150
|
Most integration uses public Pi exports. [`piAdapter.ts`](../src/terminal/tui/piAdapter.ts)
|
|
151
|
-
contains the remaining
|
|
152
|
-
loading them
|
|
151
|
+
contains the remaining presentation dependencies and requires exactly Pi **1.2.0** before
|
|
152
|
+
loading them. The shared [`runtime.ts`](../src/pi/runtime.ts) loader imports Pi first, then invokes
|
|
153
|
+
the same HTTP dispatcher hook as Pi's CLI before any Harness Pi runtime is used. This reapplies
|
|
154
|
+
the process `HTTP_PROXY`, `HTTPS_PROXY` and `NO_PROXY` settings after Pi's bundled Undici has
|
|
155
|
+
loaded. Account and model checks, sessions, maintenance, plugin discovery and installation,
|
|
156
|
+
pricing, and the standalone terminal all use this loader. It is process-wide and coalesces
|
|
157
|
+
concurrent initialization.
|
|
153
158
|
|
|
154
159
|
| Internal dependency | Reason |
|
|
155
160
|
|---|---|
|
|
161
|
+
| `dist/bundle/chunks/chunk-TJXZSVTD.js:configureHttpDispatcher` | Restore Pi's environment-aware HTTP proxy dispatcher after the SDK import |
|
|
156
162
|
| `dist/extensions/index.js` builtin inventory | Preserve selected `llama.cpp`, `codemode`, `tool-search` and MCP builtins with the CLI's replacement semantics |
|
|
157
163
|
| `dist/modes/interactive/theme/theme.js:setThemeInstance` | Keep public native rich components on the same active theme as the Harness screen |
|
|
158
164
|
| `dist/core/tools/renderers/index.js:withBuiltInRenderers` | Preserve existing read/edit/bash/write formatting without replacing tool execution |
|
|
@@ -160,7 +166,20 @@ loading them:
|
|
|
160
166
|
|
|
161
167
|
The adapter is initialized once; its global theme setter and renderer lookup are synchronous
|
|
162
168
|
thereafter. A Pi dependency upgrade must update this adapter deliberately and rerun the
|
|
163
|
-
terminal compatibility tests.
|
|
169
|
+
terminal compatibility tests. The dispatcher compatibility check also pins the private Pi bundle
|
|
170
|
+
chunk and fails with a clear error if that hook moves or disappears. A local proxy fixture test
|
|
171
|
+
checks that a Pi HTTP request reaches the configured proxy without contacting an external host.
|
|
172
|
+
These module imports do not instantiate `InteractiveMode`.
|
|
173
|
+
|
|
174
|
+
## Clipboard image paste
|
|
175
|
+
|
|
176
|
+
The standalone terminal reads Finder-copied file paths before checking clipboard image pixels.
|
|
177
|
+
On macOS, `.heic` and `.heif` files are converted with `/usr/bin/sips` to private temporary PNGs,
|
|
178
|
+
preserving the copied path order and leaving the originals untouched. The temporary files are
|
|
179
|
+
removed when the terminal shuts down. A failed or invalid conversion reports an error and leaves
|
|
180
|
+
the composer unchanged. Clipboard image pixels continue through Pi's native adapter, which reads
|
|
181
|
+
PNG/TIFF on macOS and normalizes TIFF to PNG. This path is terminal-emulator independent; direct
|
|
182
|
+
Warp clipboard acceptance still requires a manual check in Warp.
|
|
164
183
|
|
|
165
184
|
## Accounts, history and resource trust
|
|
166
185
|
|
|
@@ -324,9 +343,10 @@ a successful response or tool execution. Harness persisted the failed run and re
|
|
|
324
343
|
terminal; the source sign-in file was unchanged and the disposable credentials were deleted.
|
|
325
344
|
The [live report and capture](evidence/terminal-2026-10-17-followup/live-codex/README.md) retain
|
|
326
345
|
this quota failure explicitly. Successful live streaming/tool acceptance remains open until
|
|
327
|
-
account capacity is available. GitHub gist publication, actual OS clipboard
|
|
328
|
-
Windows behavior and every terminal emulator remain unverified.
|
|
329
|
-
|
|
346
|
+
account capacity is available. GitHub gist publication, actual OS clipboard pixel transfer,
|
|
347
|
+
Windows behavior and every terminal emulator remain unverified. HEIC/HEIF file staging has focused
|
|
348
|
+
conversion tests; that does not establish Finder or Warp OS interaction. Rich image/diff rendering
|
|
349
|
+
and clipboard adapters have focused compatibility tests; that does not establish OS interaction. These changes shipped in
|
|
330
350
|
`@xingular/harness` 2.0.0 (commit `44471bc`), while the complete plan's acceptance remains
|
|
331
351
|
open pending the required integration and provider evidence.
|
|
332
352
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xingular/harness",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "Xingular Harness — a Pi agent harness
|
|
5
|
+
"description": "Xingular Harness — a Pi agent harness built on recursive harnessing (Harness²): versioned eight-component harness trees, refinement from evidence, multi-account sessions, a compiled prompt, project notes, tools, and MCP",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/tanntnny/xingular-harness.git"
|
package/src/accounts/accounts.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { ModelRuntime } from '@earendil-works/pi-coding-agent'
|
|
|
10
10
|
import { log, type Bus } from '../core/bus'
|
|
11
11
|
import { HarnessError, invalid } from '../core/errors'
|
|
12
12
|
import { atomicWrite, now, readJson, safeId } from '../core/fs'
|
|
13
|
+
import { loadPi } from '../pi/runtime'
|
|
13
14
|
import type { RunEvent, RunRecord } from '../sessions/contracts'
|
|
14
15
|
import * as env from './env'
|
|
15
16
|
import { secretKey, type SecretStore } from './secrets'
|
|
@@ -222,7 +223,7 @@ export class Accounts {
|
|
|
222
223
|
}
|
|
223
224
|
|
|
224
225
|
private async loadInstallationDeviceId(): Promise<string> {
|
|
225
|
-
const { SettingsManager } = await
|
|
226
|
+
const { SettingsManager } = await loadPi()
|
|
226
227
|
const settings = SettingsManager.create(process.cwd(), env.ensureSharedDir(this.dir))
|
|
227
228
|
const deviceId = settings.getOrCreateDeviceId()
|
|
228
229
|
await settings.flush()
|
|
@@ -237,7 +238,7 @@ export class Accounts {
|
|
|
237
238
|
let runtime = this.runtimes.get(id)
|
|
238
239
|
if (!runtime) {
|
|
239
240
|
const configDir = 'configDir' in account ? account.configDir : env.ensureHome(this.dir, account)
|
|
240
|
-
runtime =
|
|
241
|
+
runtime = loadPi()
|
|
241
242
|
.then(({ ModelRuntime }) => ModelRuntime.create({ credentials: this.credentialStore(id) as never, modelsPath: join(configDir, 'models.json') }))
|
|
242
243
|
.catch((error: unknown) => {
|
|
243
244
|
this.runtimes.delete(id)
|
|
@@ -275,7 +276,7 @@ export class Accounts {
|
|
|
275
276
|
|
|
276
277
|
/** Providers Pi can sign in to (OAuth) or take a key for, independent of any account. */
|
|
277
278
|
async providers(): Promise<{ id: string; name: string; oauth: boolean; oauthLabel: string | null; subscription: boolean; apiKey: boolean }[]> {
|
|
278
|
-
const { ModelRuntime } = await
|
|
279
|
+
const { ModelRuntime } = await loadPi()
|
|
279
280
|
const none = { read: async () => undefined, list: async () => [], modify: async () => undefined, delete: async () => undefined }
|
|
280
281
|
const runtime = await ModelRuntime.create({ credentials: none, modelsPath: null, refreshOnCreate: false })
|
|
281
282
|
return runtime
|
package/src/capabilities/api.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// stores remain the source of truth; this module gives hosts one capability-shaped contract.
|
|
3
3
|
import { invalid } from '../core/errors'
|
|
4
4
|
import type { Harness } from '../harness'
|
|
5
|
-
import type { CallerContext } from '../
|
|
5
|
+
import type { CallerContext } from '../core/caller'
|
|
6
6
|
import type { Launch } from '../prompt/compile'
|
|
7
7
|
import * as pluginApi from '../plugins/api'
|
|
8
8
|
import type { Plugin } from '../plugins/types'
|
package/src/cli.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// xingular-harness serve --state <dir> [--data <dir>] [--name <product>] [--builtins <dir>]…
|
|
5
5
|
// [--host-secrets] [--host-policy] [--host-tools] [--host-directory]
|
|
6
6
|
// Serves the harness to a host process over stdin/stdout (newline-delimited JSON).
|
|
7
|
-
// xingular-harness wiki <command>
|
|
7
|
+
// xingular-harness wiki <command> Project notes, skills and history, from inside a terminal session.
|
|
8
|
+
// xingular-harness refine <command> Refine a harness tree from evidence (refine demo needs no model).
|
|
8
9
|
// xingular-harness mcp <command> Connected apps, from inside a terminal session.
|
|
9
10
|
// xingular-harness plugins <command> List, install, switch and trust plugins.
|
|
10
11
|
import { resolve } from 'node:path'
|
|
@@ -13,6 +14,7 @@ import { log } from './core/bus'
|
|
|
13
14
|
import { createHarness, type HarnessOptions } from './harness'
|
|
14
15
|
import { runPluginsCli, terminalAsk } from './plugins/cli'
|
|
15
16
|
import { runHubCli } from './hub/cli'
|
|
17
|
+
import { runRefineCli } from './refine/cli'
|
|
16
18
|
import { remoteDirectory, remotePolicy, remoteSecrets, remoteTools } from './server/remote'
|
|
17
19
|
import { RpcPeer, stdioTransport } from './server/rpc'
|
|
18
20
|
import { attach } from './server/serve'
|
|
@@ -20,17 +22,18 @@ import { runAppsCli, runWikiCli } from './terminal/cli'
|
|
|
20
22
|
import { runStandaloneTerminal, standaloneArguments } from './terminal/standalone'
|
|
21
23
|
|
|
22
24
|
const USAGE = `Usage: xingular-harness [--data <dir>] [--state <dir>]
|
|
23
|
-
xingular-harness <terminal | serve | wiki | mcp | plugins | hub> [arguments]
|
|
25
|
+
xingular-harness <terminal | serve | wiki | mcp | plugins | hub | refine> [arguments]
|
|
24
26
|
|
|
25
27
|
(no command) Start the interactive Harness terminal
|
|
26
28
|
terminal Start the interactive Harness terminal
|
|
27
29
|
serve --state <dir> [--data <dir>] [--name <product>] [--builtins <dir>]
|
|
28
30
|
[--host-secrets] [--host-policy] [--host-tools] [--host-directory]
|
|
29
31
|
Serve the harness over newline-delimited JSON
|
|
30
|
-
wiki <command>
|
|
32
|
+
wiki <command> Wiki, skill and history commands for a terminal session
|
|
31
33
|
mcp <command> Connected-app commands for a terminal session
|
|
32
34
|
plugins <command> List, install, switch and trust plugins (plugins help)
|
|
33
35
|
hub <command> Browse and install curated items (hub help)
|
|
36
|
+
refine <command> Refine a harness from evidence (refine demo, refine run)
|
|
34
37
|
|
|
35
38
|
Standalone terminal options:
|
|
36
39
|
--data <dir> Data directory (default: ~/.xingular-harness/data)
|
|
@@ -105,6 +108,7 @@ async function main(): Promise<number> {
|
|
|
105
108
|
if (command === 'mcp') return runAppsCli(rest)
|
|
106
109
|
if (command === 'plugins') return runPluginsCli(rest, { ask: terminalAsk() })
|
|
107
110
|
if (command === 'hub') return runHubCli(rest)
|
|
111
|
+
if (command === 'refine') return runRefineCli(rest)
|
|
108
112
|
process.stdout.write(USAGE)
|
|
109
113
|
return command ? 2 : 0
|
|
110
114
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Who a session works as. The embedding application supplies this when a session starts; the
|
|
2
|
+
// harness never looks an agent up by itself. The ids are opaque to the harness.
|
|
3
|
+
import { findProject, type Project } from './projects'
|
|
4
|
+
|
|
5
|
+
export interface CallerContext {
|
|
6
|
+
/** Opaque, host-owned policy data frozen when this session starts. The harness only forwards it to the embedding app's tool policy. */
|
|
7
|
+
policyContext?: unknown
|
|
8
|
+
runId?: string | null
|
|
9
|
+
sessionId?: string | null
|
|
10
|
+
projectId?: string | null
|
|
11
|
+
/** The caller: decides which plugins and connectors reach it, and whose history it recalls. */
|
|
12
|
+
agentId?: string | null
|
|
13
|
+
/** Callers whose conversations this one may not recall (opaque ids from the embedding host). */
|
|
14
|
+
recallExcludeAgents?: string[] | null
|
|
15
|
+
/** The caller's name, recorded on what it writes. */
|
|
16
|
+
agentName?: string | null
|
|
17
|
+
/** `Chat` for an agent's own conversation, `Code` for a session in a project. */
|
|
18
|
+
actor?: string | null
|
|
19
|
+
/** `cli` when the caller reaches the harness through a terminal command. */
|
|
20
|
+
surface?: string | null
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const isSession = (context: CallerContext): boolean => context.actor === 'Code'
|
|
24
|
+
export const actorOf = (context: CallerContext): string => (isSession(context) ? 'Code' : 'Chat')
|
|
25
|
+
export const usesCli = (context: CallerContext): boolean => context.surface === 'cli'
|
|
26
|
+
|
|
27
|
+
export function projectOf(root: string, context: CallerContext): Project | null {
|
|
28
|
+
if (!context.projectId) return null
|
|
29
|
+
try {
|
|
30
|
+
return findProject(root, context.projectId)
|
|
31
|
+
} catch {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/core/fs.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto'
|
|
2
|
-
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeSync } from 'node:fs'
|
|
2
|
+
import { closeSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, statSync, writeSync } from 'node:fs'
|
|
3
3
|
import { basename, dirname, isAbsolute, join, normalize, sep } from 'node:path'
|
|
4
4
|
|
|
5
5
|
import { HarnessError, invalid } from './errors'
|
|
@@ -90,3 +90,19 @@ export function now(): string {
|
|
|
90
90
|
export function newId(): string {
|
|
91
91
|
return randomUUID()
|
|
92
92
|
}
|
|
93
|
+
|
|
94
|
+
export function isDir(path: string): boolean {
|
|
95
|
+
try {
|
|
96
|
+
return statSync(path).isDirectory()
|
|
97
|
+
} catch {
|
|
98
|
+
return false
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function isFile(path: string): boolean {
|
|
103
|
+
try {
|
|
104
|
+
return statSync(path).isFile()
|
|
105
|
+
} catch {
|
|
106
|
+
return false
|
|
107
|
+
}
|
|
108
|
+
}
|
package/src/harness.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
// The harness, assembled: Pi sessions on isolated accounts,
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// The harness, assembled: Pi sessions on isolated accounts, versioned harness trees that shape
|
|
2
|
+
// them, refinement that improves those trees from evidence, a compiled prompt, project notes,
|
|
3
|
+
// tools, and connected apps. An application embeds one of these and adds its own prompt
|
|
4
|
+
// sections, tools, and policy; nothing here knows what that application is.
|
|
4
5
|
import { Subagents } from './sessions/subagentService'
|
|
5
6
|
import { mkdirSync } from 'node:fs'
|
|
6
7
|
import { join } from 'node:path'
|
|
@@ -15,18 +16,15 @@ import { DEFAULT_PRODUCT_NAME, EMPTY_DIRECTORY, type Directory, type HarnessEnv
|
|
|
15
16
|
import { HarnessError } from './core/errors'
|
|
16
17
|
import { readJsonOr } from './core/fs'
|
|
17
18
|
import { closeIndex } from './core/index'
|
|
18
|
-
import { projectsOrNone } from './core/projects'
|
|
19
19
|
import { ConnectedApps } from './mcp/service'
|
|
20
20
|
import type { AppOAuthClients } from './mcp/types'
|
|
21
21
|
import { migrateConnectionScopes } from './mcp/scopeMigration'
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import * as
|
|
28
|
-
import { countPages } from './memory/wiki'
|
|
29
|
-
import * as wikiIndex from './memory/wikiIndex'
|
|
22
|
+
import * as recall from './history/recall'
|
|
23
|
+
import type { RefineHost } from './refine/host'
|
|
24
|
+
import { Refinements } from './refine/service'
|
|
25
|
+
import { configureBuiltins } from './tree/components'
|
|
26
|
+
import { TreeTrust } from './tree/trust'
|
|
27
|
+
import * as notes from './wiki/notes'
|
|
30
28
|
import * as pluginApi from './plugins/api'
|
|
31
29
|
import * as attachments from './plugins/attachments'
|
|
32
30
|
import { HARNESS_PLUGINS } from './plugins/builtin'
|
|
@@ -40,7 +38,6 @@ import { RunRegistry } from './sessions/registry'
|
|
|
40
38
|
import { markInterruptedRuns } from './sessions/runStore'
|
|
41
39
|
import type { SessionFactory } from './sessions/runner'
|
|
42
40
|
import { Sessions } from './sessions/sessions'
|
|
43
|
-
import * as registry from './skills/registry'
|
|
44
41
|
import { Terminals, type TerminalAssets } from './terminal/terminals'
|
|
45
42
|
|
|
46
43
|
export interface HarnessOptions {
|
|
@@ -57,19 +54,13 @@ export interface HarnessOptions {
|
|
|
57
54
|
policy?: ToolPolicy
|
|
58
55
|
/** Replaces Pi, for tests. */
|
|
59
56
|
sessionFactory?: SessionFactory
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
externalMaintenance?: boolean
|
|
63
|
-
maintenanceExcludedTriggers?: string[]
|
|
64
|
-
/** Queue consolidation after every settled run (the default). False leaves memory to explicit proposals and scheduled merges. */
|
|
65
|
-
maintenanceRunTrigger?: boolean
|
|
66
|
-
/** How long the maintenance queue stays empty before full memory blocks are condensed. */
|
|
67
|
-
idleMs?: number
|
|
57
|
+
/** Where refinement executions work and with what; without it, a temporary copy of the task's source and Pi's own tools. */
|
|
58
|
+
refineHost?: RefineHost
|
|
68
59
|
/** Where the built commands a terminal session runs are, when they are not beside this package. */
|
|
69
60
|
assets?: TerminalAssets
|
|
70
61
|
/** Optional legacy executable override. The default is the Harness-owned terminal frontend. */
|
|
71
62
|
piProgram?: () => string | null
|
|
72
|
-
/** Folders of built-in skills, prompts, and rules, each laid out like `registry/`. */
|
|
63
|
+
/** Folders of built-in skills, prompts, and rules for the component library, each laid out like `registry/`. */
|
|
73
64
|
builtins?: string[]
|
|
74
65
|
/**
|
|
75
66
|
* The application's own parts, described as plugins: which tools, prompt sections and tool
|
|
@@ -90,7 +81,10 @@ export interface HarnessOptions {
|
|
|
90
81
|
/** What starting on a data root found and repaired. */
|
|
91
82
|
export interface Recovery {
|
|
92
83
|
interruptedRuns: number
|
|
84
|
+
/** Notes in the Wiki. */
|
|
93
85
|
reindexedPages: number
|
|
86
|
+
/** Where an earlier Wiki's memory, facts and revisions were moved, when they were. */
|
|
87
|
+
legacyWiki?: string | null
|
|
94
88
|
/** Set when the storage migration failed; the old data is left as it was. */
|
|
95
89
|
migrationError: string | null
|
|
96
90
|
}
|
|
@@ -133,7 +127,11 @@ export class Harness {
|
|
|
133
127
|
applicationPlugins: ReadonlySet<string> = new Set()
|
|
134
128
|
readonly subagents: Subagents
|
|
135
129
|
readonly sessions: Sessions
|
|
136
|
-
|
|
130
|
+
/** Which plugin code in harness trees a person trusted. */
|
|
131
|
+
readonly trust: TreeTrust
|
|
132
|
+
/** Refinement: improving a harness tree from evidence of how it ran. */
|
|
133
|
+
readonly refine: Refinements
|
|
134
|
+
refineHost: RefineHost | null
|
|
137
135
|
readonly terminals: Terminals
|
|
138
136
|
/** Resolves when what was loaded in the background at construction is in place. */
|
|
139
137
|
readonly ready: Promise<void>
|
|
@@ -147,7 +145,9 @@ export class Harness {
|
|
|
147
145
|
this.env = new Env(options.dataRoot || null, options.directory ?? EMPTY_DIRECTORY, options.productName ?? DEFAULT_PRODUCT_NAME, this.bus)
|
|
148
146
|
this.hub = new Hub(this, options.hub)
|
|
149
147
|
this.policy = options.policy ?? {}
|
|
150
|
-
|
|
148
|
+
configureBuiltins(options.builtins ?? [])
|
|
149
|
+
this.trust = new TreeTrust(this.stateDir)
|
|
150
|
+
this.refineHost = options.refineHost ?? null
|
|
151
151
|
const secrets = options.secrets ?? fileSecretStore(join(this.stateDir, 'secrets.json'))
|
|
152
152
|
this.accounts = new Accounts({ dir: join(this.stateDir, 'accounts'), secrets, bus: this.bus })
|
|
153
153
|
this.approvals = new Approvals(this.stateDir, this.bus)
|
|
@@ -200,10 +200,11 @@ export class Harness {
|
|
|
200
200
|
broker: this.apps,
|
|
201
201
|
plugins: this.plugins,
|
|
202
202
|
resolveCapabilities: (caller, tools, cwd) => capabilities.resolve(this, { agentId: caller.agentId ?? null, projectId: caller.projectId ?? null, cwd, tools }),
|
|
203
|
-
factory: options.sessionFactory
|
|
203
|
+
factory: options.sessionFactory,
|
|
204
|
+
trust: this.trust
|
|
204
205
|
})
|
|
205
|
-
this.
|
|
206
|
-
this.terminals = new Terminals({ subagents: this.subagents, resolveCapabilities: (caller, tools, cwd) => capabilities.resolve(this, { agentId: caller.agentId ?? null, projectId: caller.projectId ?? null, cwd, tools }), env: this.env, stateDir: this.stateDir, accounts: this.accounts, runs: this.runs, approvals: this.approvals, 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, hub: this.hub })
|
|
206
|
+
this.refine = new Refinements({ root: () => this.env.root, bus: this.bus, trust: this.trust, host: () => this.refineHost, runtime: { root: () => this.env.root, runs: this.runs, run: (input) => this.sessions.run(input) } })
|
|
207
|
+
this.terminals = new Terminals({ trust: this.trust, subagents: this.subagents, resolveCapabilities: (caller, tools, cwd) => capabilities.resolve(this, { agentId: caller.agentId ?? null, projectId: caller.projectId ?? null, cwd, tools }), env: this.env, stateDir: this.stateDir, accounts: this.accounts, runs: this.runs, approvals: this.approvals, 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, hub: this.hub })
|
|
207
208
|
this.ready = this.apps.start().catch((error) => log('connected apps', error instanceof Error ? error.message : String(error)))
|
|
208
209
|
this.runs.listen((event, run) => {
|
|
209
210
|
this.accounts.onRunEvent(event, run)
|
|
@@ -212,8 +213,6 @@ export class Harness {
|
|
|
212
213
|
if ((event.type === 'completed' || event.type === 'failed') && run) {
|
|
213
214
|
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 })
|
|
214
215
|
}
|
|
215
|
-
if (event.type === 'completed' || event.type === 'failed' || (event.type === 'status.changed' && (event.payload as { status?: string }).status === 'interrupted')) this.maintenance.finishRun(event.runId, event.type !== 'completed')
|
|
216
|
-
this.maintenance.onRunEvent(event)
|
|
217
216
|
})
|
|
218
217
|
}
|
|
219
218
|
|
|
@@ -230,27 +229,28 @@ export class Harness {
|
|
|
230
229
|
}
|
|
231
230
|
|
|
232
231
|
/**
|
|
233
|
-
* Opens a data root:
|
|
234
|
-
* and refreshes the index. `agentIds`
|
|
235
|
-
*
|
|
232
|
+
* Opens a data root: moves an earlier Wiki's memory and facts aside (once), lays out the Wiki,
|
|
233
|
+
* settles runs that never ended, and refreshes the history index. `agentIds` is accepted for
|
|
234
|
+
* hosts written against earlier versions and is not used.
|
|
236
235
|
*/
|
|
237
|
-
open(dataRoot: string,
|
|
236
|
+
open(dataRoot: string, _agentIds: readonly string[] = []): Recovery {
|
|
238
237
|
if (this.hub.busy && this.env.dataRoot !== dataRoot) throw new HarnessError('hub_busy', 'Wait for the hub installation before changing the data location.')
|
|
239
238
|
if (this.env.dataRoot && this.env.dataRoot !== dataRoot) closeIndex(this.env.dataRoot)
|
|
240
239
|
this.env.dataRoot = dataRoot
|
|
241
240
|
this.hub.watch(dataRoot)
|
|
242
241
|
const root = dataRoot
|
|
243
|
-
this.hooks.watch(
|
|
242
|
+
this.hooks.watch(notes.wikiDir(root))
|
|
244
243
|
void this.hooks.reload()
|
|
245
|
-
for (const folder of ['registry/skills', 'registry/prompts', 'registry/rules', 'config', 'runs']) mkdirSync(join(root, folder), { recursive: true })
|
|
244
|
+
for (const folder of ['registry/skills', 'registry/prompts', 'registry/rules', 'config', 'runs', 'harnesses']) mkdirSync(join(root, folder), { recursive: true })
|
|
246
245
|
let migrationError: string | null = null
|
|
247
|
-
let
|
|
246
|
+
let legacyWiki: string | null = null
|
|
248
247
|
try {
|
|
249
|
-
|
|
248
|
+
legacyWiki = notes.moveLegacy(root)
|
|
249
|
+
if (legacyWiki) log(`moved the earlier Wiki's memory, facts and revisions to ${legacyWiki}`)
|
|
250
|
+
notes.ensureLayout(root, this.env.productName)
|
|
250
251
|
} catch (error) {
|
|
251
252
|
migrationError = error instanceof Error ? error.message : String(error)
|
|
252
|
-
|
|
253
|
-
log('Wiki migration failed', migrationError)
|
|
253
|
+
log('Wiki layout', migrationError)
|
|
254
254
|
}
|
|
255
255
|
try {
|
|
256
256
|
migrateConnectionScopes(root, this.stateDir)
|
|
@@ -259,55 +259,36 @@ export class Harness {
|
|
|
259
259
|
migrationError = migrationError ? `${migrationError}; connector scope migration: ${detail}` : detail
|
|
260
260
|
log('connector scope migration failed', detail)
|
|
261
261
|
}
|
|
262
|
-
if (!wikiMigrationError) {
|
|
263
|
-
try {
|
|
264
|
-
store.ensureLayout(root, this.env.productName)
|
|
265
|
-
// Every project has its fixed pages, and the generated indexes match the files.
|
|
266
|
-
for (const project of projectsOrNone(root)) store.ensureProjectNamespace(root, project.slug, project.name)
|
|
267
|
-
writeIndex(store.wikiDir(root))
|
|
268
|
-
} catch (error) {
|
|
269
|
-
log('Wiki layout', error instanceof Error ? error.message : String(error))
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
262
|
let interruptedRuns = 0
|
|
273
263
|
try {
|
|
274
264
|
interruptedRuns = markInterruptedRuns(root, `${this.env.productName} stopped before the run settled.`)
|
|
275
265
|
} catch (error) {
|
|
276
266
|
log('run recovery', error instanceof Error ? error.message : String(error))
|
|
277
267
|
}
|
|
278
|
-
let reindexedPages: number
|
|
279
|
-
try {
|
|
280
|
-
reindexedPages = wikiIndex.ensure(root)
|
|
281
|
-
} catch (error) {
|
|
282
|
-
log('index', error instanceof Error ? error.message : String(error))
|
|
283
|
-
reindexedPages = countPages(root)
|
|
284
|
-
}
|
|
285
268
|
try {
|
|
286
|
-
// Runs recorded while nothing was running
|
|
269
|
+
// Runs recorded while nothing was running become searchable.
|
|
287
270
|
recall.sync(root)
|
|
288
271
|
} catch (error) {
|
|
289
272
|
log('recall', error instanceof Error ? error.message : String(error))
|
|
290
273
|
}
|
|
291
|
-
this.recovery = { interruptedRuns, reindexedPages, migrationError }
|
|
292
|
-
this.maintenance.start()
|
|
274
|
+
this.recovery = { interruptedRuns, reindexedPages: safeCount(root), migrationError, legacyWiki }
|
|
293
275
|
return this.recovery
|
|
294
276
|
}
|
|
295
277
|
|
|
296
|
-
/**
|
|
278
|
+
/** Rebuilds the history index from the run records. Returns the notes in the Wiki. */
|
|
297
279
|
rebuildIndex(): number {
|
|
298
|
-
const pages = wikiIndex.rebuild(this.env.root)
|
|
299
280
|
recall.sync(this.env.root)
|
|
300
|
-
|
|
301
|
-
|
|
281
|
+
const count = safeCount(this.env.root)
|
|
282
|
+
this.recovery = { ...this.recovery, reindexedPages: count }
|
|
283
|
+
return count
|
|
302
284
|
}
|
|
303
285
|
|
|
304
|
-
/** Stops live runs and releases the index. */
|
|
286
|
+
/** Stops live runs and refinements and releases the index. */
|
|
305
287
|
async close(): Promise<void> {
|
|
306
288
|
this.hub.close()
|
|
307
|
-
this.
|
|
289
|
+
await this.refine.close()
|
|
308
290
|
await this.terminals.close()
|
|
309
291
|
await this.runs.interruptAll()
|
|
310
|
-
await this.maintenance.settled()
|
|
311
292
|
await this.hooks.settled().catch(() => undefined)
|
|
312
293
|
this.hooks.close()
|
|
313
294
|
await this.apps.close()
|
|
@@ -315,6 +296,14 @@ export class Harness {
|
|
|
315
296
|
}
|
|
316
297
|
}
|
|
317
298
|
|
|
299
|
+
function safeCount(root: string): number {
|
|
300
|
+
try {
|
|
301
|
+
return notes.paths(root).length
|
|
302
|
+
} catch {
|
|
303
|
+
return 0
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
318
307
|
export function createHarness(options: HarnessOptions): Harness {
|
|
319
308
|
const harness = new Harness(options)
|
|
320
309
|
if (options.dataRoot) harness.open(options.dataRoot)
|
|
@@ -9,7 +9,7 @@ import { newId, writeJson } from '../core/fs'
|
|
|
9
9
|
import { closeIndex } from '../core/index'
|
|
10
10
|
import { runRecordSchema, type RunRecord } from '../sessions/contracts'
|
|
11
11
|
import { parseEvent } from '../sessions/runStore'
|
|
12
|
-
import type { CallerContext } from '
|
|
12
|
+
import type { CallerContext } from '../core/caller'
|
|
13
13
|
import { passages, read, readTool, search, searchTool, sync } from './recall'
|
|
14
14
|
|
|
15
15
|
const run = (id: string, mode: string, session: string, project: string | null, goal: string): RunRecord =>
|
|
@@ -12,7 +12,7 @@ import { charCount, takeChars } from '../core/text'
|
|
|
12
12
|
import type { RunEvent, RunRecord } from '../sessions/contracts'
|
|
13
13
|
import { readAllRuns } from '../sessions/runIndex'
|
|
14
14
|
import { findRun, readEvents } from '../sessions/runStore'
|
|
15
|
-
import { isSession, projectOf, type CallerContext } from '
|
|
15
|
+
import { isSession, projectOf, type CallerContext } from '../core/caller'
|
|
16
16
|
|
|
17
17
|
const PASSAGE_LIMIT = 6000
|
|
18
18
|
const READ_LIMIT = 12_000
|
|
@@ -8,9 +8,9 @@ import { closeIndex } from '../core/index'
|
|
|
8
8
|
import { writeJson } from '../core/fs'
|
|
9
9
|
import { runRecordSchema, type RunEvent, type RunRecord } from '../sessions/contracts'
|
|
10
10
|
import { appendEvent, saveRun } from '../sessions/runStore'
|
|
11
|
-
import {
|
|
12
|
-
import type { CallerContext } from '
|
|
13
|
-
import { dispatch } from '
|
|
11
|
+
import { HARNESS_TOOLS } from '../tools/definitions'
|
|
12
|
+
import type { CallerContext } from '../core/caller'
|
|
13
|
+
import { dispatch } from '../tools/dispatch'
|
|
14
14
|
import { readTool, searchTool } from './trajectory'
|
|
15
15
|
|
|
16
16
|
const roots: string[] = []
|
|
@@ -170,7 +170,7 @@ test('trajectory calls reject malformed filters and missing current runs', () =>
|
|
|
170
170
|
test('the model tool registry exposes both trajectory calls through Wiki dispatch', () => {
|
|
171
171
|
const { root, env } = setup()
|
|
172
172
|
save(root, run('registered-run'))
|
|
173
|
-
const calls =
|
|
173
|
+
const calls = HARNESS_TOOLS.map((tool) => tool.call)
|
|
174
174
|
expect(calls).toContain('trajectory.search')
|
|
175
175
|
expect(calls).toContain('trajectory.read')
|
|
176
176
|
expect(JSON.parse(dispatch(env, agent, 'trajectory.search', {})).runs[0].id).toBe('registered-run')
|
|
@@ -8,7 +8,7 @@ import { scrubSensitive } from '../mcp/connections'
|
|
|
8
8
|
import { isActiveStatus, runStatusSchema, type RunEvent, type RunRecord } from '../sessions/contracts'
|
|
9
9
|
import { readAllRuns } from '../sessions/runIndex'
|
|
10
10
|
import { findRun, snapshot } from '../sessions/runStore'
|
|
11
|
-
import type { CallerContext } from '
|
|
11
|
+
import type { CallerContext } from '../core/caller'
|
|
12
12
|
|
|
13
13
|
const SEARCH_DEFAULT = 10
|
|
14
14
|
const SEARCH_MAX = 30
|