@xingular/harness 0.1.7 → 1.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 +97 -1
- package/LICENSE +202 -0
- package/NOTICE.md +22 -0
- package/README.md +187 -85
- package/THIRD_PARTY_LICENSES.md +942 -0
- package/dist/cli.js +13701 -13498
- package/dist/sdk/accounts/accounts.d.ts +165 -0
- package/dist/sdk/accounts/accounts.js +730 -0
- package/dist/sdk/accounts/choices.d.ts +23 -0
- package/dist/sdk/accounts/choices.js +62 -0
- package/dist/sdk/accounts/env.d.ts +78 -0
- package/dist/sdk/accounts/env.js +294 -0
- package/dist/sdk/accounts/secrets.d.ts +16 -0
- package/dist/sdk/accounts/secrets.js +40 -0
- package/dist/sdk/accounts/store.d.ts +114 -0
- package/dist/sdk/accounts/store.js +255 -0
- package/dist/sdk/browser/config.d.ts +16 -0
- package/dist/sdk/browser/config.js +22 -0
- package/dist/sdk/browser/diagnostics.d.ts +4 -0
- package/dist/sdk/browser/diagnostics.js +38 -0
- package/dist/sdk/browser/index.d.ts +17 -0
- package/dist/sdk/browser/index.js +65 -0
- package/dist/sdk/browser/network.d.ts +10 -0
- package/dist/sdk/browser/network.js +59 -0
- package/dist/sdk/browser/session.d.ts +84 -0
- package/dist/sdk/browser/session.js +192 -0
- package/dist/sdk/browser/tools.d.ts +8 -0
- package/dist/sdk/browser/tools.js +329 -0
- package/dist/sdk/capabilities/api.d.ts +22 -0
- package/dist/sdk/capabilities/api.js +54 -0
- package/dist/sdk/capabilities/runSnapshot.d.ts +18 -0
- package/dist/sdk/capabilities/runSnapshot.js +47 -0
- package/dist/sdk/capabilities/types.d.ts +29 -0
- package/dist/sdk/capabilities/types.js +1 -0
- package/dist/sdk/cli.d.ts +2 -0
- package/dist/sdk/cli.js +142 -0
- package/dist/sdk/core/bus.d.ts +23 -0
- package/dist/sdk/core/bus.js +76 -0
- package/dist/sdk/core/caller.d.ts +22 -0
- package/dist/sdk/core/caller.js +16 -0
- package/dist/sdk/core/changes.d.ts +15 -0
- package/dist/sdk/core/changes.js +15 -0
- package/dist/sdk/core/effort.d.ts +2 -0
- package/dist/sdk/core/effort.js +1 -0
- package/dist/sdk/core/env.d.ts +18 -0
- package/dist/sdk/core/env.js +8 -0
- package/dist/sdk/core/errors.d.ts +15 -0
- package/dist/sdk/core/errors.js +24 -0
- package/dist/sdk/core/frontmatter.d.ts +12 -0
- package/dist/sdk/core/frontmatter.js +50 -0
- package/dist/sdk/core/fs.d.ts +18 -0
- package/dist/sdk/core/fs.js +106 -0
- package/dist/sdk/core/index.d.ts +24 -0
- package/dist/sdk/core/index.js +187 -0
- package/dist/sdk/core/path.d.ts +7 -0
- package/dist/sdk/core/path.js +79 -0
- package/dist/sdk/core/projects.d.ts +18 -0
- package/dist/sdk/core/projects.js +67 -0
- package/dist/sdk/core/secrets.d.ts +2 -0
- package/dist/sdk/core/secrets.js +5 -0
- package/dist/sdk/core/text.d.ts +7 -0
- package/dist/sdk/core/text.js +40 -0
- package/dist/sdk/doctor/checks.d.ts +5 -0
- package/dist/sdk/doctor/checks.js +208 -0
- package/dist/sdk/doctor/probe.d.ts +4 -0
- package/dist/sdk/doctor/probe.js +92 -0
- package/dist/sdk/doctor/service.d.ts +23 -0
- package/dist/sdk/doctor/service.js +144 -0
- package/dist/sdk/doctor/types.d.ts +141 -0
- package/dist/sdk/doctor/types.js +29 -0
- package/dist/sdk/harness.d.ts +133 -0
- package/dist/sdk/harness.js +299 -0
- package/dist/sdk/history/lessonPrompt.d.ts +2 -0
- package/dist/sdk/history/lessonPrompt.js +15 -0
- package/dist/sdk/history/lessons.d.ts +52 -0
- package/dist/sdk/history/lessons.js +173 -0
- package/dist/sdk/history/recall.d.ts +68 -0
- package/dist/sdk/history/recall.js +199 -0
- package/dist/sdk/history/trajectory.d.ts +7 -0
- package/dist/sdk/history/trajectory.js +216 -0
- package/dist/sdk/hub/catalog.json +123 -0
- package/dist/sdk/hub/cli.d.ts +7 -0
- package/dist/sdk/hub/cli.js +81 -0
- package/dist/sdk/hub/hub.d.ts +74 -0
- package/dist/sdk/hub/hub.js +474 -0
- package/dist/sdk/hub/schema.d.ts +280 -0
- package/dist/sdk/hub/schema.js +78 -0
- package/dist/sdk/index.d.ts +63 -0
- package/dist/sdk/index.js +48 -0
- package/dist/sdk/mcp/connections.d.ts +83 -0
- package/dist/sdk/mcp/connections.js +669 -0
- package/dist/sdk/mcp/manager.d.ts +26 -0
- package/dist/sdk/mcp/manager.js +124 -0
- package/dist/sdk/mcp/oauth.d.ts +35 -0
- package/dist/sdk/mcp/oauth.js +171 -0
- package/dist/sdk/mcp/scopeMigration.d.ts +19 -0
- package/dist/sdk/mcp/scopeMigration.js +198 -0
- package/dist/sdk/mcp/service.d.ts +160 -0
- package/dist/sdk/mcp/service.js +510 -0
- package/dist/sdk/mcp/types.d.ts +131 -0
- package/dist/sdk/mcp/types.js +49 -0
- package/dist/sdk/pi/runtime.d.ts +13 -0
- package/dist/sdk/pi/runtime.js +31 -0
- package/dist/sdk/plugins/api.d.ts +66 -0
- package/dist/sdk/plugins/api.js +155 -0
- package/dist/sdk/plugins/attachments.d.ts +46 -0
- package/dist/sdk/plugins/attachments.js +111 -0
- package/dist/sdk/plugins/builtin.d.ts +6 -0
- package/dist/sdk/plugins/builtin.js +68 -0
- package/dist/sdk/plugins/catalogue.d.ts +29 -0
- package/dist/sdk/plugins/catalogue.js +196 -0
- package/dist/sdk/plugins/cli.d.ts +13 -0
- package/dist/sdk/plugins/cli.js +187 -0
- package/dist/sdk/plugins/discover.d.ts +42 -0
- package/dist/sdk/plugins/discover.js +115 -0
- package/dist/sdk/plugins/firstParty.d.ts +50 -0
- package/dist/sdk/plugins/firstParty.js +113 -0
- package/dist/sdk/plugins/hooks.d.ts +94 -0
- package/dist/sdk/plugins/hooks.js +162 -0
- package/dist/sdk/plugins/identity.d.ts +19 -0
- package/dist/sdk/plugins/identity.js +48 -0
- package/dist/sdk/plugins/install.d.ts +20 -0
- package/dist/sdk/plugins/install.js +110 -0
- package/dist/sdk/plugins/inventory.d.ts +22 -0
- package/dist/sdk/plugins/inventory.js +110 -0
- package/dist/sdk/plugins/manifest.d.ts +13 -0
- package/dist/sdk/plugins/manifest.js +56 -0
- package/dist/sdk/plugins/plugins.d.ts +104 -0
- package/dist/sdk/plugins/plugins.js +164 -0
- package/dist/sdk/plugins/runtime.d.ts +9 -0
- package/dist/sdk/plugins/runtime.js +95 -0
- package/dist/sdk/plugins/trust.d.ts +44 -0
- package/dist/sdk/plugins/trust.js +120 -0
- package/dist/sdk/plugins/types.d.ts +159 -0
- package/dist/sdk/plugins/types.js +7 -0
- package/dist/sdk/prompt/compile.d.ts +174 -0
- package/dist/sdk/prompt/compile.js +396 -0
- package/dist/sdk/refine/cli.d.ts +4 -0
- package/dist/sdk/refine/cli.js +186 -0
- package/dist/sdk/refine/demo.d.ts +21 -0
- package/dist/sdk/refine/demo.js +114 -0
- package/dist/sdk/refine/evidence.d.ts +55 -0
- package/dist/sdk/refine/evidence.js +330 -0
- package/dist/sdk/refine/host.d.ts +51 -0
- package/dist/sdk/refine/host.js +76 -0
- package/dist/sdk/refine/ledger.d.ts +48 -0
- package/dist/sdk/refine/ledger.js +76 -0
- package/dist/sdk/refine/library.d.ts +55 -0
- package/dist/sdk/refine/library.js +177 -0
- package/dist/sdk/refine/pi.d.ts +72 -0
- package/dist/sdk/refine/pi.js +248 -0
- package/dist/sdk/refine/prompts.d.ts +45 -0
- package/dist/sdk/refine/prompts.js +390 -0
- package/dist/sdk/refine/recursion.d.ts +113 -0
- package/dist/sdk/refine/recursion.js +258 -0
- package/dist/sdk/refine/runner.d.ts +81 -0
- package/dist/sdk/refine/runner.js +150 -0
- package/dist/sdk/refine/selection.d.ts +57 -0
- package/dist/sdk/refine/selection.js +251 -0
- package/dist/sdk/refine/service.d.ts +84 -0
- package/dist/sdk/refine/service.js +165 -0
- package/dist/sdk/refine/suite.d.ts +67 -0
- package/dist/sdk/refine/suite.js +174 -0
- package/dist/sdk/refine/trajectory.d.ts +9 -0
- package/dist/sdk/refine/trajectory.js +68 -0
- package/dist/sdk/refine/types.d.ts +77 -0
- package/dist/sdk/refine/types.js +10 -0
- package/dist/sdk/server/methods.d.ts +3 -0
- package/dist/sdk/server/methods.js +370 -0
- package/dist/sdk/server/remote.d.ts +19 -0
- package/dist/sdk/server/remote.js +84 -0
- package/dist/sdk/server/rpc.d.ts +20 -0
- package/dist/sdk/server/rpc.js +102 -0
- package/dist/sdk/server/serve.d.ts +10 -0
- package/dist/sdk/server/serve.js +18 -0
- package/dist/sdk/sessions/approvals.d.ts +47 -0
- package/dist/sdk/sessions/approvals.js +119 -0
- package/dist/sdk/sessions/continuation.d.ts +48 -0
- package/dist/sdk/sessions/continuation.js +85 -0
- package/dist/sdk/sessions/contracts.d.ts +534 -0
- package/dist/sdk/sessions/contracts.js +299 -0
- package/dist/sdk/sessions/extensionUi.d.ts +19 -0
- package/dist/sdk/sessions/extensionUi.js +64 -0
- package/dist/sdk/sessions/history.d.ts +16 -0
- package/dist/sdk/sessions/history.js +68 -0
- package/dist/sdk/sessions/lineageJoin.d.ts +2 -0
- package/dist/sdk/sessions/lineageJoin.js +61 -0
- package/dist/sdk/sessions/planMode.d.ts +36 -0
- package/dist/sdk/sessions/planMode.js +84 -0
- package/dist/sdk/sessions/policy.d.ts +43 -0
- package/dist/sdk/sessions/policy.js +1 -0
- package/dist/sdk/sessions/pricing.d.ts +34 -0
- package/dist/sdk/sessions/pricing.js +40 -0
- package/dist/sdk/sessions/reasoningSummaries.d.ts +11 -0
- package/dist/sdk/sessions/reasoningSummaries.js +61 -0
- package/dist/sdk/sessions/registry.d.ts +102 -0
- package/dist/sdk/sessions/registry.js +383 -0
- package/dist/sdk/sessions/runIndex.d.ts +21 -0
- package/dist/sdk/sessions/runIndex.js +65 -0
- package/dist/sdk/sessions/runStore.d.ts +66 -0
- package/dist/sdk/sessions/runStore.js +335 -0
- package/dist/sdk/sessions/runner.d.ts +213 -0
- package/dist/sdk/sessions/runner.js +589 -0
- package/dist/sdk/sessions/sessions.d.ts +140 -0
- package/dist/sdk/sessions/sessions.js +394 -0
- package/dist/sdk/sessions/subagentService.d.ts +41 -0
- package/dist/sdk/sessions/subagentService.js +247 -0
- package/dist/sdk/sessions/subagents.d.ts +50 -0
- package/dist/sdk/sessions/subagents.js +68 -0
- package/dist/sdk/terminal/accountsMenu.d.ts +46 -0
- package/dist/sdk/terminal/accountsMenu.js +392 -0
- package/dist/sdk/terminal/cli.d.ts +20 -0
- package/dist/sdk/terminal/cli.js +121 -0
- package/dist/sdk/terminal/client.d.ts +15 -0
- package/dist/sdk/terminal/client.js +126 -0
- package/dist/sdk/terminal/control.d.ts +4 -0
- package/dist/sdk/terminal/control.js +25 -0
- package/dist/sdk/terminal/controller.d.ts +72 -0
- package/dist/sdk/terminal/controller.js +351 -0
- package/dist/sdk/terminal/hubMenu.d.ts +10 -0
- package/dist/sdk/terminal/hubMenu.js +34 -0
- package/dist/sdk/terminal/launch.d.ts +5 -0
- package/dist/sdk/terminal/launch.js +27 -0
- package/dist/sdk/terminal/logs.d.ts +14 -0
- package/dist/sdk/terminal/logs.js +59 -0
- package/dist/sdk/terminal/piExtension.d.ts +6 -0
- package/dist/sdk/terminal/piExtension.js +392 -0
- package/dist/sdk/terminal/pluginMenu.d.ts +34 -0
- package/dist/sdk/terminal/pluginMenu.js +101 -0
- package/dist/sdk/terminal/socket.d.ts +70 -0
- package/dist/sdk/terminal/socket.js +256 -0
- package/dist/sdk/terminal/standalone.d.ts +18 -0
- package/dist/sdk/terminal/standalone.js +146 -0
- package/dist/sdk/terminal/terminals.d.ts +176 -0
- package/dist/sdk/terminal/terminals.js +767 -0
- package/dist/sdk/terminal/trajectory.d.ts +10 -0
- package/dist/sdk/terminal/trajectory.js +30 -0
- package/dist/sdk/terminal/tui/chrome.d.ts +64 -0
- package/dist/sdk/terminal/tui/chrome.js +169 -0
- package/dist/sdk/terminal/tui/clipboardFiles.d.ts +11 -0
- package/dist/sdk/terminal/tui/clipboardFiles.js +74 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +7 -0
- package/dist/sdk/terminal/tui/commandPages.js +63 -0
- package/dist/sdk/terminal/tui/commands.d.ts +42 -0
- package/dist/sdk/terminal/tui/commands.js +276 -0
- package/dist/sdk/terminal/tui/dialogGeometry.d.ts +27 -0
- package/dist/sdk/terminal/tui/dialogGeometry.js +50 -0
- package/dist/sdk/terminal/tui/dialogs.d.ts +75 -0
- package/dist/sdk/terminal/tui/dialogs.js +363 -0
- package/dist/sdk/terminal/tui/keybindings.d.ts +11 -0
- package/dist/sdk/terminal/tui/keybindings.js +49 -0
- package/dist/sdk/terminal/tui/list.d.ts +73 -0
- package/dist/sdk/terminal/tui/list.js +196 -0
- package/dist/sdk/terminal/tui/modeChoices.d.ts +42 -0
- package/dist/sdk/terminal/tui/modeChoices.js +70 -0
- package/dist/sdk/terminal/tui/modelPicker.d.ts +55 -0
- package/dist/sdk/terminal/tui/modelPicker.js +155 -0
- package/dist/sdk/terminal/tui/mouse.d.ts +3 -0
- package/dist/sdk/terminal/tui/mouse.js +3 -0
- package/dist/sdk/terminal/tui/page.d.ts +14 -0
- package/dist/sdk/terminal/tui/page.js +1 -0
- package/dist/sdk/terminal/tui/pageOverlay.d.ts +27 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +145 -0
- package/dist/sdk/terminal/tui/pages/accountsPage.d.ts +54 -0
- package/dist/sdk/terminal/tui/pages/accountsPage.js +181 -0
- package/dist/sdk/terminal/tui/pages/componentsPage.d.ts +47 -0
- package/dist/sdk/terminal/tui/pages/componentsPage.js +119 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.d.ts +37 -0
- package/dist/sdk/terminal/tui/pages/doctorPage.js +145 -0
- package/dist/sdk/terminal/tui/pages/hubPage.d.ts +39 -0
- package/dist/sdk/terminal/tui/pages/hubPage.js +128 -0
- package/dist/sdk/terminal/tui/pages/logPage.d.ts +38 -0
- package/dist/sdk/terminal/tui/pages/logPage.js +189 -0
- package/dist/sdk/terminal/tui/pages/permissionsPage.d.ts +44 -0
- package/dist/sdk/terminal/tui/pages/permissionsPage.js +124 -0
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +57 -0
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +243 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.d.ts +55 -0
- package/dist/sdk/terminal/tui/pages/settingsPage.js +88 -0
- package/dist/sdk/terminal/tui/pages/trajectoryPage.d.ts +58 -0
- package/dist/sdk/terminal/tui/pages/trajectoryPage.js +377 -0
- package/dist/sdk/terminal/tui/piAdapter.d.ts +18 -0
- package/dist/sdk/terminal/tui/piAdapter.js +48 -0
- package/dist/sdk/terminal/tui/planMode.d.ts +2 -0
- package/dist/sdk/terminal/tui/planMode.js +2 -0
- package/dist/sdk/terminal/tui/questionPicker.d.ts +20 -0
- package/dist/sdk/terminal/tui/questionPicker.js +150 -0
- package/dist/sdk/terminal/tui/questions.d.ts +19 -0
- package/dist/sdk/terminal/tui/questions.js +42 -0
- package/dist/sdk/terminal/tui/runtime.d.ts +114 -0
- package/dist/sdk/terminal/tui/runtime.js +441 -0
- package/dist/sdk/terminal/tui/search.d.ts +22 -0
- package/dist/sdk/terminal/tui/search.js +60 -0
- package/dist/sdk/terminal/tui/selection.d.ts +16 -0
- package/dist/sdk/terminal/tui/selection.js +16 -0
- package/dist/sdk/terminal/tui/sessionUi.d.ts +3 -0
- package/dist/sdk/terminal/tui/sessionUi.js +30 -0
- package/dist/sdk/terminal/tui/settingsModel.d.ts +12 -0
- package/dist/sdk/terminal/tui/settingsModel.js +84 -0
- package/dist/sdk/terminal/tui/sideQuestion.d.ts +53 -0
- package/dist/sdk/terminal/tui/sideQuestion.js +197 -0
- package/dist/sdk/terminal/tui/skillInvocation.d.ts +9 -0
- package/dist/sdk/terminal/tui/skillInvocation.js +43 -0
- package/dist/sdk/terminal/tui/status.d.ts +18 -0
- package/dist/sdk/terminal/tui/status.js +67 -0
- package/dist/sdk/terminal/tui/theme.d.ts +45 -0
- package/dist/sdk/terminal/tui/theme.js +176 -0
- package/dist/sdk/terminal/tui/treeModel.d.ts +79 -0
- package/dist/sdk/terminal/tui/treeModel.js +483 -0
- package/dist/sdk/terminal/tui/treePicker.d.ts +49 -0
- package/dist/sdk/terminal/tui/treePicker.js +251 -0
- package/dist/sdk/terminal/tui/ui.d.ts +47 -0
- package/dist/sdk/terminal/tui/ui.js +148 -0
- package/dist/sdk/terminal/tui/workingStatus.d.ts +9 -0
- package/dist/sdk/terminal/tui/workingStatus.js +229 -0
- package/dist/sdk/tools/askUser.d.ts +6 -0
- package/dist/sdk/tools/askUser.js +74 -0
- package/dist/sdk/tools/checklist.d.ts +9 -0
- package/dist/sdk/tools/checklist.js +54 -0
- package/dist/sdk/tools/definitions.d.ts +30 -0
- package/dist/sdk/tools/definitions.js +117 -0
- package/dist/sdk/tools/dispatch.d.ts +16 -0
- package/dist/sdk/tools/dispatch.js +67 -0
- package/dist/sdk/tools/harnessTools.d.ts +6 -0
- package/dist/sdk/tools/harnessTools.js +20 -0
- package/dist/sdk/tools/sessionName.d.ts +7 -0
- package/dist/sdk/tools/sessionName.js +31 -0
- package/dist/sdk/tools/spawnSubagent.d.ts +4 -0
- package/dist/sdk/tools/spawnSubagent.js +49 -0
- package/dist/sdk/tools/types.d.ts +48 -0
- package/dist/sdk/tools/types.js +1 -0
- package/dist/sdk/variant/components.d.ts +34 -0
- package/dist/sdk/variant/components.js +230 -0
- package/dist/sdk/variant/materialise.d.ts +66 -0
- package/dist/sdk/variant/materialise.js +81 -0
- package/dist/sdk/variant/migrate.d.ts +4 -0
- package/dist/sdk/variant/migrate.js +41 -0
- package/dist/sdk/variant/seed.d.ts +20 -0
- package/dist/sdk/variant/seed.js +116 -0
- package/dist/sdk/variant/store.d.ts +82 -0
- package/dist/sdk/variant/store.js +151 -0
- package/dist/sdk/variant/surface.d.ts +56 -0
- package/dist/sdk/variant/surface.js +81 -0
- package/dist/sdk/variant/trust.d.ts +27 -0
- package/dist/sdk/variant/trust.js +64 -0
- package/dist/sdk/variant/variant.d.ts +71 -0
- package/dist/sdk/variant/variant.js +331 -0
- package/dist/sdk/web/markdown.d.ts +10 -0
- package/dist/sdk/web/markdown.js +66 -0
- package/dist/sdk/web/safeRequest.d.ts +34 -0
- package/dist/sdk/web/safeRequest.js +175 -0
- package/dist/sdk/web/search.d.ts +53 -0
- package/dist/sdk/web/search.js +168 -0
- package/dist/sdk/web/text.d.ts +2 -0
- package/dist/sdk/web/text.js +7 -0
- package/dist/sdk/web/tools.d.ts +22 -0
- package/dist/sdk/web/tools.js +195 -0
- package/dist/sdk/web/turndown-plugin-gfm.d.ts +4 -0
- package/dist/sdk/wiki/notes.d.ts +68 -0
- package/dist/sdk/wiki/notes.js +297 -0
- package/dist/sdk/world/actions.d.ts +23 -0
- package/dist/sdk/world/actions.js +31 -0
- package/dist/sdk/world/cli.d.ts +12 -0
- package/dist/sdk/world/cli.js +166 -0
- package/dist/sdk/world/images.d.ts +20 -0
- package/dist/sdk/world/images.js +48 -0
- package/dist/sdk/world/untrusted.d.ts +12 -0
- package/dist/sdk/world/untrusted.js +20 -0
- package/dist/sdk/world/world.d.ts +65 -0
- package/dist/sdk/world/world.js +80 -0
- package/dist/terminal-opentui.js +13612 -0
- package/dist/terminal-pi.js +808 -151
- package/docs/README.md +22 -0
- package/docs/architecture/00-overview.md +159 -0
- package/docs/architecture/01-extension-points.md +78 -0
- package/docs/architecture/02-sessions-and-runs.md +146 -0
- package/docs/architecture/03-variants.md +95 -0
- package/docs/architecture/04-prompt.md +102 -0
- package/docs/architecture/05-refinement.md +107 -0
- package/docs/architecture/06-plugins-and-capabilities.md +93 -0
- package/docs/architecture/07-tools-and-gate.md +88 -0
- package/docs/architecture/08-memory-and-history.md +94 -0
- package/docs/architecture/09-accounts-and-connectors.md +90 -0
- package/docs/architecture/10-server-protocol.md +84 -0
- package/docs/architecture/11-terminal.md +81 -0
- package/docs/architecture/12-storage.md +61 -0
- package/docs/architecture/13-world-tools.md +23 -0
- package/docs/architecture/README.md +114 -0
- package/docs/architecture/proposals/PROP-0001-reporting-section.md +106 -0
- package/docs/architecture/proposals/PROP-0002-retain-rollout-workspaces.md +104 -0
- package/docs/architecture/proposals/PROP-0003-executor-settings.md +81 -0
- package/docs/architecture/proposals/PROP-0004-flexible-reporting-guidance.md +71 -0
- package/docs/architecture/proposals/PROP-0005-xharness-cli-name.md +78 -0
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +68 -0
- package/docs/architecture/proposals/PROP-0007-run-continuation.md +80 -0
- package/docs/architecture/proposals/PROP-0008-lessons-from-runs.md +80 -0
- package/docs/architecture/proposals/PROP-0009-foundation-and-variants.md +78 -0
- package/docs/architecture/proposals/PROP-0010-session-names.md +73 -0
- package/docs/architecture/proposals/PROP-0011-doctor.md +63 -0
- package/docs/architecture/proposals/PROP-0012-release-baseline.md +56 -0
- package/docs/architecture/proposals/README.md +76 -0
- package/docs/decisions/ADR-0002-native-subagent-lifecycle.md +49 -0
- package/docs/decisions/ADR-0003-shared-asynchronous-subagents.md +70 -0
- package/docs/decisions/ADR-0004-fullscreen-terminal-layout.md +50 -0
- package/docs/decisions/ADR-0005-app-provided-oauth-clients.md +54 -0
- package/docs/decisions/ADR-0006-opentui-only-terminal.md +47 -0
- package/docs/decisions/ADR-0007-reporting-section.md +33 -0
- package/docs/decisions/ADR-0008-flexible-reporting-guidance.md +38 -0
- package/docs/decisions/ADR-0009-xharness-command.md +38 -0
- package/docs/decisions/ADR-0010-lessons-in-the-wiki.md +31 -0
- package/docs/decisions/ADR-0011-foundation-and-variants.md +28 -0
- package/docs/decisions/ADR-0012-native-session-names.md +34 -0
- package/docs/decisions/ADR-0013-doctor.md +27 -0
- package/docs/decisions/ADR-0014-public-release-baseline.md +21 -0
- package/docs/doctor.md +74 -0
- package/docs/experiments/EXP-0001-terminal-input-latency/plan.md +81 -0
- package/docs/experiments/EXP-0001-terminal-input-latency/results.md +46 -0
- package/docs/experiments/EXP-0002-opentui-typing-latency/plan.md +70 -0
- package/docs/experiments/EXP-0002-opentui-typing-latency/results.md +92 -0
- package/docs/features/README.md +20 -0
- package/docs/features/automations/README.md +41 -0
- package/docs/features/cores/README.md +39 -0
- package/docs/features/google-harness2/README.md +52 -0
- package/docs/features/integrations/README.md +40 -0
- package/docs/features/interfaces/README.md +29 -0
- package/docs/features/memory/README.md +28 -0
- package/docs/releases/1.0.0.md +40 -0
- package/docs/releases/README.md +36 -0
- package/docs/study/00-index.md +78 -0
- package/docs/study/arms/00-matrix.md +20 -0
- package/docs/study/arms/01-system-prompt.md +211 -0
- package/docs/study/arms/02-tools.md +79 -0
- package/docs/study/arms/03-subagents.md +90 -0
- package/docs/study/arms/04-skills.md +75 -0
- package/docs/study/arms/05-context-management.md +90 -0
- package/docs/study/arms/06-memory.md +90 -0
- package/docs/study/arms/07-governance.md +71 -0
- package/docs/study/arms/08-execution.md +69 -0
- package/docs/study/arms/09-observability.md +60 -0
- package/docs/study/arms/10-refinement.md +110 -0
- package/docs/study/btw-comparison.md +37 -0
- package/docs/study/feature-gap-review.md +42 -0
- package/docs/study/insights.md +123 -0
- package/docs/study/roadmap.md +72 -0
- package/docs/study/sources/claude-code.md +44 -0
- package/docs/study/sources/deepseek-harness.md +102 -0
- package/docs/study/sources/harness2.md +50 -0
- package/docs/study/sources/kimi-cli.md +42 -0
- package/docs/study/sources/omp.md +93 -0
- package/docs/study/sources/opencode.md +43 -0
- package/docs/study/sources/openhands.md +46 -0
- package/docs/study/sources/smolagents.md +40 -0
- package/docs/study/sources/swe-agent.md +34 -0
- package/docs/system-prompt-design.md +196 -0
- package/docs/terminal-tui.md +473 -56
- package/package.json +43 -13
- package/docs/evidence/terminal-2026-10-17/120x40-completed-transcript.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/40x20-settings-interaction.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/40x20-settings-keyboard.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/40x20-welcome.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/80x24-approval-0.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/80x24-question.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17/verification.json +0 -130
- package/docs/evidence/terminal-2026-10-17-followup/120x40-completed-transcript.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/40x20-settings-interaction.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/40x20-settings-keyboard.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/40x20-welcome.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/80x24-approval-0.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/80x24-plugin-light.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/80x24-question-batch.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/80x24-question.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-approval-0.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-plugin-light.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-question-batch.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/80x24-question.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/app-bundle/verification.json +0 -95
- package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-approval-0.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-plugin-light.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-question-batch.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/80x24-question.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/README.md +0 -27
- package/docs/evidence/terminal-2026-10-17-followup/global-install/shell-command-80x24.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/global-install/shell-command.json +0 -18
- package/docs/evidence/terminal-2026-10-17-followup/global-install/verification.json +0 -95
- package/docs/evidence/terminal-2026-10-17-followup/harness-reference-80x24.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/harness-reference-final-80x24.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/harness-reference-scenario.json +0 -60
- package/docs/evidence/terminal-2026-10-17-followup/live-codex/README.md +0 -24
- package/docs/evidence/terminal-2026-10-17-followup/live-codex/complete-80x24.ansi.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/live-codex/verification.json +0 -57
- package/docs/evidence/terminal-2026-10-17-followup/omp-reference-80x24-early.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/omp-reference-80x24.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/omp-reference-scenario.json +0 -58
- package/docs/evidence/terminal-2026-10-17-followup/omp-supplemental-static.json.gz +0 -0
- package/docs/evidence/terminal-2026-10-17-followup/rea-artifacts.json +0 -320
- package/docs/evidence/terminal-2026-10-17-followup/reference-comparison.json +0 -30
- package/docs/evidence/terminal-2026-10-17-followup/reference-comparison.md +0 -133
- package/docs/evidence/terminal-2026-10-17-followup/reference-source-observations.json +0 -20
- package/docs/evidence/terminal-2026-10-17-followup/verification.json +0 -207
- package/src/accounts/accounts.test.ts +0 -333
- package/src/accounts/accounts.ts +0 -658
- package/src/accounts/env.test.ts +0 -95
- package/src/accounts/env.ts +0 -338
- package/src/accounts/secrets.ts +0 -50
- package/src/accounts/store.ts +0 -338
- package/src/capabilities/api.ts +0 -75
- package/src/capabilities/runSnapshot.ts +0 -59
- package/src/capabilities/types.ts +0 -34
- package/src/cli.ts +0 -118
- package/src/core/bus.ts +0 -34
- package/src/core/changes.ts +0 -33
- package/src/core/env.ts +0 -27
- package/src/core/errors.ts +0 -32
- package/src/core/frontmatter.ts +0 -47
- package/src/core/fs.test.ts +0 -38
- package/src/core/fs.ts +0 -92
- package/src/core/index.ts +0 -193
- package/src/core/path.test.ts +0 -14
- package/src/core/path.ts +0 -79
- package/src/core/projects.ts +0 -90
- package/src/core/text.ts +0 -39
- package/src/harness.ts +0 -312
- package/src/hub/catalog.json +0 -123
- package/src/hub/cli.ts +0 -59
- package/src/hub/hub.test.ts +0 -205
- package/src/hub/hub.ts +0 -256
- package/src/hub/schema.ts +0 -81
- package/src/index.ts +0 -75
- package/src/mcp/connections.ts +0 -614
- package/src/mcp/manager.ts +0 -130
- package/src/mcp/oauth.ts +0 -195
- package/src/mcp/scopeMigration.test.ts +0 -79
- package/src/mcp/scopeMigration.ts +0 -204
- package/src/mcp/service.test.ts +0 -376
- package/src/mcp/service.ts +0 -549
- package/src/mcp/types.ts +0 -108
- 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 -163
- package/src/memory/curation.ts +0 -64
- package/src/memory/dispatch.ts +0 -59
- 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 -176
- package/src/memory/maintenance.ts +0 -518
- 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/recall.test.ts +0 -115
- package/src/memory/recall.ts +0 -278
- package/src/memory/references.ts +0 -77
- package/src/memory/runContext.ts +0 -93
- package/src/memory/secrets.ts +0 -6
- 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 -422
- package/src/memory/wikiIndex.test.ts +0 -88
- package/src/memory/wikiIndex.ts +0 -116
- package/src/plugins/api.ts +0 -181
- package/src/plugins/attachments.test.ts +0 -84
- package/src/plugins/attachments.ts +0 -141
- package/src/plugins/builtin.ts +0 -82
- package/src/plugins/catalogue.test.ts +0 -254
- package/src/plugins/catalogue.ts +0 -251
- package/src/plugins/cli.ts +0 -184
- package/src/plugins/discover.ts +0 -151
- package/src/plugins/firstParty.test.ts +0 -143
- package/src/plugins/firstParty.ts +0 -129
- package/src/plugins/hooks.test.ts +0 -250
- package/src/plugins/hooks.ts +0 -239
- package/src/plugins/identity.ts +0 -37
- package/src/plugins/install.test.ts +0 -107
- package/src/plugins/install.ts +0 -106
- package/src/plugins/inventory.ts +0 -123
- package/src/plugins/manifest.ts +0 -76
- package/src/plugins/plugins.test.ts +0 -205
- package/src/plugins/plugins.ts +0 -189
- package/src/plugins/proposal.ts +0 -121
- package/src/plugins/runtime.test.ts +0 -40
- package/src/plugins/runtime.ts +0 -82
- package/src/plugins/trust.ts +0 -142
- package/src/plugins/types.ts +0 -149
- package/src/prompt/compile.test.ts +0 -261
- package/src/prompt/compile.ts +0 -732
- package/src/server/methods.ts +0 -349
- package/src/server/remote.ts +0 -77
- package/src/server/rpc.ts +0 -116
- package/src/server/serve.ts +0 -28
- package/src/server/server.test.ts +0 -174
- package/src/sessions/approvals.ts +0 -118
- package/src/sessions/contracts.ts +0 -336
- package/src/sessions/conversation.test.ts +0 -177
- package/src/sessions/extensionSession.test.ts +0 -74
- package/src/sessions/extensionUi.test.ts +0 -65
- package/src/sessions/extensionUi.ts +0 -102
- package/src/sessions/history.ts +0 -51
- package/src/sessions/lineageJoin.ts +0 -75
- package/src/sessions/policy.ts +0 -36
- package/src/sessions/pricing.ts +0 -60
- package/src/sessions/questionBatches.test.ts +0 -110
- package/src/sessions/reasoningSummaries.ts +0 -59
- package/src/sessions/registry.ts +0 -381
- package/src/sessions/runIndex.ts +0 -99
- package/src/sessions/runStore.test.ts +0 -152
- package/src/sessions/runStore.ts +0 -332
- package/src/sessions/runner.ts +0 -604
- package/src/sessions/runtimeResources.test.ts +0 -61
- package/src/sessions/sessions.test.ts +0 -549
- package/src/sessions/sessions.ts +0 -339
- package/src/skills/registry.test.ts +0 -104
- package/src/skills/registry.ts +0 -413
- package/src/terminal/accountsMenu.test.ts +0 -225
- package/src/terminal/accountsMenu.ts +0 -382
- package/src/terminal/cli.ts +0 -149
- package/src/terminal/client.ts +0 -74
- package/src/terminal/control.test.ts +0 -23
- package/src/terminal/control.ts +0 -23
- package/src/terminal/controller.ts +0 -291
- package/src/terminal/hubMenu.ts +0 -24
- package/src/terminal/launch.test.ts +0 -25
- package/src/terminal/launch.ts +0 -48
- package/src/terminal/piExtension.lifecycle.test.ts +0 -55
- package/src/terminal/piExtension.ts +0 -259
- package/src/terminal/pluginControl.test.ts +0 -146
- package/src/terminal/pluginMenu.ts +0 -112
- package/src/terminal/socket.test.ts +0 -71
- package/src/terminal/socket.ts +0 -232
- package/src/terminal/standalone.test.ts +0 -167
- package/src/terminal/standalone.ts +0 -153
- package/src/terminal/terminal.test.ts +0 -399
- package/src/terminal/terminals.ts +0 -553
- package/src/terminal/tui/application.test.ts +0 -114
- package/src/terminal/tui/application.ts +0 -491
- package/src/terminal/tui/commands.test.ts +0 -218
- package/src/terminal/tui/commands.ts +0 -361
- package/src/terminal/tui/dialogs.ts +0 -301
- package/src/terminal/tui/editor.test.ts +0 -84
- package/src/terminal/tui/editor.ts +0 -79
- package/src/terminal/tui/keybindings.test.ts +0 -22
- package/src/terminal/tui/keybindings.ts +0 -50
- package/src/terminal/tui/piAdapter.ts +0 -62
- package/src/terminal/tui/presentation.test.ts +0 -79
- package/src/terminal/tui/questions.test.ts +0 -41
- package/src/terminal/tui/questions.ts +0 -47
- package/src/terminal/tui/runtime.socket.test.ts +0 -104
- package/src/terminal/tui/runtime.test.ts +0 -215
- package/src/terminal/tui/runtime.ts +0 -388
- package/src/terminal/tui/sessionUi.ts +0 -24
- package/src/terminal/tui/settings.test.ts +0 -150
- package/src/terminal/tui/settings.ts +0 -188
- package/src/terminal/tui/status.test.ts +0 -70
- package/src/terminal/tui/status.ts +0 -109
- package/src/terminal/tui/theme.ts +0 -174
- package/src/terminal/tui/tools.test.ts +0 -145
- package/src/terminal/tui/tools.ts +0 -142
- package/src/terminal/tui/transcript.test.ts +0 -25
- package/src/terminal/tui/transcript.ts +0 -124
- package/src/terminal/tui/ui.test.ts +0 -313
- package/src/terminal/tui/ui.ts +0 -166
- package/src/terminal/tui/welcome.ts +0 -102
- package/src/tools/askUser.test.ts +0 -69
- package/src/tools/askUser.ts +0 -59
- package/src/tools/definitions.ts +0 -187
- package/src/tools/maintenance.ts +0 -24
- 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/tools/spawnSubagent.ts +0 -92
- package/src/tools/types.ts +0 -39
- package/src/web/safeRequest.ts +0 -171
- package/src/web/text.ts +0 -53
- package/src/web/tools.ts +0 -134
- package/src/web/web.test.ts +0 -141
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
// Lessons from finished runs. When the `lessons` plugin is on for a run's caller, a completed
|
|
2
|
+
// top-level run is reflected on in the background: a file-tools-only session reads its record and
|
|
3
|
+
// may keep one general lesson as a Wiki note under `library/lessons/`. The lesson shape and the
|
|
4
|
+
// answer-free guards are refinement's (harness²); the caps follow OMP's memory budget.
|
|
5
|
+
//
|
|
6
|
+
// Lessons are reference notes (INV-MEM-1). They never enter the stable prefix and never change a
|
|
7
|
+
// variant; something that should become an instruction goes through refinement and `adopt`.
|
|
8
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { tmpdir } from 'node:os';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { loadRun as loadCapabilities } from "../capabilities/runSnapshot.js";
|
|
12
|
+
import { log } from "../core/bus.js";
|
|
13
|
+
import * as frontmatter from "../core/frontmatter.js";
|
|
14
|
+
import { looksSecret } from "../core/secrets.js";
|
|
15
|
+
import * as attachments from "../plugins/attachments.js";
|
|
16
|
+
import { LESSONS, LESSONS_CODE, LESSONS_SETTINGS, settingValue } from "../plugins/firstParty.js";
|
|
17
|
+
import { assertNoGraderVerdicts, assertVariantHasNoGraderVerdicts } from "../refine/evidence.js";
|
|
18
|
+
import { hasExperience } from "../refine/library.js";
|
|
19
|
+
import { PiImprover } from "../refine/pi.js";
|
|
20
|
+
import { parseExperience } from "../refine/selection.js";
|
|
21
|
+
import { renderEvents } from "../refine/trajectory.js";
|
|
22
|
+
import { executionEvents, findRun, readEvents } from "../sessions/runStore.js";
|
|
23
|
+
import * as notes from "../wiki/notes.js";
|
|
24
|
+
import { lessonMission, REFLECTION_IDENTITY } from "./lessonPrompt.js";
|
|
25
|
+
export const REFLECTION_TRIGGER = 'reflection';
|
|
26
|
+
export const LESSONS_FOLDER = 'library/lessons';
|
|
27
|
+
/** Runs that are themselves part of improving the harness are never reflected on. */
|
|
28
|
+
const SKIPPED_TRIGGERS = new Set(['probe', 'improver', REFLECTION_TRIGGER]);
|
|
29
|
+
const REFLECTION_TOOLS = ['read', 'grep', 'find', 'ls'];
|
|
30
|
+
/** The same floor refinement keeps for a trajectory (INV-REF-8). */
|
|
31
|
+
const OBS_TRUNC = 10_000;
|
|
32
|
+
const slug = (text) => text.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 60).replace(/-+$/, '') || 'lesson';
|
|
33
|
+
/** The lessons the Wiki keeps now. */
|
|
34
|
+
export function keptLessons(root) {
|
|
35
|
+
return notes.paths(root).filter((path) => path.startsWith(`${LESSONS_FOLDER}/`)).flatMap((path) => {
|
|
36
|
+
try {
|
|
37
|
+
const text = readFileSync(join(notes.wikiDir(root), `${path}.md`), 'utf8');
|
|
38
|
+
const parsed = frontmatter.parse(text);
|
|
39
|
+
const front = parsed?.front ?? {};
|
|
40
|
+
const sources = Array.isArray(front.sources) ? front.sources.filter((id) => typeof id === 'string') : [];
|
|
41
|
+
return [{ path, cue: typeof front.cue === 'string' ? front.cue : '', sources, front, body: parsed?.body ?? text }];
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** The model the run used, as Pi names it, from its last usage report. */
|
|
49
|
+
function modelOf(root, runId) {
|
|
50
|
+
const usage = readEvents(root, runId).filter((event) => event.type === 'usage.updated').at(-1)?.payload;
|
|
51
|
+
return typeof usage?.model === 'string' && typeof usage.provider === 'string' ? `${usage.provider}/${usage.model}` : null;
|
|
52
|
+
}
|
|
53
|
+
function lessonNote(experience, sources) {
|
|
54
|
+
const title = experience.cue.length > 80 ? `${experience.cue.slice(0, 77).trimEnd()}…` : experience.cue;
|
|
55
|
+
const body = [`# ${title}`, '', `**When:** ${experience.cue}`, '', `**Do:** ${experience.lesson}`, '', `**Evidence:** ${experience.evidence}`].join('\n');
|
|
56
|
+
return frontmatter.serialize({ title, cue: experience.cue, confidence: experience.confidence ?? null, sources }, body);
|
|
57
|
+
}
|
|
58
|
+
export class Lessons {
|
|
59
|
+
options;
|
|
60
|
+
queue = [];
|
|
61
|
+
abort = new AbortController();
|
|
62
|
+
draining = null;
|
|
63
|
+
closed = false;
|
|
64
|
+
constructor(options) {
|
|
65
|
+
this.options = options;
|
|
66
|
+
}
|
|
67
|
+
/** Called when a run completes. Cheap: the work happens later, one run at a time. */
|
|
68
|
+
consider(run) {
|
|
69
|
+
if (this.closed || !run || run.status !== 'completed' || run.parentRunId || run.relation)
|
|
70
|
+
return;
|
|
71
|
+
if (run.trigger && SKIPPED_TRIGGERS.has(run.trigger))
|
|
72
|
+
return;
|
|
73
|
+
if (this.queue.includes(run.id))
|
|
74
|
+
return;
|
|
75
|
+
this.queue.push(run.id);
|
|
76
|
+
this.draining ??= this.drain().finally(() => {
|
|
77
|
+
this.draining = null;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/** Resolves when everything queued so far was reflected on. */
|
|
81
|
+
async idle() {
|
|
82
|
+
while (this.draining)
|
|
83
|
+
await this.draining;
|
|
84
|
+
}
|
|
85
|
+
async close() {
|
|
86
|
+
this.closed = true;
|
|
87
|
+
this.queue.length = 0;
|
|
88
|
+
this.abort.abort();
|
|
89
|
+
await this.draining?.catch(() => undefined);
|
|
90
|
+
}
|
|
91
|
+
async drain() {
|
|
92
|
+
while (this.queue.length && !this.closed) {
|
|
93
|
+
const runId = this.queue.shift();
|
|
94
|
+
try {
|
|
95
|
+
const outcome = await this.reflect(runId);
|
|
96
|
+
if (outcome.kind !== 'skipped')
|
|
97
|
+
log(`lessons: run ${runId}: ${outcome.kind}${'path' in outcome ? ` ${outcome.path}` : ''}`);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
log(`lessons: run ${runId} was not reflected on`, error instanceof Error ? error.message : String(error));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/** The plugin's settings for this run's caller, or null when it is off for them. */
|
|
105
|
+
limits(root, runId) {
|
|
106
|
+
const contribution = `${LESSONS}/code/${LESSONS_CODE}`;
|
|
107
|
+
if (!loadCapabilities(root, runId)?.contributions.includes(contribution))
|
|
108
|
+
return null;
|
|
109
|
+
const saved = attachments.load(root).settings[LESSONS];
|
|
110
|
+
const value = (key) => settingValue(LESSONS_SETTINGS.find((setting) => setting.key === key), saved);
|
|
111
|
+
return { minToolCalls: value('minToolCalls'), maxLessons: value('maxLessons'), maxChars: value('maxChars') };
|
|
112
|
+
}
|
|
113
|
+
/** Reflects on one finished run. Exposed for tests and for a host that wants to ask directly. */
|
|
114
|
+
async reflect(runId) {
|
|
115
|
+
const root = this.options.root();
|
|
116
|
+
const run = findRun(root, runId);
|
|
117
|
+
if (!run || run.status !== 'completed')
|
|
118
|
+
return { kind: 'skipped', reason: 'not completed' };
|
|
119
|
+
const limits = this.limits(root, runId);
|
|
120
|
+
if (!limits)
|
|
121
|
+
return { kind: 'skipped', reason: 'plugin off' };
|
|
122
|
+
const events = executionEvents(readEvents(root, runId));
|
|
123
|
+
if (events.filter((event) => event.type === 'tool.started').length < limits.minToolCalls)
|
|
124
|
+
return { kind: 'skipped', reason: 'too few tool calls' };
|
|
125
|
+
const kept = keptLessons(root);
|
|
126
|
+
if (kept.some((lesson) => lesson.sources.includes(runId)))
|
|
127
|
+
return { kind: 'skipped', reason: 'already reflected' };
|
|
128
|
+
if (kept.length >= limits.maxLessons)
|
|
129
|
+
return { kind: 'skipped', reason: 'lesson limit reached' };
|
|
130
|
+
const context = mkdtempSync(join(tmpdir(), 'harness-lesson-'));
|
|
131
|
+
try {
|
|
132
|
+
const goal = run.goal ? `# Request\n\n${run.goal}\n\n# Record\n\n` : '# Record\n\n';
|
|
133
|
+
writeFileSync(join(context, 'run.md'), `${goal}${renderEvents(events, { obsTrunc: OBS_TRUNC })}\n`);
|
|
134
|
+
writeFileSync(join(context, 'lessons.md'), kept.length ? `${kept.map((lesson) => `${lesson.path} -- ${lesson.cue}`).join('\n')}\n` : '(no lessons kept yet)\n');
|
|
135
|
+
assertNoGraderVerdicts(context);
|
|
136
|
+
const reflector = this.options.reflector?.(run, modelOf(root, runId), this.abort.signal)
|
|
137
|
+
?? new PiImprover(this.options.runtime, join(root, 'reflection'), { accountId: run.accountId, model: modelOf(root, runId), effort: 'medium' }, this.abort.signal, { trigger: REFLECTION_TRIGGER, identity: REFLECTION_IDENTITY, tools: REFLECTION_TOOLS });
|
|
138
|
+
const reply = await reflector.call({ mission: lessonMission(), contextDir: context, label: `lesson-${runId}`, signal: this.abort.signal });
|
|
139
|
+
return this.keep(root, runId, reply.text, kept, limits);
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
rmSync(context, { recursive: true, force: true });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
keep(root, runId, text, kept, limits) {
|
|
146
|
+
if (/^\s*NO_ENTRY\s*$/m.test(text) && !text.includes('<experience>'))
|
|
147
|
+
return { kind: 'no_entry' };
|
|
148
|
+
const same = /^\s*SAME\s+(\S+)\s*$/m.exec(text)?.[1]?.replace(/\.md$/, '');
|
|
149
|
+
if (same) {
|
|
150
|
+
const lesson = kept.find((entry) => entry.path === same);
|
|
151
|
+
if (!lesson)
|
|
152
|
+
return { kind: 'skipped', reason: `named an unknown lesson ${same}` };
|
|
153
|
+
notes.write(root, lesson.path, frontmatter.serialize({ ...lesson.front, sources: [...lesson.sources, runId] }, lesson.body), { actor: REFLECTION_TRIGGER, reason: `Seen again in run ${runId}` });
|
|
154
|
+
return { kind: 'same', path: lesson.path };
|
|
155
|
+
}
|
|
156
|
+
const experience = parseExperience(text);
|
|
157
|
+
if (!hasExperience(experience))
|
|
158
|
+
return { kind: 'no_entry' };
|
|
159
|
+
const body = lessonNote(experience, [runId]);
|
|
160
|
+
if (`${experience.cue}${experience.lesson}${experience.evidence}`.length > limits.maxChars)
|
|
161
|
+
return { kind: 'skipped', reason: 'lesson too long' };
|
|
162
|
+
if (looksSecret(body))
|
|
163
|
+
return { kind: 'skipped', reason: 'lesson looked like it held a credential' };
|
|
164
|
+
assertVariantHasNoGraderVerdicts({ 'lesson.md': body });
|
|
165
|
+
const taken = new Set(notes.paths(root));
|
|
166
|
+
const base = `${LESSONS_FOLDER}/${slug(experience.cue)}`;
|
|
167
|
+
let path = base;
|
|
168
|
+
for (let n = 2; taken.has(path); n++)
|
|
169
|
+
path = `${base}-${n}`;
|
|
170
|
+
notes.write(root, path, body, { actor: REFLECTION_TRIGGER, reason: `Lesson from run ${runId}` });
|
|
171
|
+
return { kind: 'written', path };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { HarnessEnv } from "../core/env.js";
|
|
2
|
+
import type { RunEvent, RunRecord } from "../sessions/contracts.js";
|
|
3
|
+
import { type CallerContext } from "../core/caller.js";
|
|
4
|
+
export interface Passage {
|
|
5
|
+
/** `user`, `event` (a turn the application started, not the user), `assistant`, or `tool`. */
|
|
6
|
+
role: 'user' | 'event' | 'assistant' | 'tool';
|
|
7
|
+
at: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}
|
|
10
|
+
/** A run as recall sees it: what was asked, what the assistant said, and which tools it used. */
|
|
11
|
+
export declare function passages(run: RunRecord, events: readonly RunEvent[]): Passage[];
|
|
12
|
+
/** Indexes one run from its record and events. Index failures never block a run. */
|
|
13
|
+
export declare function indexRun(root: string, runId: string): void;
|
|
14
|
+
/**
|
|
15
|
+
* Indexes every run that is missing or whose status changed since it was indexed. Returns how
|
|
16
|
+
* many were (re)indexed. Called at startup, so earlier conversations are searchable.
|
|
17
|
+
*/
|
|
18
|
+
export declare function sync(root: string): number;
|
|
19
|
+
/**
|
|
20
|
+
* What a caller may recall. An agent searches its own conversations by default and everything
|
|
21
|
+
* when it asks; a session only sees session runs in its own project (or its own session when
|
|
22
|
+
* it has none), never an agent's conversation.
|
|
23
|
+
*/
|
|
24
|
+
export interface Scope {
|
|
25
|
+
/** Only runs of this session. */
|
|
26
|
+
sessionId?: string | null;
|
|
27
|
+
/** Only runs of this mode (`code` for sessions). */
|
|
28
|
+
mode?: string | null;
|
|
29
|
+
projectId?: string | null;
|
|
30
|
+
/** ISO date or timestamp; only passages at or after it. */
|
|
31
|
+
since?: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface RecallHit {
|
|
34
|
+
runId: string;
|
|
35
|
+
seq: number;
|
|
36
|
+
role: string;
|
|
37
|
+
at: string;
|
|
38
|
+
mode: string;
|
|
39
|
+
sessionId: string;
|
|
40
|
+
projectId: string | null;
|
|
41
|
+
snippet: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function search(root: string, query: string, scope: Scope, limit: number): RecallHit[];
|
|
44
|
+
export interface RecallMessage {
|
|
45
|
+
seq: number;
|
|
46
|
+
role: string;
|
|
47
|
+
at: string;
|
|
48
|
+
text: string;
|
|
49
|
+
}
|
|
50
|
+
export declare function read(root: string, runId: string): RecallMessage[];
|
|
51
|
+
type Params = Record<string, unknown> | null | undefined;
|
|
52
|
+
/** Who a run belonged to, for a person or a model reading a hit. */
|
|
53
|
+
export declare function sourceLabel(env: HarnessEnv, mode: string, sessionId: string, projectId: string | null): string;
|
|
54
|
+
/** `recall.search` as text for a model. */
|
|
55
|
+
export declare function searchTool(env: HarnessEnv, context: CallerContext, params: Params): string;
|
|
56
|
+
/** `recall.read` as text for a model. */
|
|
57
|
+
export declare function readTool(env: HarnessEnv, context: CallerContext, params: Params): string;
|
|
58
|
+
export interface RecallFilter {
|
|
59
|
+
sessionId?: string | null;
|
|
60
|
+
projectId?: string | null;
|
|
61
|
+
since?: string | null;
|
|
62
|
+
}
|
|
63
|
+
export type RecallResult = RecallHit & {
|
|
64
|
+
source: string;
|
|
65
|
+
};
|
|
66
|
+
/** A person's search: every conversation and run, narrowed only by the filter. */
|
|
67
|
+
export declare function searchAll(env: HarnessEnv, query: string, filter?: RecallFilter): RecallResult[];
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { logLine } from "../core/bus.js";
|
|
2
|
+
import { invalid, notFound } from "../core/errors.js";
|
|
3
|
+
import { all, run as execute, transaction, withIndex } from "../core/index.js";
|
|
4
|
+
import { findProject, listProjects } from "../core/projects.js";
|
|
5
|
+
import { charCount, takeChars } from "../core/text.js";
|
|
6
|
+
import { readAllRuns } from "../sessions/runIndex.js";
|
|
7
|
+
import { findRun, readEvents } from "../sessions/runStore.js";
|
|
8
|
+
import { isSession, projectOf } from "../core/caller.js";
|
|
9
|
+
const PASSAGE_LIMIT = 6000;
|
|
10
|
+
const READ_LIMIT = 12_000;
|
|
11
|
+
const SEARCH_DEFAULT = 8;
|
|
12
|
+
const SEARCH_MAX = 30;
|
|
13
|
+
/** A run as recall sees it: what was asked, what the assistant said, and which tools it used. */
|
|
14
|
+
export function passages(run, events) {
|
|
15
|
+
const found = [];
|
|
16
|
+
const goal = run.goal?.trim();
|
|
17
|
+
if (goal)
|
|
18
|
+
found.push({ role: goal.startsWith('[App coordination event') ? 'event' : 'user', at: run.startedAt, text: takeChars(goal, PASSAGE_LIMIT) });
|
|
19
|
+
for (const event of events) {
|
|
20
|
+
const payload = event.payload;
|
|
21
|
+
if (event.type === 'message.completed' && payload.role === 'assistant') {
|
|
22
|
+
const text = typeof payload.text === 'string' ? payload.text.trim() : '';
|
|
23
|
+
if (text)
|
|
24
|
+
found.push({ role: 'assistant', at: event.timestamp, text: takeChars(text, PASSAGE_LIMIT) });
|
|
25
|
+
}
|
|
26
|
+
else if (event.type === 'tool.started') {
|
|
27
|
+
const summary = typeof payload.summary === 'string' ? payload.summary.trim() : '';
|
|
28
|
+
if (summary)
|
|
29
|
+
found.push({ role: 'tool', at: event.timestamp, text: takeChars(`${typeof payload.name === 'string' ? payload.name : 'tool'}: ${summary}`, PASSAGE_LIMIT) });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return found;
|
|
33
|
+
}
|
|
34
|
+
function writeRun(database, run, found) {
|
|
35
|
+
execute(database, 'DELETE FROM recall_fts WHERE run_id = ?1', run.id);
|
|
36
|
+
found.forEach((passage, seq) => {
|
|
37
|
+
execute(database, 'INSERT INTO recall_fts (run_id, seq, role, at, mode, session_id, project_id, text) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)', run.id, seq, passage.role, passage.at, run.mode, run.sessionId, run.projectId, passage.text);
|
|
38
|
+
});
|
|
39
|
+
execute(database, 'INSERT OR REPLACE INTO recall_runs (run_id, passages, status) VALUES (?1, ?2, ?3)', run.id, found.length, run.status);
|
|
40
|
+
}
|
|
41
|
+
function eventsOf(root, runId) {
|
|
42
|
+
try {
|
|
43
|
+
return readEvents(root, runId);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/** Indexes one run from its record and events. Index failures never block a run. */
|
|
50
|
+
export function indexRun(root, runId) {
|
|
51
|
+
const run = findRun(root, runId);
|
|
52
|
+
if (!run)
|
|
53
|
+
return;
|
|
54
|
+
const found = passages(run, eventsOf(root, runId));
|
|
55
|
+
try {
|
|
56
|
+
withIndex(root, (database) => writeRun(database, run, found));
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
logLine(`[recall] run ${runId} was not indexed: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Indexes every run that is missing or whose status changed since it was indexed. Returns how
|
|
64
|
+
* many were (re)indexed. Called at startup, so earlier conversations are searchable.
|
|
65
|
+
*/
|
|
66
|
+
export function sync(root) {
|
|
67
|
+
const known = new Map();
|
|
68
|
+
withIndex(root, (database) => {
|
|
69
|
+
for (const row of all(database, 'SELECT run_id, status FROM recall_runs'))
|
|
70
|
+
known.set(row.run_id, row.status);
|
|
71
|
+
});
|
|
72
|
+
const pending = readAllRuns(root)
|
|
73
|
+
.filter((run) => known.get(run.id) !== run.status)
|
|
74
|
+
.map((run) => ({ run, found: passages(run, eventsOf(root, run.id)) }));
|
|
75
|
+
if (pending.length)
|
|
76
|
+
withIndex(root, (database) => transaction(database, () => pending.forEach(({ run, found }) => writeRun(database, run, found))));
|
|
77
|
+
return pending.length;
|
|
78
|
+
}
|
|
79
|
+
/** Any word may match; passages that match more of them, and rarer ones, rank first. */
|
|
80
|
+
function ftsAny(query) {
|
|
81
|
+
const terms = [];
|
|
82
|
+
for (const word of query.split(/[^\p{Alphabetic}\p{N}]+/u)) {
|
|
83
|
+
if (charCount(word) <= 1)
|
|
84
|
+
continue;
|
|
85
|
+
const term = `"${word.toLowerCase()}"*`;
|
|
86
|
+
if (!terms.includes(term) && terms.length < 24)
|
|
87
|
+
terms.push(term);
|
|
88
|
+
}
|
|
89
|
+
return terms.length ? terms.join(' OR ') : null;
|
|
90
|
+
}
|
|
91
|
+
export function search(root, query, scope, limit) {
|
|
92
|
+
const fts = ftsAny(query);
|
|
93
|
+
if (!fts)
|
|
94
|
+
return [];
|
|
95
|
+
return withIndex(root, (database) => all(database, `SELECT run_id, seq, role, at, mode, session_id, project_id, snippet(recall_fts, 7, '', '', '…', 40) AS snippet
|
|
96
|
+
FROM recall_fts WHERE recall_fts MATCH ?1
|
|
97
|
+
AND (?2 IS NULL OR session_id = ?2) AND (?3 IS NULL OR mode = ?3)
|
|
98
|
+
AND (?4 IS NULL OR project_id = ?4) AND (?5 IS NULL OR at >= ?5)
|
|
99
|
+
ORDER BY bm25(recall_fts), at DESC LIMIT ?6`, fts, scope.sessionId ?? null, scope.mode ?? null, scope.projectId ?? null, scope.since ?? null, Math.min(Math.max(limit, 1), SEARCH_MAX)).map((row) => ({ runId: row.run_id, seq: Number(row.seq), role: row.role, at: row.at ?? '', mode: row.mode ?? '', sessionId: row.session_id ?? '', projectId: row.project_id, snippet: row.snippet })));
|
|
100
|
+
}
|
|
101
|
+
export function read(root, runId) {
|
|
102
|
+
return withIndex(root, (database) => all(database, 'SELECT seq, role, at, text FROM recall_fts WHERE run_id = ?1 ORDER BY CAST(seq AS INTEGER)', runId).map((row) => ({ seq: Number(row.seq), role: row.role, at: row.at ?? '', text: row.text })));
|
|
103
|
+
}
|
|
104
|
+
function textParam(params, key) {
|
|
105
|
+
const value = params?.[key];
|
|
106
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
107
|
+
}
|
|
108
|
+
/** The widest scope a caller may use, narrowed by what it asked for. */
|
|
109
|
+
function callerScope(root, context, params) {
|
|
110
|
+
const since = textParam(params, 'since') || null;
|
|
111
|
+
if (isSession(context)) {
|
|
112
|
+
// A session never reads an agent's conversations: only session runs of its project, or its own.
|
|
113
|
+
const projectId = projectOf(root, context)?.id ?? null;
|
|
114
|
+
if (!projectId && !context.sessionId)
|
|
115
|
+
throw invalid('Recall needs a session.');
|
|
116
|
+
return { sessionId: projectId ? null : context.sessionId, mode: 'code', projectId, since };
|
|
117
|
+
}
|
|
118
|
+
if (!context.agentId)
|
|
119
|
+
throw invalid('Recall needs an agent or a Harness session.');
|
|
120
|
+
const key = textParam(params, 'project');
|
|
121
|
+
let projectId = null;
|
|
122
|
+
if (key) {
|
|
123
|
+
const project = listProjects(root).find((entry) => entry.id === key || entry.slug === key || entry.name.toLowerCase() === key.toLowerCase());
|
|
124
|
+
if (!project)
|
|
125
|
+
throw invalid(`There is no project called ${key}.`);
|
|
126
|
+
projectId = project.id;
|
|
127
|
+
}
|
|
128
|
+
const everything = textParam(params, 'scope') === 'all' || projectId !== null;
|
|
129
|
+
return { sessionId: everything ? null : context.agentId, mode: null, projectId, since };
|
|
130
|
+
}
|
|
131
|
+
function mayRead(scope, run) {
|
|
132
|
+
if (!scope.mode)
|
|
133
|
+
return true;
|
|
134
|
+
return scope.mode === run.mode && (!scope.projectId || scope.projectId === run.projectId) && (!scope.sessionId || scope.sessionId === run.sessionId);
|
|
135
|
+
}
|
|
136
|
+
/** Who a run belonged to, for a person or a model reading a hit. */
|
|
137
|
+
export function sourceLabel(env, mode, sessionId, projectId) {
|
|
138
|
+
if (mode === 'chat') {
|
|
139
|
+
// One of an agent's extra conversations has its own id.
|
|
140
|
+
const owner = env.directory.sessionAgent(sessionId);
|
|
141
|
+
const name = env.directory.agentName(sessionId) ?? (owner ? env.directory.agentName(owner) : null);
|
|
142
|
+
return name ? `chat with ${name}` : 'group or earlier chat';
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
return projectId ? `Harness session in ${findProject(env.root, projectId).name}` : 'Harness session';
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return 'Harness session';
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/** Whether a conversation belongs to an agent the caller may not recall. */
|
|
152
|
+
function excluded(env, context, sessionId) {
|
|
153
|
+
const hidden = context.recallExcludeAgents ?? [];
|
|
154
|
+
if (!hidden.length)
|
|
155
|
+
return false;
|
|
156
|
+
return hidden.includes(sessionId) || hidden.includes(env.directory.sessionAgent(sessionId) ?? '');
|
|
157
|
+
}
|
|
158
|
+
/** `recall.search` as text for a model. */
|
|
159
|
+
export function searchTool(env, context, params) {
|
|
160
|
+
const query = textParam(params, 'query');
|
|
161
|
+
if (!query)
|
|
162
|
+
throw invalid('Give a query.');
|
|
163
|
+
const scope = callerScope(env.root, context, params);
|
|
164
|
+
const requested = params?.limit;
|
|
165
|
+
const limit = typeof requested === 'number' && Number.isInteger(requested) && requested >= 0 ? requested : SEARCH_DEFAULT;
|
|
166
|
+
// The caller's current run is what it is in the middle of, not something to recall.
|
|
167
|
+
const hits = search(env.root, query, scope, limit + 4 + (context.recallExcludeAgents?.length ? SEARCH_MAX : 0)).filter((hit) => hit.runId !== context.runId && !excluded(env, context, hit.sessionId)).slice(0, Math.min(Math.max(limit, 1), SEARCH_MAX));
|
|
168
|
+
if (!hits.length) {
|
|
169
|
+
return scope.sessionId && !isSession(context) ? 'Nothing in your own conversations matches. Pass scope "all" to search every conversation and Harness run.' : 'Nothing in the recorded history matches.';
|
|
170
|
+
}
|
|
171
|
+
return `${hits.map((hit) => `- [run ${hit.runId} · ${hit.at.slice(0, 10)} · ${sourceLabel(env, hit.mode, hit.sessionId, hit.projectId)}] ${hit.role}: ${hit.snippet.replaceAll('\n', ' ')}`).join('\n')}\n\nRead a whole run with history_read and its run id.`;
|
|
172
|
+
}
|
|
173
|
+
/** `recall.read` as text for a model. */
|
|
174
|
+
export function readTool(env, context, params) {
|
|
175
|
+
const runId = textParam(params, 'run') || textParam(params, 'runId');
|
|
176
|
+
const run = findRun(env.root, runId);
|
|
177
|
+
if (!run)
|
|
178
|
+
throw notFound('Run');
|
|
179
|
+
// Reading is as wide as an "all" search for an agent, and as narrow as a search for a session.
|
|
180
|
+
if (!mayRead(callerScope(env.root, context, { scope: 'all' }), run) || excluded(env, context, run.sessionId))
|
|
181
|
+
throw notFound('Run');
|
|
182
|
+
const messages = read(env.root, run.id);
|
|
183
|
+
if (!messages.length)
|
|
184
|
+
return 'That run has no recorded messages.';
|
|
185
|
+
let text = `Run ${run.id} · ${run.startedAt.slice(0, 16)} · ${sourceLabel(env, run.mode, run.sessionId, run.projectId)} · ${run.status}\n`;
|
|
186
|
+
for (const message of messages) {
|
|
187
|
+
const line = `\n${message.role}: ${message.text}\n`;
|
|
188
|
+
if (charCount(text) + charCount(line) > READ_LIMIT) {
|
|
189
|
+
text += '\n(The rest of the run was left out.)\n';
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
text += line;
|
|
193
|
+
}
|
|
194
|
+
return text;
|
|
195
|
+
}
|
|
196
|
+
/** A person's search: every conversation and run, narrowed only by the filter. */
|
|
197
|
+
export function searchAll(env, query, filter = {}) {
|
|
198
|
+
return search(env.root, query, { sessionId: filter.sessionId, mode: null, projectId: filter.projectId, since: filter.since || null }, SEARCH_MAX).map((hit) => ({ ...hit, source: sourceLabel(env, hit.mode, hit.sessionId, hit.projectId) }));
|
|
199
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HarnessEnv } from "../core/env.js";
|
|
2
|
+
import type { CallerContext } from "../core/caller.js";
|
|
3
|
+
export type Params = Record<string, unknown> | null | undefined;
|
|
4
|
+
/** `trajectory.search`: structured list/search over the current Harness data root. */
|
|
5
|
+
export declare function searchTool(env: HarnessEnv, context: CallerContext, params: Params): string;
|
|
6
|
+
/** `trajectory.read`: a bounded page of the events that affected a run. */
|
|
7
|
+
export declare function readTool(env: HarnessEnv, context: CallerContext, params: Params): string;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { invalid, notFound } from "../core/errors.js";
|
|
2
|
+
import { listProjects } from "../core/projects.js";
|
|
3
|
+
import { compareText } from "../core/text.js";
|
|
4
|
+
import { scrub } from "../accounts/store.js";
|
|
5
|
+
import { scrubSensitive } from "../mcp/connections.js";
|
|
6
|
+
import { isActiveStatus, runStatusSchema } from "../sessions/contracts.js";
|
|
7
|
+
import { readAllRuns } from "../sessions/runIndex.js";
|
|
8
|
+
import { findRun, snapshot } from "../sessions/runStore.js";
|
|
9
|
+
const SEARCH_DEFAULT = 10;
|
|
10
|
+
const SEARCH_MAX = 30;
|
|
11
|
+
const READ_DEFAULT = 8;
|
|
12
|
+
const READ_MAX = 10;
|
|
13
|
+
const EVENT_MAX_CHARS = 18_000;
|
|
14
|
+
const PAGE_MAX_CHARS = 48_000;
|
|
15
|
+
function textParam(params, key) {
|
|
16
|
+
const value = params?.[key];
|
|
17
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
18
|
+
}
|
|
19
|
+
function boundedInteger(params, key, fallback, minimum, maximum) {
|
|
20
|
+
const value = params?.[key];
|
|
21
|
+
if (value === undefined)
|
|
22
|
+
return fallback;
|
|
23
|
+
if (typeof value !== 'number' || !Number.isInteger(value) || value < minimum || value > maximum) {
|
|
24
|
+
throw invalid(`${key} must be an integer from ${minimum} to ${maximum}.`);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
function dateValue(params, key) {
|
|
29
|
+
const value = textParam(params, key);
|
|
30
|
+
if (!value)
|
|
31
|
+
return null;
|
|
32
|
+
if (!/^\d{4}-\d{2}-\d{2}(?:T.*)?$/.test(value))
|
|
33
|
+
throw invalid(`${key} must be a YYYY-MM-DD date or ISO timestamp.`);
|
|
34
|
+
const timestamp = Date.parse(/^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}${key === 'until' ? 'T23:59:59.999Z' : 'T00:00:00.000Z'}` : value);
|
|
35
|
+
if (!Number.isFinite(timestamp))
|
|
36
|
+
throw invalid(`${key} must be a valid date or ISO timestamp.`);
|
|
37
|
+
return { text: value, timestamp };
|
|
38
|
+
}
|
|
39
|
+
function decodeCursor(value) {
|
|
40
|
+
if (!value)
|
|
41
|
+
return null;
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(Buffer.from(value, 'base64url').toString('utf8'));
|
|
44
|
+
if (typeof parsed.startedAt !== 'string' || !parsed.startedAt || typeof parsed.id !== 'string' || !parsed.id)
|
|
45
|
+
throw new Error('invalid cursor');
|
|
46
|
+
return { startedAt: parsed.startedAt, id: parsed.id };
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw invalid('The trajectory cursor is invalid. Start a new search without it.');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function encodeCursor(run) {
|
|
53
|
+
return Buffer.from(JSON.stringify({ startedAt: run.startedAt, id: run.id })).toString('base64url');
|
|
54
|
+
}
|
|
55
|
+
function hidden(env, context, run) {
|
|
56
|
+
const excluded = context.recallExcludeAgents ?? [];
|
|
57
|
+
if (!excluded.length)
|
|
58
|
+
return false;
|
|
59
|
+
return excluded.includes(run.agentId ?? '') || excluded.includes(run.sessionId) || excluded.includes(env.directory.sessionAgent(run.sessionId) ?? '');
|
|
60
|
+
}
|
|
61
|
+
function afterCursor(run, cursor) {
|
|
62
|
+
return run.startedAt < cursor.startedAt || (run.startedAt === cursor.startedAt && run.id < cursor.id);
|
|
63
|
+
}
|
|
64
|
+
function summary(env, run, projects) {
|
|
65
|
+
const project = projects.find((entry) => entry.id === run.projectId);
|
|
66
|
+
const goal = run.goal?.trim() ?? '';
|
|
67
|
+
return {
|
|
68
|
+
id: run.id,
|
|
69
|
+
agentId: run.agentId,
|
|
70
|
+
agentName: run.agentId ? env.directory.agentName(run.agentId) : null,
|
|
71
|
+
project: project ? { id: project.id, name: project.name, slug: project.slug } : null,
|
|
72
|
+
mode: run.mode,
|
|
73
|
+
status: run.status,
|
|
74
|
+
goal: goal.length > 500 ? `${goal.slice(0, 497)}…` : goal || null,
|
|
75
|
+
startedAt: run.startedAt,
|
|
76
|
+
endedAt: run.endedAt,
|
|
77
|
+
parentRunId: run.parentRunId,
|
|
78
|
+
relation: run.relation
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/** `trajectory.search`: structured list/search over the current Harness data root. */
|
|
82
|
+
export function searchTool(env, context, params) {
|
|
83
|
+
const agent = textParam(params, 'agent');
|
|
84
|
+
const projectKey = textParam(params, 'project');
|
|
85
|
+
const mode = textParam(params, 'mode');
|
|
86
|
+
const status = textParam(params, 'status');
|
|
87
|
+
const since = dateValue(params, 'since');
|
|
88
|
+
const until = dateValue(params, 'until');
|
|
89
|
+
const limit = boundedInteger(params, 'limit', SEARCH_DEFAULT, 1, SEARCH_MAX);
|
|
90
|
+
const cursor = decodeCursor(textParam(params, 'cursor'));
|
|
91
|
+
if (status && !runStatusSchema.safeParse(status).success)
|
|
92
|
+
throw invalid(`Unknown run status ${status}.`);
|
|
93
|
+
const projects = listProjects(env.root);
|
|
94
|
+
const project = projectKey ? projects.find((entry) => entry.id === projectKey || entry.slug === projectKey || entry.name.toLowerCase() === projectKey.toLowerCase()) : null;
|
|
95
|
+
if (projectKey && !project)
|
|
96
|
+
throw invalid(`There is no project called ${projectKey}.`);
|
|
97
|
+
const matching = readAllRuns(env.root)
|
|
98
|
+
.filter((run) => !hidden(env, context, run))
|
|
99
|
+
.filter((run) => !agent || run.agentId === agent || (run.agentId !== null && env.directory.agentName(run.agentId)?.toLowerCase() === agent.toLowerCase()))
|
|
100
|
+
.filter((run) => !projectKey || run.projectId === project?.id)
|
|
101
|
+
.filter((run) => !mode || run.mode === mode)
|
|
102
|
+
.filter((run) => !status || run.status === status)
|
|
103
|
+
.filter((run) => !since || Date.parse(run.startedAt) >= since.timestamp)
|
|
104
|
+
.filter((run) => !until || Date.parse(run.startedAt) <= until.timestamp)
|
|
105
|
+
.filter((run) => !cursor || afterCursor(run, cursor))
|
|
106
|
+
.sort((left, right) => compareText(right.startedAt, left.startedAt) || compareText(right.id, left.id));
|
|
107
|
+
const page = matching.slice(0, limit);
|
|
108
|
+
const hasMore = matching.length > page.length;
|
|
109
|
+
return JSON.stringify({
|
|
110
|
+
runs: page.map((run) => summary(env, run, projects)),
|
|
111
|
+
hasMore,
|
|
112
|
+
nextCursor: hasMore && page.length ? encodeCursor(page[page.length - 1]) : null
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function boundedValue(value, path, truncated, stringLimit, arrayLimit, depth = 0) {
|
|
116
|
+
if (typeof value === 'string') {
|
|
117
|
+
const fieldLimit = path.endsWith('.systemPrompt') || path.endsWith('.contextUpdate') ? Math.min(12_000, stringLimit) :
|
|
118
|
+
path.endsWith('.input') || path.endsWith('.output') || path.endsWith('.summary') ? Math.min(10_000, stringLimit) : stringLimit;
|
|
119
|
+
if (value.length <= fieldLimit)
|
|
120
|
+
return value;
|
|
121
|
+
truncated.push(path);
|
|
122
|
+
return `${value.slice(0, Math.max(0, fieldLimit - 16))}… [truncated]`;
|
|
123
|
+
}
|
|
124
|
+
if (Array.isArray(value)) {
|
|
125
|
+
const kept = value.slice(0, arrayLimit).map((item, index) => boundedValue(item, `${path}[${index}]`, truncated, stringLimit, arrayLimit, depth + 1));
|
|
126
|
+
if (value.length > arrayLimit)
|
|
127
|
+
truncated.push(path);
|
|
128
|
+
return kept;
|
|
129
|
+
}
|
|
130
|
+
if (!value || typeof value !== 'object' || depth >= 8)
|
|
131
|
+
return value;
|
|
132
|
+
const entries = Object.entries(value);
|
|
133
|
+
const kept = entries.slice(0, 40).map(([key, item]) => [key, boundedValue(item, `${path}.${key}`, truncated, stringLimit, arrayLimit, depth + 1)]);
|
|
134
|
+
if (entries.length > 40)
|
|
135
|
+
truncated.push(path);
|
|
136
|
+
return Object.fromEntries(kept);
|
|
137
|
+
}
|
|
138
|
+
function publicEvent(event) {
|
|
139
|
+
let stringLimit = 12_000;
|
|
140
|
+
let arrayLimit = 20;
|
|
141
|
+
while (true) {
|
|
142
|
+
const truncated = [];
|
|
143
|
+
const omitted = [];
|
|
144
|
+
let payload = scrubSensitive(event.payload);
|
|
145
|
+
if (event.type === 'account.status' && payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
146
|
+
const account = payload;
|
|
147
|
+
if (Object.hasOwn(account, 'accountId')) {
|
|
148
|
+
delete account.accountId;
|
|
149
|
+
omitted.push('payload.accountId');
|
|
150
|
+
}
|
|
151
|
+
if (typeof account.message === 'string')
|
|
152
|
+
account.message = scrub(account.message);
|
|
153
|
+
}
|
|
154
|
+
if (event.type === 'usage.updated' && payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
155
|
+
const request = payload.request;
|
|
156
|
+
if (request && typeof request === 'object' && !Array.isArray(request) && Object.hasOwn(request, 'reasoningSummary')) {
|
|
157
|
+
delete request.reasoningSummary;
|
|
158
|
+
omitted.push('payload.request.reasoningSummary');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const value = {
|
|
162
|
+
id: event.id,
|
|
163
|
+
sequence: event.sequence,
|
|
164
|
+
timestamp: event.timestamp,
|
|
165
|
+
type: event.type,
|
|
166
|
+
payload: boundedValue(payload, 'payload', truncated, stringLimit, arrayLimit)
|
|
167
|
+
};
|
|
168
|
+
if (event.coalescedFrom !== undefined)
|
|
169
|
+
value.coalescedFrom = event.coalescedFrom;
|
|
170
|
+
if (truncated.length)
|
|
171
|
+
value.truncatedFields = truncated.slice(0, 40);
|
|
172
|
+
if (omitted.length)
|
|
173
|
+
value.omittedFields = omitted;
|
|
174
|
+
if (JSON.stringify(value).length <= EVENT_MAX_CHARS || (stringLimit <= 500 && arrayLimit <= 1))
|
|
175
|
+
return value;
|
|
176
|
+
stringLimit = Math.max(500, Math.floor(stringLimit / 2));
|
|
177
|
+
arrayLimit = Math.max(1, Math.floor(arrayLimit / 2));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/** `trajectory.read`: a bounded page of the events that affected a run. */
|
|
181
|
+
export function readTool(env, context, params) {
|
|
182
|
+
const requested = textParam(params, 'run');
|
|
183
|
+
if (!requested)
|
|
184
|
+
throw invalid('Give a run id or "current".');
|
|
185
|
+
const runId = requested === 'current' ? context.runId ?? '' : requested;
|
|
186
|
+
if (!runId)
|
|
187
|
+
throw invalid('This caller has no current run.');
|
|
188
|
+
const run = findRun(env.root, runId);
|
|
189
|
+
if (!run || hidden(env, context, run))
|
|
190
|
+
throw notFound('Run');
|
|
191
|
+
const afterSequence = boundedInteger(params, 'afterSequence', -1, -1, Number.MAX_SAFE_INTEGER);
|
|
192
|
+
const limit = boundedInteger(params, 'limit', READ_DEFAULT, 1, READ_MAX);
|
|
193
|
+
const captured = snapshot(env.root, run.id);
|
|
194
|
+
const candidates = captured.events.filter((event) => event.sequence > afterSequence);
|
|
195
|
+
const events = [];
|
|
196
|
+
let eventChars = 0;
|
|
197
|
+
for (const event of candidates.slice(0, limit)) {
|
|
198
|
+
const bounded = publicEvent(event);
|
|
199
|
+
const size = JSON.stringify(bounded).length;
|
|
200
|
+
if (events.length && eventChars + size > PAGE_MAX_CHARS)
|
|
201
|
+
break;
|
|
202
|
+
events.push(bounded);
|
|
203
|
+
eventChars += size;
|
|
204
|
+
}
|
|
205
|
+
const lastSequence = events.length ? Number(events[events.length - 1].sequence) : afterSequence;
|
|
206
|
+
const hasMore = candidates.some((event) => event.sequence > lastSequence);
|
|
207
|
+
const projects = listProjects(env.root);
|
|
208
|
+
const result = {
|
|
209
|
+
run: summary(env, captured.run, projects),
|
|
210
|
+
active: isActiveStatus(captured.run.status),
|
|
211
|
+
events,
|
|
212
|
+
hasMore,
|
|
213
|
+
nextAfterSequence: lastSequence
|
|
214
|
+
};
|
|
215
|
+
return JSON.stringify(result);
|
|
216
|
+
}
|