total-recall-brain 3.44.0 → 3.46.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/bin/total-recall.mjs +10 -6
- package/docs/ARCHITECTURE.md +10 -7
- package/docs/reference/cli-reference.md +13 -8
- package/frontend/dist/assets/index-BRYE1jmv.css +1 -0
- package/frontend/dist/assets/index-D1goIPjC.js +5598 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +5 -4
- package/frontend/src/App.tsx +185 -238
- package/frontend/src/api/auth-inventory.ts +62 -0
- package/frontend/src/api/memory.ts +13 -8
- package/frontend/src/api/plugin-credentials.ts +23 -0
- package/frontend/src/api/plugins.ts +37 -53
- package/frontend/src/api/workspace-drawers.ts +97 -0
- package/frontend/src/components/BrainSelector.tsx +9 -3
- package/frontend/src/components/plugins/PluginPanel.tsx +83 -42
- package/frontend/src/components/plugins/PluginSettings.tsx +10 -20
- package/frontend/src/components/plugins/PluginWorkspace.tsx +367 -0
- package/frontend/src/components/plugins/ScopedPluginCommands.tsx +142 -0
- package/frontend/src/components/plugins/plugin-frame.ts +44 -33
- package/frontend/src/components/plugins/scoped-plugin-operations.ts +28 -0
- package/frontend/src/components/workspace/DrawerChat.tsx +110 -0
- package/frontend/src/components/workspace/DrawerLayout.tsx +196 -0
- package/frontend/src/components/workspace/DrawerMarkup.tsx +49 -0
- package/frontend/src/components/workspace/PluginChunkLibrary.tsx +92 -0
- package/frontend/src/components/workspace/PluginHome.tsx +76 -0
- package/frontend/src/components/workspace/WorkspaceDrawers.tsx +96 -0
- package/frontend/src/components/workspace/WorkspacePage.tsx +95 -0
- package/frontend/src/components/workspace/usePluginDesign.ts +38 -0
- package/frontend/src/index.css +409 -136
- package/frontend/src/pages/HelpPage.tsx +15 -46
- package/frontend/src/pages/MemoryPage.tsx +19 -36
- package/frontend/src/pages/SettingsPage.tsx +1 -1
- package/metadata.plugin.schema.json +53 -227
- package/package.json +1 -1
- package/scaffold/.agent/skills/plugin/SKILL.md +13 -11
- package/scaffold/.agent/skills/plugin/evals/README.md +14 -21
- package/scaffold/.agent/skills/plugin/evals/cases/01-fabricated-seed.mjs +15 -18
- package/scaffold/.agent/skills/plugin/evals/cases/02-reassuring-default.mjs +19 -24
- package/scaffold/.agent/skills/plugin/evals/cases/03-declared-template.mjs +12 -20
- package/scaffold/.agent/skills/plugin/evals/cases/04-home-collects-credential.mjs +35 -0
- package/scaffold/.agent/skills/plugin/evals/cases/05-host-leak-in-scaffold.mjs +8 -28
- package/scaffold/.agent/skills/plugin/evals/cases/06-correct-plugin.mjs +85 -47
- package/scaffold/.agent/skills/plugin/evals/run.mjs +17 -18
- package/scaffold/.agent/skills/plugin/prompts/05-credentials.md +4 -4
- package/scaffold/.agent/skills/plugin/prompts/07-scaffold.md +2 -2
- package/scaffold/.agent/skills/plugin/prompts/08-verify.md +4 -4
- package/scaffold/.agent/skills/plugin/references/anti-patterns.md +2 -2
- package/scaffold/.agent/skills/plugin/references/generative-home.md +38 -0
- package/scaffold/.agent/skills/plugin/references/scaffold.md +5 -5
- package/scaffold/.agent/skills/plugin/references/surfaces.md +13 -4
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +75 -153
- package/scaffold/.agent/skills/plugin/subagents/plugin-reviewer.md +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/DESIGN.md +76 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +3 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +20 -15
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +13 -51
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/access-overview.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/agent-login.md +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/baseline-audit.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-repository-binding.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/credential-store-maintenance.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-password.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/dashboard-sessions.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/endpoint-probe.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/google-sign-in.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/mesh-machines.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/passkey-enrollment.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/registered-brains.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/scoped-access-keys.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/secret-inventory.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/chunks/signing-key-rotation.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/home.js +375 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/DESIGN.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +247 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +130 -0
- package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
- package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +151 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/control-ledger.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/local-inventory-triage.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/pending-control.md +39 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/run-evidence.md +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/schedule-vs-run-evidence.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/chunks/source-coverage.md +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/automations/ui/home.js +278 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/DESIGN.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +61 -7
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +40 -40
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/config.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/detect.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/gate.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/init.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/report.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/run.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/chunks/snapshot.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +55 -23
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/home.mjs +214 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/DESIGN.md +146 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +51 -34
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/create.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/history.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/library.md +49 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/list.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/promote.md +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/read.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/remove.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/rollback.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/show.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/status.md +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test-path.md +38 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/test.md +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/chunks/update.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/home.js +533 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/DESIGN.md +110 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +13 -10
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/benchmark.md +49 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/coverage.md +53 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/models.md +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/pull.md +41 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/chunks/status.md +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/home.js +353 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/DESIGN.md +31 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/README.md +136 -0
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +140 -24
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +9 -9
- package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +151 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +54 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +174 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +92 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +156 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/unmerged.mjs +182 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/change-comparison.md +27 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/commit-identity.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/create-worktree.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/registered-worktrees.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/remove-worktree.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/repository-ledger.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/unmerged-work.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/upstream-divergence.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-conflict-triage.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/chunks/worktree-terminal.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/git/ui/home.js +273 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/DESIGN.md +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +4 -5
- package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +37 -39
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/active-device-dispatch.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-probe.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/control-server.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/credential-readiness.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/elected-leader.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/enrollment-key.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/file-receipt.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-execution.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/fleet-presence.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/interactive-ssh.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/local-enrollment.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/mesh-policy.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/network-atlas.md +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-roster.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/node-selection-proof.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/owner-observations.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/peer-path.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/pinned-leader.md +24 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/reachability-evidence-ladder.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/recorded-login.md +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/remote-execution.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/chunks/secret-grants.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/{card.js → home.js} +93 -22
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/DESIGN.md +86 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +44 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +128 -24
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +188 -34
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/chat.mjs +80 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/adapter-docs-freshness.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/chat-handoff.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/fleet-maintenance.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/harness-inventory.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-access.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/included-usage-verification.md +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/independent-review.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/managed-execution.md +20 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/official-documentation.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/quota-recovery.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/review-routing-decision.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/route-work.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/routing-evidence.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/chunks/run-ledger.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/home.js +198 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/DESIGN.md +75 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +2 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +64 -32
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +10 -44
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/generate.mjs +5 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/graph.mjs +3 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +23 -18
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/mesh-vaults.mjs +8 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +123 -113
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/search.mjs +3 -5
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/token.mjs +21 -47
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +17 -6
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/auto-refresh.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/bootstrap-pages.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generate-wiki.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/generator-settings.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/host-probe.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/ingest-preview.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-hygiene.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/link-topology.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/passage-search.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/propagation-plan.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/provider-credentials.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/refresh-report.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/task-evidence.md +22 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/upstream-record.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/watch-upstream.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/chunks/wiki-atlas.md +19 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +261 -513
- package/scaffold/.agent/skills/total-recall/plugins/security/DESIGN.md +84 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/README.md +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -19
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +2 -3
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +19 -52
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/device-evidence.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/evidence-desk.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/guided-device-review.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/host-and-network.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/https-headers.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/mesh-coverage.md +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/private-security-tools.md +16 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/single-origin-header-check.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/source-review.md +21 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/chunks/ssh-permission-plan.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/home.js +283 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/DESIGN.md +93 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package-lock.json +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/ssss/package.json +2 -1
- package/scaffold/.agent/skills/total-recall/plugins/ssss/plugin.json +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/conformance.md +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/registry.md +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/repair.md +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/scaffold.md +33 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/status.md +43 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validate.md +34 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/validation-diagnostics.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/chunks/workspace-registry-preflight.md +17 -0
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/home.js +348 -0
- package/scaffold/.agent/skills/total-recall/references/operations.md +3 -1
- package/src/cli/agent.mjs +1 -1
- package/src/cli/api.mjs +3 -3
- package/src/cli/brief.mjs +3 -0
- package/src/cli/command.mjs +41 -2
- package/src/cli/config.mjs +16 -0
- package/src/cli/connect.mjs +2 -7
- package/src/cli/dream.mjs +93 -69
- package/src/cli/harness.mjs +4 -4
- package/src/cli/help.mjs +1 -1
- package/src/cli/init.mjs +0 -25
- package/src/cli/lessons.mjs +51 -0
- package/src/cli/memory-plugin.mjs +1 -1
- package/src/cli/plugin/chunks.mjs +145 -0
- package/src/cli/plugin/copies.mjs +64 -14
- package/src/cli/plugin/create.mjs +30 -59
- package/src/cli/plugin/index.mjs +31 -16
- package/src/cli/plugin/install.mjs +10 -15
- package/src/cli/plugin/remove.mjs +1 -1
- package/src/cli/plugin/search.mjs +2 -2
- package/src/cli/preflight.mjs +19 -6
- package/src/cli/proposals.mjs +1 -1
- package/src/cli/recall.mjs +66 -23
- package/src/cli/remember.mjs +3 -14
- package/src/cli/server.mjs +18 -0
- package/src/cli/setup.mjs +2 -9
- package/src/cli/start.mjs +1 -0
- package/src/cli/startup.mjs +19 -0
- package/src/cli/task.mjs +9 -2
- package/src/cli/update.mjs +6 -2
- package/src/core/access-ledger.mjs +78 -0
- package/src/core/agent-manager.mjs +2 -2
- package/src/core/app-deploy/design-tokens.mjs +14 -9
- package/src/core/app-deploy/ui-elements.mjs +1 -1
- package/src/core/brain-locate.mjs +15 -0
- package/src/core/brain-registry.mjs +5 -0
- package/src/core/brain-updates.mjs +35 -11
- package/src/core/claude-hooks.mjs +3 -1
- package/src/core/cleanup-task.mjs +15 -0
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/cli-maintenance.mjs +4 -1
- package/src/core/cli-semantic-index.mjs +23 -3
- package/src/core/context-compiler.mjs +20 -0
- package/src/core/daemon-control.mjs +5 -1
- package/src/core/daemon-loop.mjs +39 -62
- package/src/core/dashboard-bundle.mjs +261 -0
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/dream.mjs +33 -105
- package/src/core/ensure-dependencies.mjs +152 -0
- package/src/core/fast-recall.mjs +2 -2
- package/src/core/field-evaluation.mjs +23 -0
- package/src/core/gate-mode.mjs +1 -1
- package/src/core/harness-hooks.mjs +14 -2
- package/src/core/lesson-hooks.mjs +74 -0
- package/src/core/lesson-memory.mjs +24 -0
- package/src/core/lesson-sessions.mjs +52 -0
- package/src/core/lessons.mjs +96 -0
- package/src/core/memory-brain.mjs +30 -8
- package/src/core/meta-harness.mjs +2 -2
- package/src/core/optimizer.mjs +66 -165
- package/src/core/plugin-activation.mjs +63 -20
- package/src/core/plugin-contracts/{card.mjs → manifest.mjs} +26 -57
- package/src/core/plugin-contracts/startup-check.mjs +49 -0
- package/src/core/plugin-contracts/ui.mjs +9 -3
- package/src/core/plugin-distribution.mjs +6 -6
- package/src/core/plugin-lifecycle.mjs +58 -0
- package/src/core/plugin-loader.mjs +21 -8
- package/src/core/plugin-runner.mjs +21 -10
- package/src/core/plugin-runtime.mjs +40 -1
- package/src/core/plugin-startup-checks.mjs +44 -0
- package/src/core/plugin-state.mjs +19 -5
- package/src/core/plugin-store.mjs +64 -33
- package/src/core/post-mortem.mjs +52 -36
- package/src/core/preflight-gate.mjs +23 -2
- package/src/core/project-id.mjs +15 -5
- package/src/core/proposal-applier.mjs +31 -2
- package/src/core/repo-sync.mjs +4 -6
- package/src/core/required-plugins.mjs +57 -10
- package/src/core/rule-length.mjs +23 -0
- package/src/core/runtime.mjs +1 -1
- package/src/core/scheduler.mjs +35 -43
- package/src/core/schema.mjs +3 -2
- package/src/core/search.mjs +4 -3
- package/src/core/server-service.mjs +148 -0
- package/src/core/session-watcher.mjs +19 -1
- package/src/core/skill-gap.mjs +16 -0
- package/src/core/ssss-kernel-bridge.mjs +32 -4
- package/src/core/ssss-operation-service.mjs +1 -0
- package/src/core/startup-health.mjs +34 -4
- package/src/core/surface.mjs +65 -54
- package/src/core/task-executors.mjs +54 -106
- package/src/core/testing/plugin-fixture.mjs +22 -21
- package/src/core/throttled-fetch.mjs +25 -18
- package/src/core/ui-chunk-choice.mjs +77 -0
- package/src/core/ui-chunk.registry.json +23 -0
- package/src/core/ui-chunks.mjs +434 -0
- package/src/core/validated-write.mjs +1 -0
- package/src/core/vault-cache.mjs +58 -59
- package/src/core/vector-field.mjs +134 -192
- package/src/core/workspace-compositions.mjs +151 -0
- package/src/core/workspace-drawers.mjs +79 -0
- package/src/server/api.mjs +4 -27
- package/src/server/index.mjs +28 -21
- package/src/server/invoke-core-child.mjs +24 -0
- package/src/server/invoke-core-operations.mjs +102 -0
- package/src/server/memory-events.mjs +31 -0
- package/src/server/plugin-brain-targets.mjs +89 -0
- package/src/server/rest.mjs +2 -0
- package/src/server/route-inventory.mjs +3 -2
- package/src/server/route-manifest.json +47 -55
- package/src/server/routes/_shared.mjs +7 -14
- package/src/server/routes/brains.mjs +29 -62
- package/src/server/routes/context.mjs +0 -41
- package/src/server/routes/dashboard.mjs +26 -0
- package/src/server/routes/invoke.mjs +26 -1
- package/src/server/routes/plugins.mjs +154 -45
- package/src/server/routes/ssss.mjs +0 -169
- package/src/server/routes/system.mjs +0 -15
- package/src/server/routes/tasks.mjs +0 -26
- package/src/server/routes/ui-chunks.mjs +192 -0
- package/src/server/routes/vault.mjs +2 -23
- package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
- package/frontend/dist/assets/index-DtwsDyFM.css +0 -1
- package/frontend/src/components/plugins/card/CardEngine.tsx +0 -108
- package/frontend/src/components/plugins/card/CardScenes.tsx +0 -103
- package/frontend/src/components/plugins/card/ExecutionPicker.tsx +0 -64
- package/frontend/src/components/plugins/card/args.ts +0 -28
- package/frontend/src/components/plugins/card/card.css +0 -32
- package/frontend/src/components/plugins/card/cli-line.ts +0 -13
- package/frontend/src/components/plugins/card/contract.ts +0 -91
- package/frontend/src/components/plugins/card/format.ts +0 -49
- package/frontend/src/components/plugins/card/runner-context.ts +0 -11
- package/frontend/src/components/plugins/card/runner.tsx +0 -43
- package/frontend/src/components/plugins/card/theme.ts +0 -35
- package/frontend/src/components/plugins/card/use-source.ts +0 -22
- package/frontend/src/components/plugins/card/widgets.tsx +0 -94
- package/frontend/src/components/plugins/workspace/WorkspaceHost.tsx +0 -23
- package/frontend/src/pages/PluginsPage.tsx +0 -1719
- package/scaffold/.agent/skills/plugin/evals/cases/04-card-collects-credential.mjs +0 -47
- package/scaffold/.agent/skills/plugin/prompts/06-card.md +0 -46
- package/scaffold/.agent/skills/plugin/references/card.md +0 -90
- package/scaffold/.agent/skills/plugin/subagents/card-designer.md +0 -60
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +0 -67
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +0 -1179
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +0 -588
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +0 -575
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +0 -482
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +0 -100
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +0 -637
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +0 -217
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +0 -32
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +0 -1276
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +0 -45
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +0 -1401
- package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +0 -4
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +0 -41
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +0 -396
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +0 -98
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/DESIGN.md +0 -15
- package/scaffold/.agent/skills/total-recall/plugins/ssss/ui/card.js +0 -182
- package/src/core/append-log.mjs +0 -131
- package/src/core/plugin-tasks.mjs +0 -173
- package/templates/onboarding-interview.md +0 -82
- /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/** One-shot harness sessions. Portable: this module uses Node built-ins only. */
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { randomUUID } from 'node:crypto';
|
|
7
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
8
|
+
|
|
9
|
+
// Match the one-shot adapters in worker.mjs. Callers can supply their prepared
|
|
10
|
+
// args (model, permissions and provider settings), without the prompt operand.
|
|
11
|
+
const COMMANDS = {
|
|
12
|
+
agy: ['--mode', 'accept-edits', '--sandbox', '--output-format', 'json', '-p'],
|
|
13
|
+
cursor: ['--print', '--output-format', 'json'],
|
|
14
|
+
dsh: ['--profile', 'headless', '--json', '-'],
|
|
15
|
+
grok: ['--output-format', 'json', '-p'],
|
|
16
|
+
muse: ['exec', '--json'],
|
|
17
|
+
pi: ['--print', '--mode', 'json', '--provider', 'openrouter', '--tools', 'read,bash', '--no-extensions'],
|
|
18
|
+
hermes: ['chat', '--oneshot', '--format', 'stream-json', '--query-file', '-'],
|
|
19
|
+
openclaw: ['agent', '--message'],
|
|
20
|
+
aider: ['--yes', '--message'],
|
|
21
|
+
goose: ['run', '--text'],
|
|
22
|
+
openhands: ['run'],
|
|
23
|
+
cline: ['task', '--json'],
|
|
24
|
+
ollama: ['run'],
|
|
25
|
+
};
|
|
26
|
+
const STDIN = new Set(['dsh', 'pi', 'hermes', 'ollama']);
|
|
27
|
+
const PLAIN = new Set(['ollama', 'aider', 'goose', 'openhands', 'cline']);
|
|
28
|
+
// Verified 2026-11-09. No "latest" selectors: parallel jobs must not share state.
|
|
29
|
+
// antigravity.google/docs/cli/reference/; cursor.com/docs/cli/reference/parameters
|
|
30
|
+
// docs.x.ai/build/cli/reference; dev.meta.ai/docs/muse-code/extending
|
|
31
|
+
// github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/cli.md
|
|
32
|
+
// github.com/NousResearch/hermes-agent/blob/main/website/docs/reference/cli-commands.md
|
|
33
|
+
// docs.openclaw.ai/cli/agent; aider.chat/docs/config/options.html
|
|
34
|
+
// goose-docs.ai/docs/guides/goose-cli-commands/
|
|
35
|
+
// github.com/OpenHands/OpenHands-CLI; github.com/cline/cline/blob/main/docs/cli/cli-reference.mdx
|
|
36
|
+
const SELECTOR = { agy: '--conversation', cursor: '--resume', grok: '--resume',
|
|
37
|
+
muse: '--session-id', pi: '--session', hermes: '--resume', openclaw: '--session-id',
|
|
38
|
+
cline: '--id', openhands: '--resume' };
|
|
39
|
+
const emptyUsage = () => ({ inputTokens: null, outputTokens: null, costUsd: null });
|
|
40
|
+
const number = (...values) => values.find(value => typeof value === 'number' && Number.isFinite(value) && value >= 0) ?? null;
|
|
41
|
+
const sumUsage = (a, b) => Object.fromEntries(Object.keys(a).map(key => [key,
|
|
42
|
+
a[key] === null && b[key] === null ? null : (a[key] ?? 0) + (b[key] ?? 0)]));
|
|
43
|
+
const usageOf = value => ({
|
|
44
|
+
inputTokens: number(value?.inputTokens, value?.input_tokens, value?.input),
|
|
45
|
+
outputTokens: number(value?.outputTokens, value?.output_tokens, value?.output),
|
|
46
|
+
costUsd: number(value?.costUsd, value?.cost_usd, value?.total_cost_usd, value?.cost?.total),
|
|
47
|
+
});
|
|
48
|
+
const textOf = content => typeof content === 'string' ? content : Array.isArray(content)
|
|
49
|
+
? content.filter(part => part?.type === 'text').map(part => part.text || '').join('\n') : '';
|
|
50
|
+
const errorText = value => typeof value === 'string' ? value : value?.message;
|
|
51
|
+
const failureKind = text => /quota|rate.?limit|resource_exhausted|\b429\b|insufficient credits/i.test(text) ? 'quota'
|
|
52
|
+
: /not logged in|unauthenticated|\b401\b|invalid api key/i.test(text) ? 'auth' : 'agent';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* `args` are the existing one-shot command, before prompt injection. Unknown
|
|
56
|
+
* adapters may pass { id, input } as harness. Their controls stay unsupported.
|
|
57
|
+
* Queued replies are returned in done.pendingMessages and job.pendingMessages,
|
|
58
|
+
* so the next attempt can carry them without pretending they were delivered.
|
|
59
|
+
*/
|
|
60
|
+
export function createResumeDriver({ harness, binary, args = [], cwd, env, job,
|
|
61
|
+
options = {}, onEvent = () => {}, onFrame = () => {}, spawnFn = spawn }) {
|
|
62
|
+
for (const key of ['maxTurns', 'maxTokens', 'maxBudgetUsd']) {
|
|
63
|
+
const value = options[key];
|
|
64
|
+
if (value != null && (typeof value !== 'number' || !Number.isFinite(value) || value < 0 ||
|
|
65
|
+
(key !== 'maxBudgetUsd' && !Number.isInteger(value)))) throw new Error(`Invalid ${key}.`);
|
|
66
|
+
}
|
|
67
|
+
const id = typeof harness === 'string' ? harness : harness?.id;
|
|
68
|
+
const inputMode = harness?.input || (STDIN.has(id) ? 'stdin' : id === 'muse' ? 'file' : 'argument');
|
|
69
|
+
const baseArgs = [...(args.length ? args : COMMANDS[id] || [])];
|
|
70
|
+
const knownResume = Boolean(SELECTOR[id] || ['aider', 'goose'].includes(id));
|
|
71
|
+
const canFork = ['grok', 'pi'].includes(id);
|
|
72
|
+
const pendingMessages = Array.isArray(job?.pendingMessages)
|
|
73
|
+
? job.pendingMessages.filter(item => typeof item?.text === 'string').map(item => ({ ...item })) : [];
|
|
74
|
+
let sessionId = options.sessionId || null, active = null, settled = false, closed = false, closeRequested = false;
|
|
75
|
+
let started = false, directory, totalUsage = emptyUsage(), totalTurns = 0, limitError;
|
|
76
|
+
let resolveDone, controls = Promise.resolve(), lastResponse;
|
|
77
|
+
let done = new Promise(resolve => { resolveDone = resolve; });
|
|
78
|
+
const capabilities = { reply: 'unsupported', interrupt: 'metaharness', answer: 'unsupported',
|
|
79
|
+
fork: 'unsupported', goal: 'unsupported', limits: 'metaharness', schema: 'unsupported', review: 'unsupported' };
|
|
80
|
+
const emit = event => { try { onEvent(event); } catch { /* A view cannot stop a job. */ } };
|
|
81
|
+
const refresh = () => {
|
|
82
|
+
const resume = knownResume && Boolean(sessionId);
|
|
83
|
+
capabilities.reply = capabilities.answer = resume ? 'metaharness' : 'unsupported';
|
|
84
|
+
capabilities.fork = resume && canFork ? 'metaharness' : 'unsupported';
|
|
85
|
+
};
|
|
86
|
+
const setSession = value => {
|
|
87
|
+
if (typeof value !== 'string' || !value) return;
|
|
88
|
+
active?.sessionReady?.(value);
|
|
89
|
+
if (value === sessionId) return;
|
|
90
|
+
sessionId = value; refresh();
|
|
91
|
+
emit({ kind: 'session', text: `${id} session ${value}`, sessionId: value, session: value });
|
|
92
|
+
};
|
|
93
|
+
const resultUsage = () => sumUsage(totalUsage, active?.usage || emptyUsage());
|
|
94
|
+
const finish = result => {
|
|
95
|
+
if (settled) return;
|
|
96
|
+
settled = true;
|
|
97
|
+
if (directory) fs.rmSync(directory, { recursive: true, force: true });
|
|
98
|
+
const value = { response: null, structured: null, error: null, failureKind: null, ...result,
|
|
99
|
+
sessionId, usage: resultUsage(), ...(pendingMessages.length ? { pendingMessages: [...pendingMessages] } : {}) };
|
|
100
|
+
lastResponse = value.response;
|
|
101
|
+
emit({ kind: 'finish', text: value.ok ? 'Finished' : value.error, ...value });
|
|
102
|
+
resolveDone(value);
|
|
103
|
+
};
|
|
104
|
+
const serialize = action => {
|
|
105
|
+
const next = controls.then(action);
|
|
106
|
+
controls = next.catch(() => {});
|
|
107
|
+
return next;
|
|
108
|
+
};
|
|
109
|
+
const checkOpen = () => { if (closed || closeRequested || settled) throw new Error('The session is closed.'); };
|
|
110
|
+
const signal = (run, name) => {
|
|
111
|
+
try {
|
|
112
|
+
if (process.platform !== 'win32' && run.child.pid) process.kill(-run.child.pid, name);
|
|
113
|
+
else run.child.kill(name);
|
|
114
|
+
} catch { /* It may have exited. */ }
|
|
115
|
+
};
|
|
116
|
+
const stop = async reason => {
|
|
117
|
+
const run = active;
|
|
118
|
+
if (!run || run.exited) return;
|
|
119
|
+
run.stopReason ||= reason;
|
|
120
|
+
signal(run, 'SIGTERM');
|
|
121
|
+
// Wait for close (all output drained) before a replacement can start.
|
|
122
|
+
// Harnesses write their final result (session id, usage) after SIGTERM; give them time on a busy machine.
|
|
123
|
+
const timer = setTimeout(() => signal(run, 'SIGKILL'), options.stopGraceMs ?? 5000);
|
|
124
|
+
try { await run.exit; } finally {
|
|
125
|
+
clearTimeout(timer);
|
|
126
|
+
// A descendant can keep running after its parent exits on SIGTERM.
|
|
127
|
+
if (process.platform !== 'win32') signal(run, 'SIGKILL');
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const checkLimits = () => {
|
|
131
|
+
const usage = resultUsage();
|
|
132
|
+
const turns = totalTurns + (active?.turns || 0);
|
|
133
|
+
const cap = options.maxTokens != null && (usage.inputTokens ?? 0) + (usage.outputTokens ?? 0) >= options.maxTokens ? 'token'
|
|
134
|
+
: options.maxBudgetUsd != null && (usage.costUsd ?? 0) >= options.maxBudgetUsd ? 'budget'
|
|
135
|
+
: options.maxTurns != null && turns >= options.maxTurns ? 'turn' : null;
|
|
136
|
+
if (cap && !limitError) {
|
|
137
|
+
limitError = `Metaharness ${cap} limit reached.`;
|
|
138
|
+
void stop('limit').then(() => finish({ ok: false, error: limitError, failureKind: 'limit' }));
|
|
139
|
+
}
|
|
140
|
+
return Boolean(limitError);
|
|
141
|
+
};
|
|
142
|
+
const frame = (run, value) => {
|
|
143
|
+
onFrame(value);
|
|
144
|
+
if (!value || typeof value !== 'object') return;
|
|
145
|
+
if (Array.isArray(value)) { for (const item of value) frame(run, item); return; }
|
|
146
|
+
run.frames.push(value);
|
|
147
|
+
const agentMeta = value.result?.meta?.agentMeta || value.meta?.agentMeta || value.payload?.meta?.agentMeta;
|
|
148
|
+
setSession(value.session_id || value.sessionId || value.conversation_id || value.conversationId ||
|
|
149
|
+
(value.type === 'session' ? value.id : null) || agentMeta?.sessionId);
|
|
150
|
+
const type = value.type;
|
|
151
|
+
const message = value.message;
|
|
152
|
+
if (['turn_start', 'turn.started'].includes(type)) emit({ kind: 'step-start', text: 'Turn started' });
|
|
153
|
+
if (['tool_use', 'tool_execution_start'].includes(type)) emit({ kind: 'step-start', text: value.name || value.toolName || 'Tool started' });
|
|
154
|
+
if (['tool_result', 'tool_execution_end'].includes(type)) emit({ kind: 'step-end', text: value.name || value.toolName || 'Tool finished' });
|
|
155
|
+
const text = type === 'text' ? value.text : type === 'message_update' ? value.assistantMessageEvent?.delta
|
|
156
|
+
: type === 'message_end' && message?.role === 'assistant' ? textOf(message.content) : null;
|
|
157
|
+
if (text) emit({ kind: 'message', text });
|
|
158
|
+
if (type === 'turn_end') { run.turns += 1; emit({ kind: 'step-end', text: 'Turn finished' }); }
|
|
159
|
+
if (typeof value.num_turns === 'number') run.turns = Math.max(run.turns, value.num_turns);
|
|
160
|
+
const error = errorText(value.error) || (value.is_error ? value.result : null);
|
|
161
|
+
if (error || type === 'error') { run.error = String(error || value.message || 'Agent failed'); emit({ kind: 'error', text: run.error }); }
|
|
162
|
+
if (value.structured_output !== undefined) run.structured = value.structured_output;
|
|
163
|
+
|
|
164
|
+
// Pi usage is cumulative within a message; agent_end repeats completed
|
|
165
|
+
// messages. Do not add both the updates and the final snapshot.
|
|
166
|
+
let usage;
|
|
167
|
+
if (id === 'pi') {
|
|
168
|
+
if (type === 'message_start' && message?.role === 'assistant') run.partialUsage = emptyUsage();
|
|
169
|
+
if (type === 'message_update' && value.usage) run.partialUsage = usageOf(value.usage);
|
|
170
|
+
if (type === 'message_end' && message?.role === 'assistant') {
|
|
171
|
+
run.messageUsage = sumUsage(run.messageUsage, usageOf(message.usage));
|
|
172
|
+
run.partialUsage = emptyUsage();
|
|
173
|
+
}
|
|
174
|
+
if (type === 'agent_end' && Array.isArray(value.messages)) {
|
|
175
|
+
// agent_end covers one low-level run; message_end remains authoritative
|
|
176
|
+
// when it was observed, including across automatic retries.
|
|
177
|
+
if (!run.sawMessageUsage) run.messageUsage = value.messages.filter(m => m.role === 'assistant')
|
|
178
|
+
.reduce((sum, m) => sumUsage(sum, usageOf(m.usage)), emptyUsage());
|
|
179
|
+
}
|
|
180
|
+
if (type === 'message_end' && message?.role === 'assistant') run.sawMessageUsage = true;
|
|
181
|
+
usage = sumUsage(run.messageUsage, run.partialUsage);
|
|
182
|
+
} else if (value.usage || value.tokens || value.total_cost_usd !== undefined || agentMeta?.usage) {
|
|
183
|
+
usage = usageOf(value.usage || value.tokens || agentMeta?.usage);
|
|
184
|
+
usage.costUsd = number(value.total_cost_usd, value.costUsd, usage.costUsd);
|
|
185
|
+
}
|
|
186
|
+
if (usage && Object.values(usage).some(n => n !== null)) {
|
|
187
|
+
run.usage = { ...run.usage, ...Object.fromEntries(Object.entries(usage).filter(([, n]) => n !== null)) };
|
|
188
|
+
emit({ kind: 'usage', text: 'Usage reported', ...resultUsage() });
|
|
189
|
+
}
|
|
190
|
+
checkLimits();
|
|
191
|
+
};
|
|
192
|
+
const queue = (text, command, requestId) => {
|
|
193
|
+
pendingMessages.push({ command, text, ...(requestId === undefined ? {} : { requestId }) });
|
|
194
|
+
if (job && typeof job === 'object') job.pendingMessages = [...pendingMessages];
|
|
195
|
+
const ack = { ack: true, via: 'metaharness', queued: true, reply: 'unsupported' };
|
|
196
|
+
emit({ kind: 'ack', text: 'Queued for the next attempt; this harness cannot resume this session.', command, ...ack });
|
|
197
|
+
return ack;
|
|
198
|
+
};
|
|
199
|
+
const launch = async (text, { resume = false, fork = false } = {}) => {
|
|
200
|
+
checkOpen();
|
|
201
|
+
if (checkLimits()) return;
|
|
202
|
+
directory ||= fs.mkdtempSync(path.join(os.tmpdir(), 'metaharness-resume-'));
|
|
203
|
+
let argv = [...baseArgs], extra = [];
|
|
204
|
+
if (resume) {
|
|
205
|
+
// Remove an earlier selector before setting the exact saved session.
|
|
206
|
+
const selectors = new Set(['--conversation', '--resume', '--session', '--session-id', '--id']);
|
|
207
|
+
argv = argv.filter((arg, i, all) => !selectors.has(arg) && !selectors.has(all[i - 1]));
|
|
208
|
+
if (id === 'aider') extra.push('--restore-chat-history');
|
|
209
|
+
else if (id === 'goose') extra.push('--resume', '--name', sessionId);
|
|
210
|
+
else extra.push(SELECTOR[id], sessionId);
|
|
211
|
+
if (fork) {
|
|
212
|
+
if (id === 'pi') extra = ['--fork', sessionId];
|
|
213
|
+
else extra.push('--fork-session');
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (id === 'aider') {
|
|
217
|
+
const history = argv.indexOf('--chat-history-file');
|
|
218
|
+
if (history >= 0) sessionId = argv[history + 1];
|
|
219
|
+
else {
|
|
220
|
+
sessionId ||= path.join(os.tmpdir(), `metaharness-aider-${randomUUID()}.history.md`);
|
|
221
|
+
extra.push('--chat-history-file', sessionId);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (id === 'goose' && !resume) { sessionId ||= `metaharness-${randomUUID()}`; extra.push('--name', sessionId); }
|
|
225
|
+
if (['grok', 'openclaw'].includes(id) && !resume && !argv.includes('--session-id')) {
|
|
226
|
+
sessionId ||= randomUUID(); extra.push('--session-id', sessionId);
|
|
227
|
+
}
|
|
228
|
+
refresh();
|
|
229
|
+
// The worker supplies an unfilled prompt option. Keep its operand next to
|
|
230
|
+
// it even when provider or model options were appended to the command.
|
|
231
|
+
const promptOption = ['agy', 'grok'].includes(id) ? '-p'
|
|
232
|
+
: ['openclaw', 'aider'].includes(id) ? '--message' : id === 'goose' ? '--text' : null;
|
|
233
|
+
if (promptOption) argv = argv.filter(arg => arg !== promptOption);
|
|
234
|
+
argv.push(...extra);
|
|
235
|
+
const prompt = [text, ...pendingMessages.splice(0).map(item => item.text)].join('\n\n');
|
|
236
|
+
if (job && typeof job === 'object') job.pendingMessages = [];
|
|
237
|
+
if (inputMode === 'file') {
|
|
238
|
+
const file = path.join(directory, `${randomUUID()}.txt`);
|
|
239
|
+
fs.writeFileSync(file, prompt, { mode: 0o600 }); argv.push('--prompt-file', file);
|
|
240
|
+
} else if (inputMode !== 'stdin') argv.push(...(promptOption ? [promptOption] : []), prompt);
|
|
241
|
+
let child;
|
|
242
|
+
try { child = spawnFn(binary, argv, { cwd, env, shell: false, detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe'] }); }
|
|
243
|
+
catch (error) { finish({ ok: false, error: error.message, failureKind: 'process' }); throw error; }
|
|
244
|
+
let resolveExit, resolveSession;
|
|
245
|
+
const run = { child, awaitingHeader: ['pi', 'hermes'].includes(id), frames: [], stdout: '', stderr: '', buffer: '', bytes: 0, turns: 0,
|
|
246
|
+
usage: emptyUsage(), messageUsage: emptyUsage(), partialUsage: emptyUsage(),
|
|
247
|
+
exit: new Promise(resolve => { resolveExit = resolve; }),
|
|
248
|
+
session: new Promise(resolve => { resolveSession = resolve; }), sessionReady: value => resolveSession(value) };
|
|
249
|
+
active = run;
|
|
250
|
+
const decoder = new StringDecoder('utf8');
|
|
251
|
+
const line = text => { try { frame(run, JSON.parse(text)); } catch { /* Plain output is valid for some adapters. */ } };
|
|
252
|
+
const collect = chunk => {
|
|
253
|
+
run.bytes += chunk.length;
|
|
254
|
+
if (run.bytes > 32 * 1024 * 1024) { void stop('output-limit'); return; }
|
|
255
|
+
const text = decoder.write(chunk); run.stdout += text; run.buffer += text;
|
|
256
|
+
let end;
|
|
257
|
+
while ((end = run.buffer.indexOf('\n')) >= 0) { line(run.buffer.slice(0, end)); run.buffer = run.buffer.slice(end + 1); }
|
|
258
|
+
};
|
|
259
|
+
child.stdout.on('data', collect);
|
|
260
|
+
child.stderr.on('data', chunk => { run.stderr = (run.stderr + chunk).slice(-8000); });
|
|
261
|
+
child.stdin.on('error', () => {});
|
|
262
|
+
const exit = (code, signalName) => {
|
|
263
|
+
if (run.exited) return;
|
|
264
|
+
run.buffer += decoder.end();
|
|
265
|
+
if (run.buffer.trim()) line(run.buffer);
|
|
266
|
+
// Pretty-printed JSON is not JSONL.
|
|
267
|
+
if (!run.frames.length) { try { frame(run, JSON.parse(run.stdout)); } catch {} }
|
|
268
|
+
run.exited = true;
|
|
269
|
+
if (!run.turns) run.turns = 1;
|
|
270
|
+
checkLimits();
|
|
271
|
+
totalUsage = sumUsage(totalUsage, run.usage); totalTurns += run.turns;
|
|
272
|
+
active = null; resolveSession(null); resolveExit();
|
|
273
|
+
if (limitError) { finish({ ok: false, error: limitError, failureKind: 'limit' }); return; }
|
|
274
|
+
if (['replace', 'interrupt'].includes(run.stopReason)) return;
|
|
275
|
+
if (run.stopReason === 'close') { finish({ ok: false, error: 'Session closed.', failureKind: 'cancelled' }); return; }
|
|
276
|
+
const frames = run.frames, last = frames.at(-1);
|
|
277
|
+
const agentError = run.error || (last?.exit_code ? `Agent exited ${last.exit_code}` : null) ||
|
|
278
|
+
(['FAILED', 'ERROR', 'CANCELED', 'WAITING'].includes(last?.status) ? last.status : null) ||
|
|
279
|
+
(last?.messages?.some(m => ['error', 'aborted'].includes(m.stopReason)) ? 'Agent turn failed' : null);
|
|
280
|
+
let response = last?.result ?? last?.response ?? last?.text;
|
|
281
|
+
if (id === 'pi') response = frames.filter(f => f.type === 'agent_end').flatMap(f => f.messages || [])
|
|
282
|
+
.filter(m => m.role === 'assistant').map(m => textOf(m.content)).join('\n');
|
|
283
|
+
if (id === 'openclaw') response = (last?.result?.payloads || last?.payload?.payloads || last?.payloads || []).map(p => p.text || '').join('\n') || response;
|
|
284
|
+
if (PLAIN.has(id) || id === 'muse' || !COMMANDS[id]) response ||= run.stdout.trim();
|
|
285
|
+
const error = run.spawnError || agentError || (run.stopReason ? `Agent stopped: ${run.stopReason}` : null) ||
|
|
286
|
+
(code !== 0 ? run.stderr.trim() || `Agent process exited ${code ?? signalName}` : null) ||
|
|
287
|
+
(typeof response !== 'string' || !response.trim() ? 'Agent returned no completed result.' : null);
|
|
288
|
+
finish({ ok: !error, response: typeof response === 'string' ? response : null, structured: run.structured ?? null,
|
|
289
|
+
...(options.captureExecution ? { execution: { stdout: run.stdout, stderr: run.stderr, exitCode: code } } : {}),
|
|
290
|
+
error, failureKind: error ? run.spawnError || run.stopReason ? 'process' : failureKind(error) : null });
|
|
291
|
+
};
|
|
292
|
+
child.once('close', exit);
|
|
293
|
+
const live = new Promise((resolve, reject) => {
|
|
294
|
+
child.once('spawn', resolve);
|
|
295
|
+
child.once('error', error => { run.spawnError = error.message; exit(null); reject(error); });
|
|
296
|
+
});
|
|
297
|
+
child.stdin.end(inputMode === 'stdin' ? prompt : '');
|
|
298
|
+
await live;
|
|
299
|
+
// These streams have a documented session header. Wait until the resumed
|
|
300
|
+
// process has read its input before accepting another replacement.
|
|
301
|
+
if (run.awaitingHeader) await run.session;
|
|
302
|
+
run.awaitingHeader = false;
|
|
303
|
+
return run;
|
|
304
|
+
};
|
|
305
|
+
refresh();
|
|
306
|
+
const deliver = (text, command, requestId) => serialize(async () => {
|
|
307
|
+
checkOpen();
|
|
308
|
+
if (typeof text !== 'string' || !text.trim()) throw new Error('A non-empty message is required.');
|
|
309
|
+
if (!knownResume || !started) return queue(text, command, requestId);
|
|
310
|
+
await stop('replace');
|
|
311
|
+
if (settled || limitError) throw new Error(limitError || 'The session has finished.');
|
|
312
|
+
// JSON-only harnesses may report their ID only while shutting down. Read
|
|
313
|
+
// that final output before deciding whether this session can be resumed.
|
|
314
|
+
if (!sessionId) {
|
|
315
|
+
const ack = queue(text, command, requestId);
|
|
316
|
+
finish({ ok: false, error: 'The harness did not report a resumable session ID.', failureKind: 'process' });
|
|
317
|
+
return ack;
|
|
318
|
+
}
|
|
319
|
+
await launch(text, { resume: true });
|
|
320
|
+
const ack = { ack: true, via: 'metaharness' };
|
|
321
|
+
emit({ kind: 'ack', text: 'Resumed with the message.', command, ...ack });
|
|
322
|
+
return ack;
|
|
323
|
+
});
|
|
324
|
+
return {
|
|
325
|
+
capabilities,
|
|
326
|
+
start: brief => serialize(async () => {
|
|
327
|
+
checkOpen();
|
|
328
|
+
if (active || started) throw new Error('The session has already started.');
|
|
329
|
+
if (typeof brief !== 'string' || !brief.trim()) throw new Error('A non-empty brief is required.');
|
|
330
|
+
started = true;
|
|
331
|
+
if (options.sessionId && !knownResume) throw new Error('Resume is unsupported for this harness.');
|
|
332
|
+
if (options.forkSession && (!canFork || options.atTurn !== undefined)) throw new Error('Fork is unsupported for this harness or turn.');
|
|
333
|
+
await launch(brief, { resume: Boolean(options.sessionId && knownResume), fork: options.forkSession === true });
|
|
334
|
+
}),
|
|
335
|
+
send: text => deliver(text, 'send'),
|
|
336
|
+
answer: (requestId, value) => deliver(typeof value === 'string' ? value : JSON.stringify(value), 'answer', requestId),
|
|
337
|
+
interrupt: () => {
|
|
338
|
+
// A startup hook can wait before the header. It must remain stoppable
|
|
339
|
+
// while start() is waiting, rather than blocking behind that wait.
|
|
340
|
+
const startup = active?.awaitingHeader ? stop('interrupt') : null;
|
|
341
|
+
return serialize(async () => { checkOpen(); await startup; await stop('interrupt'); emit({ kind: 'ack', text: 'Interrupted.', command: 'interrupt' }); });
|
|
342
|
+
},
|
|
343
|
+
fork: ({ atTurn } = {}) => serialize(async () => {
|
|
344
|
+
checkOpen();
|
|
345
|
+
if (capabilities.fork === 'unsupported' || atTurn !== undefined) throw new Error('Fork is unsupported for this harness or turn.');
|
|
346
|
+
await stop('replace');
|
|
347
|
+
const previous = sessionId;
|
|
348
|
+
const run = await launch('Continue from this session.', { resume: true, fork: true });
|
|
349
|
+
const forked = await run?.session;
|
|
350
|
+
if (!forked || forked === previous) throw new Error('The harness did not report a forked session ID.');
|
|
351
|
+
return { sessionId: forked };
|
|
352
|
+
}),
|
|
353
|
+
close: () => {
|
|
354
|
+
closeRequested = true;
|
|
355
|
+
const startup = active?.awaitingHeader ? stop('close') : null;
|
|
356
|
+
return serialize(async () => {
|
|
357
|
+
if (closed) return;
|
|
358
|
+
closed = true; await startup; await stop('close');
|
|
359
|
+
if (!settled) finish({ ok: false, error: 'Session closed.', failureKind: 'cancelled' });
|
|
360
|
+
});
|
|
361
|
+
},
|
|
362
|
+
get done() { return done; },
|
|
363
|
+
next: async text => {
|
|
364
|
+
await serialize(async () => {
|
|
365
|
+
if (!settled || closed || closeRequested) throw new Error('The session is not ready for another turn.');
|
|
366
|
+
settled = false;
|
|
367
|
+
done = new Promise(resolve => { resolveDone = resolve; });
|
|
368
|
+
await launch(knownResume && sessionId ? text : `Previous result:\n${lastResponse}\n\n${text}`, { resume: Boolean(knownResume && sessionId) });
|
|
369
|
+
});
|
|
370
|
+
return done;
|
|
371
|
+
},
|
|
372
|
+
};
|
|
373
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/** Job lifecycle around a native session. All dependencies ship with the worker. */
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { randomUUID } from 'node:crypto';
|
|
5
|
+
import { createClaudeDriver } from './claude.mjs';
|
|
6
|
+
import { createCodexDriver } from './codex.mjs';
|
|
7
|
+
import { createResumeDriver } from './resume.mjs';
|
|
8
|
+
import { GOAL_SCHEMA, readResult } from './result.mjs';
|
|
9
|
+
import { tailInbox, appendEvent, waitingRequests } from '../control.mjs';
|
|
10
|
+
import { mcpConfigFor, createMcpTools, NO_ANSWER, QUESTION_TIMEOUT_MS } from '../mcp-server.mjs';
|
|
11
|
+
|
|
12
|
+
// A routed attempt or coordination stage shares its CLI job's limits. Each
|
|
13
|
+
// driver reports its own usage; the job sums those reports without double count.
|
|
14
|
+
const budgets = new Map();
|
|
15
|
+
|
|
16
|
+
export function sessionOptions(request) {
|
|
17
|
+
const result = {};
|
|
18
|
+
if (request.sessionId) result.resumeSession = true;
|
|
19
|
+
for (const key of ['goal', 'maxTurns', 'maxTokens', 'maxBudgetUsd', 'resultSchema', 'approvalPolicy', 'questionTimeoutMs', 'sessionId', 'forkSession', 'atTurn', 'name', 'effort', 'fallbackModel', 'reviewTarget']) {
|
|
20
|
+
if (request[key] !== undefined) result[key] = request[key];
|
|
21
|
+
}
|
|
22
|
+
for (const key of ['maxTurns', 'maxTokens', 'questionTimeoutMs']) if (result[key] !== undefined && (!Number.isSafeInteger(result[key]) || result[key] < 1)) throw new Error(`${key} must be a positive integer.`);
|
|
23
|
+
if (result.maxBudgetUsd !== undefined && (!Number.isFinite(result.maxBudgetUsd) || result.maxBudgetUsd < 0)) throw new Error('maxBudgetUsd must be nonnegative.');
|
|
24
|
+
if (result.goal !== undefined && (typeof result.goal !== 'string' || !result.goal.trim() || result.goal.length > 4000)) throw new Error('goal must have 1..4000 characters.');
|
|
25
|
+
if (result.approvalPolicy !== undefined && !['auto', 'operator', 'rules'].includes(result.approvalPolicy)) throw new Error('Approval policy must be auto, operator, or rules.');
|
|
26
|
+
if (typeof result.resultSchema === 'string') result.resultSchema = JSON.parse(fs.readFileSync(path.resolve(request.cwd, result.resultSchema), 'utf8'));
|
|
27
|
+
if (result.resultSchema !== undefined && typeof result.resultSchema !== 'boolean' && (!result.resultSchema || typeof result.resultSchema !== 'object' || Array.isArray(result.resultSchema))) throw new Error('resultSchema must be a JSON Schema.');
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function runSession({ request, adapter, binary, args, env, prompt, progress, register = () => () => {},
|
|
32
|
+
driverFactory, controlToolsFactory = createMcpTools, onFrame = () => {} } = {}) {
|
|
33
|
+
const options = sessionOptions(request);
|
|
34
|
+
options.captureExecution = adapter.id === 'openclaw';
|
|
35
|
+
options.trackTurns = true;
|
|
36
|
+
let goal = options.goal || request.contextTask || request.prompt;
|
|
37
|
+
options.goal = goal.slice(0, 4000);
|
|
38
|
+
const job = request.jobId || process.env.TR_METAHARNESS_JOB || `session-${randomUUID()}`;
|
|
39
|
+
const budget = budgets.get(job) || new Map();
|
|
40
|
+
budgets.set(job, budget);
|
|
41
|
+
const runId = randomUUID();
|
|
42
|
+
const config = mcpConfigFor(job, options);
|
|
43
|
+
if (adapter.id === 'claude') options.mcpConfig = config.claude;
|
|
44
|
+
if (adapter.id === 'codex') {
|
|
45
|
+
// exec flags belong to the old one-shot surface, not app-server.
|
|
46
|
+
const start = args.indexOf('exec');
|
|
47
|
+
const prefix = start < 0 ? [] : args.slice(0, start);
|
|
48
|
+
const model = args.lastIndexOf('--model');
|
|
49
|
+
args = [...prefix, ...(model >= 0 ? ['-c', `model=${JSON.stringify(args[model + 1])}`] : []), ...config.codex];
|
|
50
|
+
}
|
|
51
|
+
let driver, tools, watcher, deferredWatcher, replyWaiter = null, timer, stopResult, stopTask, turns = 0;
|
|
52
|
+
const deferredReplies = new Map();
|
|
53
|
+
let resolveStop;
|
|
54
|
+
const stopped = new Promise(resolve => { resolveStop = resolve; });
|
|
55
|
+
const usage = { inputTokens: null, outputTokens: null, costUsd: null };
|
|
56
|
+
const totals = () => [...budget.values()].reduce((sum, entry) => ({
|
|
57
|
+
inputTokens: sum.inputTokens + (entry.usage.inputTokens || 0), outputTokens: sum.outputTokens + (entry.usage.outputTokens || 0),
|
|
58
|
+
costUsd: sum.costUsd + (entry.usage.costUsd || 0), turns: sum.turns + entry.turns,
|
|
59
|
+
}), { inputTokens: 0, outputTokens: 0, costUsd: 0, turns: 0 });
|
|
60
|
+
const toolResults = [];
|
|
61
|
+
const emit = event => progress ? progress.event(event) : appendEvent(job, event);
|
|
62
|
+
const stop = (failureKind, error) => {
|
|
63
|
+
if (stopResult) return;
|
|
64
|
+
stopResult = { ok: false, failureKind, error };
|
|
65
|
+
resolveStop(stopResult);
|
|
66
|
+
stopTask = Promise.resolve(driver?.interrupt()).catch(() => {});
|
|
67
|
+
};
|
|
68
|
+
budget.set(runId, { usage, turns: 0, stop });
|
|
69
|
+
const limit = error => { for (const entry of budget.values()) entry.stop?.('limit', error); };
|
|
70
|
+
const onEvent = event => {
|
|
71
|
+
if (event.kind !== 'usage') emit(['question', 'approval'].includes(event.kind) ? { ...event, kind: 'native-request' } : event);
|
|
72
|
+
if (event.kind === 'usage') {
|
|
73
|
+
for (const key of ['inputTokens', 'outputTokens', 'costUsd']) if (event[key] != null) {
|
|
74
|
+
usage[key] = adapter.id === 'claude' ? (usage[key] || 0) + event[key] : event[key];
|
|
75
|
+
}
|
|
76
|
+
const used = totals();
|
|
77
|
+
emit({ ...event, ...used, limits: { maxTurns: options.maxTurns, maxTokens: options.maxTokens, maxBudgetUsd: options.maxBudgetUsd } });
|
|
78
|
+
if (options.maxTokens !== undefined && used.inputTokens + used.outputTokens >= options.maxTokens) limit('Job token limit reached.');
|
|
79
|
+
if (options.maxBudgetUsd !== undefined && usage.costUsd !== null && used.costUsd >= options.maxBudgetUsd) limit('Job budget limit reached.');
|
|
80
|
+
if (options.maxBudgetUsd !== undefined && usage.costUsd === null && adapter.id !== 'claude') stop('limit', 'The harness did not report cost, so the USD cap cannot be enforced.');
|
|
81
|
+
}
|
|
82
|
+
if (event.kind === 'finish') {
|
|
83
|
+
turns += event.turns || 1;
|
|
84
|
+
budget.get(runId).turns = turns;
|
|
85
|
+
if (options.maxTurns !== undefined && totals().turns > options.maxTurns) limit('Job turn limit reached.');
|
|
86
|
+
}
|
|
87
|
+
const item = event.item;
|
|
88
|
+
if (event.kind === 'step-end' && toolResults.length < 20 && (event.toolUseId || item?.type === 'commandExecution')) {
|
|
89
|
+
toolResults.push({ tool: item ? 'shell' : 'tool', status: event.isError || item?.exitCode ? 'failed' : 'completed',
|
|
90
|
+
...(Number.isInteger(item?.exitCode) ? { exitCode: item.exitCode } : {}), output: String(item?.aggregatedOutput || event.text || '').slice(0, 4096) });
|
|
91
|
+
}
|
|
92
|
+
if (event.kind === 'question' || event.kind === 'approval') {
|
|
93
|
+
// Native requests use the same policy, timeout and operator path as MCP.
|
|
94
|
+
void Promise.resolve().then(async () => {
|
|
95
|
+
if (!tools) return;
|
|
96
|
+
const answer = await tools.call(event.kind === 'question' ? 'ask' : 'approve', event.kind === 'question'
|
|
97
|
+
? { questions: event.questions || event.request?.questions || [event.text || 'Reply to the agent.'] }
|
|
98
|
+
: { tool_name: event.request?.tool_name || event.request?.command || event.method || 'tool', input: event.request || {} },
|
|
99
|
+
// A native request pauses the harness itself with no model call, so it waits for the answer here.
|
|
100
|
+
{ defer: false });
|
|
101
|
+
let value = answer.content[0].text;
|
|
102
|
+
if (event.kind === 'approval') {
|
|
103
|
+
const approval = JSON.parse(value);
|
|
104
|
+
value = adapter.id === 'claude' ? approval : approval.behavior === 'allow' ? 'allow' : 'deny';
|
|
105
|
+
}
|
|
106
|
+
await driver.answer(event.requestId, value);
|
|
107
|
+
emit({ kind: 'ack', requestId: event.requestId, ok: true, text: 'Native request answered.' });
|
|
108
|
+
}).catch(error => emit({ kind: 'error', text: error.message }));
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const factory = driverFactory || (adapter.id === 'claude' ? createClaudeDriver : adapter.id === 'codex' ? createCodexDriver : createResumeDriver);
|
|
112
|
+
const used = totals();
|
|
113
|
+
const driverOptions = { ...options,
|
|
114
|
+
...(options.maxTurns !== undefined ? { maxTurns: Math.max(1, options.maxTurns - used.turns) } : {}),
|
|
115
|
+
...(options.maxBudgetUsd !== undefined ? { maxBudgetUsd: Math.max(0, options.maxBudgetUsd - used.costUsd) } : {}),
|
|
116
|
+
...(options.maxTokens !== undefined ? { maxTokens: Math.max(1, options.maxTokens - used.inputTokens - used.outputTokens) } : {}) };
|
|
117
|
+
driver = factory({ harness: adapter, binary, args, cwd: request.cwd,
|
|
118
|
+
env: { ...env, TR_METAHARNESS_JOB: job }, job: { id: job }, options: driverOptions, onEvent, onFrame });
|
|
119
|
+
tools = controlToolsFactory({ job, approvalPolicy: options.approvalPolicy || 'auto', questionTimeoutMs: options.questionTimeoutMs });
|
|
120
|
+
const unregister = register(() => { stop('cancelled', 'Job cancelled.'); void driver.close(); });
|
|
121
|
+
const wait = promise => Promise.race([promise, stopped]);
|
|
122
|
+
const capabilities = () => ({ ...driver.capabilities,
|
|
123
|
+
goal: driver.capabilities.goal === 'native' ? 'native' : 'metaharness',
|
|
124
|
+
schema: driver.capabilities.schema === 'native' ? 'native' : 'metaharness',
|
|
125
|
+
limits: options.maxTokens !== undefined || adapter.id !== 'claude' ? 'metaharness' : driver.capabilities.limits });
|
|
126
|
+
const next = async (text, schema) => {
|
|
127
|
+
if (options.maxTurns !== undefined && totals().turns >= options.maxTurns) return { ok: false, failureKind: 'limit', error: 'Job turn limit reached.' };
|
|
128
|
+
return wait(driver.next(text, { resultSchema: schema,
|
|
129
|
+
...(options.maxTurns !== undefined ? { maxTurns: options.maxTurns - totals().turns } : {}),
|
|
130
|
+
...(options.maxBudgetUsd !== undefined && usage.costUsd !== null ? { maxBudgetUsd: Math.max(0, options.maxBudgetUsd - totals().costUsd) } : {}) }));
|
|
131
|
+
};
|
|
132
|
+
try {
|
|
133
|
+
timer = setTimeout(() => stop('process', 'Session timeout.'), request.timeoutMs || 900000);
|
|
134
|
+
if (options.maxTurns !== undefined && used.turns >= options.maxTurns || options.maxTokens !== undefined && used.inputTokens + used.outputTokens >= options.maxTokens || options.maxBudgetUsd !== undefined && used.costUsd >= options.maxBudgetUsd) {
|
|
135
|
+
return { ok: false, failureKind: 'limit', error: 'Job limit reached before launch.', usage, turns };
|
|
136
|
+
}
|
|
137
|
+
if (options.maxBudgetUsd === 0) return { ok: false, failureKind: 'limit', error: 'Job budget limit reached before launch.', usage, turns };
|
|
138
|
+
await wait(driver.start(`${prompt}\n\nJob goal: ${goal}`));
|
|
139
|
+
if (stopResult) return stopResult;
|
|
140
|
+
emit({ kind: 'capabilities', text: 'Session controls reported.', capabilities: capabilities() });
|
|
141
|
+
deferredWatcher = tailInbox(job, command => {
|
|
142
|
+
if (command.type !== 'reply' || !command.requestId) return false;
|
|
143
|
+
deferredReplies.set(command.requestId, command.text);
|
|
144
|
+
replyWaiter?.();
|
|
145
|
+
return { via: 'metaharness' };
|
|
146
|
+
}, { recipient: 'deferred' });
|
|
147
|
+
watcher = tailInbox(job, async command => {
|
|
148
|
+
if (command.type === 'reply') return driver.send(command.text);
|
|
149
|
+
if (command.type === 'approve') return driver.answer(command.requestId, command.value);
|
|
150
|
+
if (command.type === 'interrupt') { await driver.interrupt(); stop('interrupted', 'Turn interrupted; the session can be resumed.'); return { via: 'metaharness' }; }
|
|
151
|
+
if (command.type === 'goal') {
|
|
152
|
+
if (driver.setGoal) await driver.setGoal(command.text); else await driver.send(`Job goal: ${command.text}`);
|
|
153
|
+
goal = command.text;
|
|
154
|
+
return { via: driver.setGoal ? 'native' : 'metaharness' };
|
|
155
|
+
}
|
|
156
|
+
if (command.type === 'fork') {
|
|
157
|
+
const fork = await driver.fork({ atTurn: command.atTurn });
|
|
158
|
+
emit({ kind: 'fork', commandId: command.id, ...fork });
|
|
159
|
+
return { via: driver.capabilities.fork };
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
});
|
|
163
|
+
let result = await wait(driver.done);
|
|
164
|
+
// Deferred questions (A-125): the agent ended its turn after asking. Hold the session with no
|
|
165
|
+
// model calls until the operator replies, then start the next turn with the answer.
|
|
166
|
+
while (result.ok && !stopResult) {
|
|
167
|
+
const open = waitingRequests(job).filter(r => r.kind === 'question' && r.source === 'deferred');
|
|
168
|
+
if (!open.length) break;
|
|
169
|
+
emit({ kind: 'waiting', text: `Waiting for the operator on ${open.length} question(s); no model calls until a reply.` });
|
|
170
|
+
const answer = await wait(new Promise(resolve => {
|
|
171
|
+
const check = () => { const hit = open.find(r => deferredReplies.has(r.requestId)); if (hit) { replyWaiter = null; clearTimeout(limit); resolve({ request: hit, text: deferredReplies.get(hit.requestId) }); } };
|
|
172
|
+
const limit = setTimeout(() => { replyWaiter = null; resolve({ request: open[0], text: NO_ANSWER, timeout: true }); }, options.questionTimeoutMs ?? QUESTION_TIMEOUT_MS);
|
|
173
|
+
replyWaiter = check; check();
|
|
174
|
+
}));
|
|
175
|
+
if (!answer || stopResult) break;
|
|
176
|
+
appendEvent(job, { kind: 'ack', command: 'ask', requestId: answer.request.requestId, ok: true, value: answer.text, text: answer.text, ...(answer.timeout ? { timeout: true } : {}) });
|
|
177
|
+
result = await next(`The operator answered your question ${answer.request.text}: ${answer.text}\nContinue the task.`);
|
|
178
|
+
}
|
|
179
|
+
if (result.ok && options.maxTokens !== undefined && usage.inputTokens === null && usage.outputTokens === null) {
|
|
180
|
+
result = { ...result, ok: false, failureKind: 'limit', error: 'The harness did not report token usage, so the token cap cannot be enforced.' };
|
|
181
|
+
}
|
|
182
|
+
if (result.ok && options.maxBudgetUsd !== undefined && usage.costUsd === null) {
|
|
183
|
+
result = { ...result, ok: false, failureKind: 'limit', error: 'The harness did not report cost, so the USD cap cannot be enforced.' };
|
|
184
|
+
}
|
|
185
|
+
if ((result.ok || result.failureKind === 'schema') && options.resultSchema !== undefined) {
|
|
186
|
+
let parsed = readResult(result, options.resultSchema);
|
|
187
|
+
if (parsed.errors.length) {
|
|
188
|
+
const corrected = await next(`Correct your final result once. Return only JSON matching ${JSON.stringify(options.resultSchema)}. Errors: ${parsed.errors.join('; ')}`, options.resultSchema);
|
|
189
|
+
if (!corrected.ok) return { ...corrected, usage: { ...usage }, turns, capabilities: { ...driver.capabilities } };
|
|
190
|
+
result = corrected; parsed = readResult(result, options.resultSchema);
|
|
191
|
+
}
|
|
192
|
+
if (parsed.errors.length) result = { ...result, ok: false, failureKind: 'schema', error: parsed.errors.join('; ') };
|
|
193
|
+
else result.structured = parsed.value;
|
|
194
|
+
}
|
|
195
|
+
const work = result;
|
|
196
|
+
if (result.ok) {
|
|
197
|
+
const check = await next(`Check the goal against what you did. Goal: ${goal}\nReturn only JSON with schema ${JSON.stringify(GOAL_SCHEMA)}. List concrete evidence and all remaining work.`, GOAL_SCHEMA);
|
|
198
|
+
if (!check.ok) result = { ...work, ...check };
|
|
199
|
+
else {
|
|
200
|
+
const parsed = readResult(check, GOAL_SCHEMA);
|
|
201
|
+
result = { ...work, goalCheck: parsed.value, ...(parsed.errors.length ? { ok: false, failureKind: 'goal', error: parsed.errors.join('; ') }
|
|
202
|
+
: !parsed.value.met ? { ok: false, failureKind: 'goal', error: parsed.value.remaining.join('\n') || 'The goal was not met.' }
|
|
203
|
+
: !parsed.value.evidence.length || parsed.value.remaining.length ? { ok: false, failureKind: 'goal', error: 'The goal check needs evidence and no remaining work.' } : {}) };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { ...result, ...stopResult, usage: { ...usage }, turns,
|
|
207
|
+
capabilities: capabilities(),
|
|
208
|
+
toolResults, toolExecutionVerified: toolResults.length > 0, commandExecutionVerified: toolResults.some(t => t.tool === 'shell' && t.status === 'completed') };
|
|
209
|
+
} finally {
|
|
210
|
+
budget.get(runId).stop = null;
|
|
211
|
+
clearTimeout(timer); unregister();
|
|
212
|
+
// Close outstanding questions before waiting for the inbox consumer.
|
|
213
|
+
await tools.close();
|
|
214
|
+
if (stopTask) {
|
|
215
|
+
let grace;
|
|
216
|
+
try { await Promise.race([stopTask, new Promise(resolve => { grace = setTimeout(resolve, 1000); })]); }
|
|
217
|
+
finally { clearTimeout(grace); }
|
|
218
|
+
}
|
|
219
|
+
await driver.close();
|
|
220
|
+
await watcher?.close();
|
|
221
|
+
await deferredWatcher?.close();
|
|
222
|
+
}
|
|
223
|
+
}
|