@mastra/core 1.4.0 → 1.5.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +368 -0
- package/dist/agent/agent.d.ts +3 -2
- package/dist/agent/agent.d.ts.map +1 -1
- package/dist/agent/index.cjs +13 -13
- package/dist/agent/index.js +2 -2
- package/dist/agent/message-list/conversion/output-converter.d.ts.map +1 -1
- package/dist/agent/message-list/index.cjs +18 -18
- package/dist/agent/message-list/index.js +1 -1
- package/dist/agent/message-list/merge/MessageMerger.d.ts.map +1 -1
- package/dist/agent/message-list/message-list.d.ts.map +1 -1
- package/dist/agent/types.d.ts +2 -2
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/workflows/prepare-stream/index.d.ts +1 -0
- package/dist/agent/workflows/prepare-stream/index.d.ts.map +1 -1
- package/dist/agent/workflows/prepare-stream/prepare-tools-step.d.ts +1 -0
- package/dist/agent/workflows/prepare-stream/prepare-tools-step.d.ts.map +1 -1
- package/dist/agent/workflows/prepare-stream/schema.d.ts +8 -0
- package/dist/agent/workflows/prepare-stream/schema.d.ts.map +1 -1
- package/dist/{chunk-Y3TQ52UE.js → chunk-33TGTTTS.js} +4 -3
- package/dist/chunk-33TGTTTS.js.map +1 -0
- package/dist/{chunk-3X3CZUXI.js → chunk-3KJW4EMO.js} +660 -206
- package/dist/chunk-3KJW4EMO.js.map +1 -0
- package/dist/{chunk-YNNJLLFN.cjs → chunk-3YMDR4OL.cjs} +661 -207
- package/dist/chunk-3YMDR4OL.cjs.map +1 -0
- package/dist/{chunk-NJ7TL3LQ.js → chunk-5EOLBHHS.js} +26 -15
- package/dist/chunk-5EOLBHHS.js.map +1 -0
- package/dist/{chunk-RZ4CIIZR.js → chunk-6DUTLERJ.js} +4 -4
- package/dist/{chunk-RZ4CIIZR.js.map → chunk-6DUTLERJ.js.map} +1 -1
- package/dist/{chunk-VTE2OBKS.cjs → chunk-A6EWCOGA.cjs} +417 -77
- package/dist/chunk-A6EWCOGA.cjs.map +1 -0
- package/dist/{chunk-4XSAZPPS.js → chunk-A7V2NSY3.js} +313 -137
- package/dist/chunk-A7V2NSY3.js.map +1 -0
- package/dist/{chunk-FLPEGTEK.js → chunk-AIRMLZ43.js} +5 -5
- package/dist/{chunk-FLPEGTEK.js.map → chunk-AIRMLZ43.js.map} +1 -1
- package/dist/{chunk-DBSVT6AR.cjs → chunk-BKQAP27M.cjs} +9 -9
- package/dist/{chunk-DBSVT6AR.cjs.map → chunk-BKQAP27M.cjs.map} +1 -1
- package/dist/{chunk-RS6CZXGA.js → chunk-BQHWJLXU.js} +15 -4
- package/dist/chunk-BQHWJLXU.js.map +1 -0
- package/dist/{chunk-V2MLGA7T.js → chunk-CXVMDV2B.js} +417 -78
- package/dist/chunk-CXVMDV2B.js.map +1 -0
- package/dist/{chunk-NKYWDNCI.cjs → chunk-E2FHTXAI.cjs} +7 -7
- package/dist/{chunk-NKYWDNCI.cjs.map → chunk-E2FHTXAI.cjs.map} +1 -1
- package/dist/{chunk-7UWHFWST.cjs → chunk-EAZ6YDCQ.cjs} +15 -3
- package/dist/chunk-EAZ6YDCQ.cjs.map +1 -0
- package/dist/{chunk-64WGYTQK.cjs → chunk-FTBLAVTF.cjs} +55 -55
- package/dist/{chunk-64WGYTQK.cjs.map → chunk-FTBLAVTF.cjs.map} +1 -1
- package/dist/{chunk-4EHGOATH.js → chunk-FZ5DRHKE.js} +1337 -547
- package/dist/chunk-FZ5DRHKE.js.map +1 -0
- package/dist/{chunk-4TQ4EBYX.js → chunk-GEDGDKQ6.js} +9 -9
- package/dist/chunk-GEDGDKQ6.js.map +1 -0
- package/dist/{chunk-QTTWRCB5.js → chunk-I3AWF54W.js} +5 -5
- package/dist/{chunk-QTTWRCB5.js.map → chunk-I3AWF54W.js.map} +1 -1
- package/dist/{chunk-U2HKJZCI.js → chunk-IBNCZTNQ.js} +6 -6
- package/dist/{chunk-U2HKJZCI.js.map → chunk-IBNCZTNQ.js.map} +1 -1
- package/dist/{chunk-3JVFFAJX.cjs → chunk-IJIE3ZID.cjs} +27 -16
- package/dist/chunk-IJIE3ZID.cjs.map +1 -0
- package/dist/{chunk-NZG2JAKS.cjs → chunk-JWG272ZZ.cjs} +19 -19
- package/dist/chunk-JWG272ZZ.cjs.map +1 -0
- package/dist/{chunk-BP7VYTOP.cjs → chunk-JZ6TH4HQ.cjs} +954 -401
- package/dist/chunk-JZ6TH4HQ.cjs.map +1 -0
- package/dist/{chunk-SU5APAM6.cjs → chunk-KNXZ7KYL.cjs} +94 -6
- package/dist/chunk-KNXZ7KYL.cjs.map +1 -0
- package/dist/{chunk-CYUP7QWT.cjs → chunk-KRAGJ433.cjs} +4 -3
- package/dist/chunk-KRAGJ433.cjs.map +1 -0
- package/dist/{chunk-XDD5V446.cjs → chunk-MDC6VYA6.cjs} +6 -2
- package/dist/{chunk-XDD5V446.cjs.map → chunk-MDC6VYA6.cjs.map} +1 -1
- package/dist/{chunk-AXHBJ4GX.js → chunk-NN26FSKL.js} +10 -8
- package/dist/chunk-NN26FSKL.js.map +1 -0
- package/dist/{chunk-AY6DBRS3.js → chunk-OHLVZVIK.js} +36 -2
- package/dist/chunk-OHLVZVIK.js.map +1 -0
- package/dist/{chunk-5Q5Y34SS.js → chunk-PECKKR4C.js} +4 -4
- package/dist/{chunk-5Q5Y34SS.js.map → chunk-PECKKR4C.js.map} +1 -1
- package/dist/{chunk-HYRYTTMT.cjs → chunk-PHHJLGZU.cjs} +9 -9
- package/dist/{chunk-HYRYTTMT.cjs.map → chunk-PHHJLGZU.cjs.map} +1 -1
- package/dist/{chunk-65PHUUMF.cjs → chunk-QDH6MVJ7.cjs} +24 -22
- package/dist/chunk-QDH6MVJ7.cjs.map +1 -0
- package/dist/{chunk-VD5YA6RH.cjs → chunk-QSN5KQXZ.cjs} +18 -18
- package/dist/{chunk-VD5YA6RH.cjs.map → chunk-QSN5KQXZ.cjs.map} +1 -1
- package/dist/{chunk-4IJ4UDZX.cjs → chunk-RH2K66O2.cjs} +399 -223
- package/dist/chunk-RH2K66O2.cjs.map +1 -0
- package/dist/{chunk-4KFEMXTV.cjs → chunk-S4VVZI4E.cjs} +1361 -546
- package/dist/chunk-S4VVZI4E.cjs.map +1 -0
- package/dist/{chunk-ZATLLPIH.js → chunk-TPDMP7OD.js} +6 -2
- package/dist/chunk-TPDMP7OD.js.map +1 -0
- package/dist/{chunk-PS5ONCXY.js → chunk-UZFGMMKU.js} +82 -4
- package/dist/chunk-UZFGMMKU.js.map +1 -0
- package/dist/{chunk-7NKUSQEV.js → chunk-YIN5F7VO.js} +936 -389
- package/dist/chunk-YIN5F7VO.js.map +1 -0
- package/dist/{chunk-CZ4NQANZ.cjs → chunk-YW54RH77.cjs} +36 -2
- package/dist/chunk-YW54RH77.cjs.map +1 -0
- package/dist/datasets/experiment/executor.d.ts.map +1 -1
- package/dist/datasets/index.cjs +17 -17
- package/dist/datasets/index.js +2 -2
- package/dist/docs/SKILL.md +27 -1
- package/dist/docs/assets/SOURCE_MAP.json +463 -389
- package/dist/docs/references/docs-agents-processors.md +52 -0
- package/dist/docs/references/docs-observability-datasets-overview.md +188 -0
- package/dist/docs/references/docs-observability-datasets-running-experiments.md +266 -0
- package/dist/docs/references/docs-observability-tracing-exporters-cloud.md +7 -4
- package/dist/docs/references/reference-agents-generate.md +1 -1
- package/dist/docs/references/reference-configuration.md +3 -4
- package/dist/docs/references/reference-datasets-addItem.md +35 -0
- package/dist/docs/references/reference-datasets-addItems.md +33 -0
- package/dist/docs/references/reference-datasets-compareExperiments.md +48 -0
- package/dist/docs/references/reference-datasets-create.md +49 -0
- package/dist/docs/references/reference-datasets-dataset.md +78 -0
- package/dist/docs/references/reference-datasets-datasets-manager.md +84 -0
- package/dist/docs/references/reference-datasets-delete.md +23 -0
- package/dist/docs/references/reference-datasets-deleteExperiment.md +25 -0
- package/dist/docs/references/reference-datasets-deleteItem.md +25 -0
- package/dist/docs/references/reference-datasets-deleteItems.md +27 -0
- package/dist/docs/references/reference-datasets-get.md +29 -0
- package/dist/docs/references/reference-datasets-getDetails.md +45 -0
- package/dist/docs/references/reference-datasets-getExperiment.md +28 -0
- package/dist/docs/references/reference-datasets-getItem.md +31 -0
- package/dist/docs/references/reference-datasets-getItemHistory.md +29 -0
- package/dist/docs/references/reference-datasets-list.md +29 -0
- package/dist/docs/references/reference-datasets-listExperimentResults.md +37 -0
- package/dist/docs/references/reference-datasets-listExperiments.md +31 -0
- package/dist/docs/references/reference-datasets-listItems.md +44 -0
- package/dist/docs/references/reference-datasets-listVersions.md +31 -0
- package/dist/docs/references/reference-datasets-startExperiment.md +60 -0
- package/dist/docs/references/reference-datasets-startExperimentAsync.md +41 -0
- package/dist/docs/references/reference-datasets-update.md +46 -0
- package/dist/docs/references/reference-datasets-updateItem.md +36 -0
- package/dist/docs/references/reference-memory-observational-memory.md +36 -0
- package/dist/docs/references/reference-processors-processor-interface.md +4 -0
- package/dist/docs/references/reference-tools-create-tool.md +1 -1
- package/dist/docs/references/reference.md +24 -0
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.d.ts.map +1 -1
- package/dist/editor/types.d.ts +108 -2
- package/dist/editor/types.d.ts.map +1 -1
- package/dist/evals/index.cjs +20 -20
- package/dist/evals/index.js +3 -3
- package/dist/evals/scoreTraces/index.cjs +5 -5
- package/dist/evals/scoreTraces/index.js +2 -2
- package/dist/harness/harness.d.ts +281 -0
- package/dist/harness/harness.d.ts.map +1 -0
- package/dist/harness/index.cjs +1728 -0
- package/dist/harness/index.cjs.map +1 -0
- package/dist/harness/index.d.ts +4 -0
- package/dist/harness/index.d.ts.map +1 -0
- package/dist/harness/index.js +1723 -0
- package/dist/harness/index.js.map +1 -0
- package/dist/harness/tools.d.ts +65 -0
- package/dist/harness/tools.d.ts.map +1 -0
- package/dist/harness/types.d.ts +561 -0
- package/dist/harness/types.d.ts.map +1 -0
- package/dist/index.cjs +2 -2
- package/dist/index.js +1 -1
- package/dist/integration/index.cjs +2 -2
- package/dist/integration/index.js +1 -1
- package/dist/llm/index.cjs +20 -20
- package/dist/llm/index.js +3 -3
- package/dist/llm/model/gateways/constants.d.ts.map +1 -1
- package/dist/llm/model/gateways/models-dev.d.ts +2 -3
- package/dist/llm/model/gateways/models-dev.d.ts.map +1 -1
- package/dist/llm/model/provider-types.generated.d.ts +312 -93
- package/dist/loop/index.cjs +12 -12
- package/dist/loop/index.js +1 -1
- package/dist/loop/network/index.d.ts.map +1 -1
- package/dist/loop/test-utils/options.d.ts.map +1 -1
- package/dist/loop/test-utils/tools.d.ts.map +1 -1
- package/dist/loop/workflows/agentic-execution/llm-mapping-step.d.ts.map +1 -1
- package/dist/loop/workflows/agentic-execution/tool-call-step.d.ts.map +1 -1
- package/dist/loop/workflows/stream.d.ts.map +1 -1
- package/dist/mastra/index.cjs +2 -2
- package/dist/mastra/index.d.ts +3 -3
- package/dist/mastra/index.d.ts.map +1 -1
- package/dist/mastra/index.js +1 -1
- package/dist/memory/index.cjs +14 -14
- package/dist/memory/index.js +1 -1
- package/dist/memory/memory.d.ts +10 -0
- package/dist/memory/memory.d.ts.map +1 -1
- package/dist/memory/types.d.ts +24 -0
- package/dist/memory/types.d.ts.map +1 -1
- package/dist/models-dev-BW2GAM3K.cjs +12 -0
- package/dist/{models-dev-PPIXUUCU.cjs.map → models-dev-BW2GAM3K.cjs.map} +1 -1
- package/dist/models-dev-MDI5E2YA.js +3 -0
- package/dist/{models-dev-FQVUTQ7L.js.map → models-dev-MDI5E2YA.js.map} +1 -1
- package/dist/observability/index.cjs +11 -11
- package/dist/observability/index.js +1 -1
- package/dist/observability/utils.d.ts.map +1 -1
- package/dist/processors/index.cjs +41 -41
- package/dist/processors/index.js +1 -1
- package/dist/processors/processors/skills.d.ts +5 -0
- package/dist/processors/processors/skills.d.ts.map +1 -1
- package/dist/processors/runner.d.ts +2 -2
- package/dist/processors/runner.d.ts.map +1 -1
- package/dist/processors/step-schema.d.ts +218 -49453
- package/dist/processors/step-schema.d.ts.map +1 -1
- package/dist/provider-registry-4PH2JPIA.cjs +40 -0
- package/dist/{provider-registry-6LZAGQET.cjs.map → provider-registry-4PH2JPIA.cjs.map} +1 -1
- package/dist/provider-registry-VEJ3PN4S.js +3 -0
- package/dist/{provider-registry-QUNT7S55.js.map → provider-registry-VEJ3PN4S.js.map} +1 -1
- package/dist/provider-registry.json +657 -203
- package/dist/relevance/index.cjs +3 -3
- package/dist/relevance/index.js +1 -1
- package/dist/server/composite-auth.d.ts.map +1 -1
- package/dist/server/index.cjs +6 -1
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.js +6 -1
- package/dist/server/index.js.map +1 -1
- package/dist/storage/base.d.ts +4 -1
- package/dist/storage/base.d.ts.map +1 -1
- package/dist/storage/constants.cjs +82 -42
- package/dist/storage/constants.d.ts +11 -1
- package/dist/storage/constants.d.ts.map +1 -1
- package/dist/storage/constants.js +1 -1
- package/dist/storage/domains/agents/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/blobs/base.d.ts +47 -0
- package/dist/storage/domains/blobs/base.d.ts.map +1 -0
- package/dist/storage/domains/blobs/index.d.ts +3 -0
- package/dist/storage/domains/blobs/index.d.ts.map +1 -0
- package/dist/storage/domains/blobs/inmemory.d.ts +17 -0
- package/dist/storage/domains/blobs/inmemory.d.ts.map +1 -0
- package/dist/storage/domains/datasets/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/index.d.ts +3 -0
- package/dist/storage/domains/index.d.ts.map +1 -1
- package/dist/storage/domains/inmemory-db.d.ts +7 -1
- package/dist/storage/domains/inmemory-db.d.ts.map +1 -1
- package/dist/storage/domains/mcp-clients/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/operations/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/prompt-blocks/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/scorer-definitions/inmemory.d.ts.map +1 -1
- package/dist/storage/domains/skills/base.d.ts +47 -0
- package/dist/storage/domains/skills/base.d.ts.map +1 -0
- package/dist/storage/domains/skills/index.d.ts +3 -0
- package/dist/storage/domains/skills/index.d.ts.map +1 -0
- package/dist/storage/domains/skills/inmemory.d.ts +31 -0
- package/dist/storage/domains/skills/inmemory.d.ts.map +1 -0
- package/dist/storage/domains/versioned.d.ts +12 -3
- package/dist/storage/domains/versioned.d.ts.map +1 -1
- package/dist/storage/domains/workspaces/base.d.ts +47 -0
- package/dist/storage/domains/workspaces/base.d.ts.map +1 -0
- package/dist/storage/domains/workspaces/index.d.ts +3 -0
- package/dist/storage/domains/workspaces/index.d.ts.map +1 -0
- package/dist/storage/domains/workspaces/inmemory.d.ts +31 -0
- package/dist/storage/domains/workspaces/inmemory.d.ts.map +1 -0
- package/dist/storage/index.cjs +202 -138
- package/dist/storage/index.js +2 -2
- package/dist/storage/mock.d.ts.map +1 -1
- package/dist/storage/types.d.ts +422 -12
- package/dist/storage/types.d.ts.map +1 -1
- package/dist/stream/base/output.d.ts.map +1 -1
- package/dist/stream/index.cjs +11 -11
- package/dist/stream/index.js +2 -2
- package/dist/test-utils/llm-mock.cjs +4 -4
- package/dist/test-utils/llm-mock.js +1 -1
- package/dist/tool-loop-agent/index.cjs +4 -4
- package/dist/tool-loop-agent/index.js +1 -1
- package/dist/tools/index.cjs +8 -4
- package/dist/tools/index.js +1 -1
- package/dist/tools/is-vercel-tool.cjs +2 -2
- package/dist/tools/is-vercel-tool.js +1 -1
- package/dist/tools/tool-builder/builder.d.ts.map +1 -1
- package/dist/tools/tool.d.ts +13 -0
- package/dist/tools/tool.d.ts.map +1 -1
- package/dist/tools/toolchecks.d.ts.map +1 -1
- package/dist/tools/types.d.ts +24 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/types/zod-compat.d.ts +27 -6
- package/dist/types/zod-compat.d.ts.map +1 -1
- package/dist/utils.cjs +23 -23
- package/dist/utils.js +1 -1
- package/dist/vector/index.cjs +12 -12
- package/dist/vector/index.js +2 -2
- package/dist/workflows/evented/index.cjs +10 -10
- package/dist/workflows/evented/index.js +1 -1
- package/dist/workflows/handlers/entry.d.ts.map +1 -1
- package/dist/workflows/index.cjs +25 -25
- package/dist/workflows/index.js +1 -1
- package/dist/workflows/workflow.d.ts +2 -2
- package/dist/workflows/workflow.d.ts.map +1 -1
- package/dist/workspace/constants/index.d.ts +1 -0
- package/dist/workspace/constants/index.d.ts.map +1 -1
- package/dist/workspace/errors.d.ts +3 -0
- package/dist/workspace/errors.d.ts.map +1 -1
- package/dist/workspace/filesystem/composite-filesystem.d.ts +75 -6
- package/dist/workspace/filesystem/composite-filesystem.d.ts.map +1 -1
- package/dist/workspace/filesystem/local-filesystem.d.ts +42 -0
- package/dist/workspace/filesystem/local-filesystem.d.ts.map +1 -1
- package/dist/workspace/glob.d.ts +61 -0
- package/dist/workspace/glob.d.ts.map +1 -0
- package/dist/workspace/index.cjs +133 -41
- package/dist/workspace/index.d.ts +8 -1
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/workspace/index.js +1 -1
- package/dist/workspace/sandbox/local-sandbox.d.ts.map +1 -1
- package/dist/workspace/skills/composite-versioned-skill-source.d.ts +44 -0
- package/dist/workspace/skills/composite-versioned-skill-source.d.ts.map +1 -0
- package/dist/workspace/skills/index.d.ts +3 -0
- package/dist/workspace/skills/index.d.ts.map +1 -1
- package/dist/workspace/skills/local-skill-source.d.ts.map +1 -1
- package/dist/workspace/skills/publish.d.ts +34 -0
- package/dist/workspace/skills/publish.d.ts.map +1 -0
- package/dist/workspace/skills/skill-source.d.ts +2 -0
- package/dist/workspace/skills/skill-source.d.ts.map +1 -1
- package/dist/workspace/skills/types.d.ts +16 -0
- package/dist/workspace/skills/types.d.ts.map +1 -1
- package/dist/workspace/skills/versioned-skill-source.d.ts +20 -0
- package/dist/workspace/skills/versioned-skill-source.d.ts.map +1 -0
- package/dist/workspace/skills/workspace-skills.d.ts +5 -0
- package/dist/workspace/skills/workspace-skills.d.ts.map +1 -1
- package/dist/workspace/tools/delete-file.d.ts +5 -0
- package/dist/workspace/tools/delete-file.d.ts.map +1 -0
- package/dist/workspace/tools/edit-file.d.ts +7 -0
- package/dist/workspace/tools/edit-file.d.ts.map +1 -0
- package/dist/workspace/tools/execute-command.d.ts +7 -0
- package/dist/workspace/tools/execute-command.d.ts.map +1 -0
- package/dist/workspace/tools/file-stat.d.ts +4 -0
- package/dist/workspace/tools/file-stat.d.ts.map +1 -0
- package/dist/workspace/tools/grep.d.ts +9 -0
- package/dist/workspace/tools/grep.d.ts.map +1 -0
- package/dist/workspace/tools/helpers.d.ts +36 -0
- package/dist/workspace/tools/helpers.d.ts.map +1 -0
- package/dist/workspace/tools/index-content.d.ts +6 -0
- package/dist/workspace/tools/index-content.d.ts.map +1 -0
- package/dist/workspace/tools/index.d.ts +13 -1
- package/dist/workspace/tools/index.d.ts.map +1 -1
- package/dist/workspace/tools/list-files.d.ts +10 -0
- package/dist/workspace/tools/list-files.d.ts.map +1 -0
- package/dist/workspace/tools/mkdir.d.ts +5 -0
- package/dist/workspace/tools/mkdir.d.ts.map +1 -0
- package/dist/workspace/tools/read-file.d.ts +8 -0
- package/dist/workspace/tools/read-file.d.ts.map +1 -0
- package/dist/workspace/tools/search.d.ts +7 -0
- package/dist/workspace/tools/search.d.ts.map +1 -0
- package/dist/workspace/tools/tools.d.ts +5 -7
- package/dist/workspace/tools/tools.d.ts.map +1 -1
- package/dist/workspace/tools/tree-formatter.d.ts +2 -0
- package/dist/workspace/tools/tree-formatter.d.ts.map +1 -1
- package/dist/workspace/tools/write-file.d.ts +6 -0
- package/dist/workspace/tools/write-file.d.ts.map +1 -0
- package/dist/workspace/workspace.d.ts +56 -10
- package/dist/workspace/workspace.d.ts.map +1 -1
- package/harness.d.ts +1 -0
- package/package.json +8 -6
- package/src/llm/model/provider-types.generated.d.ts +312 -93
- package/dist/chunk-3JVFFAJX.cjs.map +0 -1
- package/dist/chunk-3X3CZUXI.js.map +0 -1
- package/dist/chunk-4EHGOATH.js.map +0 -1
- package/dist/chunk-4IJ4UDZX.cjs.map +0 -1
- package/dist/chunk-4KFEMXTV.cjs.map +0 -1
- package/dist/chunk-4TQ4EBYX.js.map +0 -1
- package/dist/chunk-4XSAZPPS.js.map +0 -1
- package/dist/chunk-65PHUUMF.cjs.map +0 -1
- package/dist/chunk-7NKUSQEV.js.map +0 -1
- package/dist/chunk-7UWHFWST.cjs.map +0 -1
- package/dist/chunk-AXHBJ4GX.js.map +0 -1
- package/dist/chunk-AY6DBRS3.js.map +0 -1
- package/dist/chunk-BP7VYTOP.cjs.map +0 -1
- package/dist/chunk-CYUP7QWT.cjs.map +0 -1
- package/dist/chunk-CZ4NQANZ.cjs.map +0 -1
- package/dist/chunk-NJ7TL3LQ.js.map +0 -1
- package/dist/chunk-NZG2JAKS.cjs.map +0 -1
- package/dist/chunk-PS5ONCXY.js.map +0 -1
- package/dist/chunk-RS6CZXGA.js.map +0 -1
- package/dist/chunk-SU5APAM6.cjs.map +0 -1
- package/dist/chunk-V2MLGA7T.js.map +0 -1
- package/dist/chunk-VTE2OBKS.cjs.map +0 -1
- package/dist/chunk-Y3TQ52UE.js.map +0 -1
- package/dist/chunk-YNNJLLFN.cjs.map +0 -1
- package/dist/chunk-ZATLLPIH.js.map +0 -1
- package/dist/models-dev-FQVUTQ7L.js +0 -3
- package/dist/models-dev-PPIXUUCU.cjs +0 -12
- package/dist/provider-registry-6LZAGQET.cjs +0 -40
- package/dist/provider-registry-QUNT7S55.js +0 -3
|
@@ -0,0 +1,1723 @@
|
|
|
1
|
+
import { Agent } from '../chunk-A7V2NSY3.js';
|
|
2
|
+
import { Workspace } from '../chunk-FZ5DRHKE.js';
|
|
3
|
+
import { createTool } from '../chunk-BQHWJLXU.js';
|
|
4
|
+
import { RequestContext } from '../chunk-CCLV5CAA.js';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
var questionCounter = 0;
|
|
8
|
+
var planCounter = 0;
|
|
9
|
+
var askUserTool = createTool({
|
|
10
|
+
id: "ask_user",
|
|
11
|
+
description: "Ask the user a question and wait for their response. Use this when you need clarification, want to validate assumptions, or need the user to make a decision between options. Provide options for structured choices (2-4 options), or omit them for open-ended questions.",
|
|
12
|
+
inputSchema: z.object({
|
|
13
|
+
question: z.string().min(1).describe("The question to ask the user. Should be clear and specific."),
|
|
14
|
+
options: z.array(
|
|
15
|
+
z.object({
|
|
16
|
+
label: z.string().describe("Short display text for this option (1-5 words)"),
|
|
17
|
+
description: z.string().optional().describe("Explanation of what this option means")
|
|
18
|
+
})
|
|
19
|
+
).optional().describe("Optional choices. If provided, shows a selection list. If omitted, shows a free-text input.")
|
|
20
|
+
}),
|
|
21
|
+
execute: async ({ question, options }, context) => {
|
|
22
|
+
try {
|
|
23
|
+
const harnessCtx = context?.requestContext?.get("harness");
|
|
24
|
+
if (!harnessCtx?.emitEvent || !harnessCtx?.registerQuestion) {
|
|
25
|
+
return {
|
|
26
|
+
content: `[Question for user]: ${question}${options ? "\nOptions: " + options.map((o) => o.label).join(", ") : ""}`,
|
|
27
|
+
isError: false
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const questionId = `q_${++questionCounter}_${Date.now()}`;
|
|
31
|
+
const answer = await new Promise((resolve, reject) => {
|
|
32
|
+
const signal = harnessCtx.abortSignal;
|
|
33
|
+
if (signal?.aborted) {
|
|
34
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const onAbort = () => reject(new DOMException("Aborted", "AbortError"));
|
|
38
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
39
|
+
harnessCtx.registerQuestion(questionId, (answer2) => {
|
|
40
|
+
signal?.removeEventListener("abort", onAbort);
|
|
41
|
+
resolve(answer2);
|
|
42
|
+
});
|
|
43
|
+
harnessCtx.emitEvent({
|
|
44
|
+
type: "ask_question",
|
|
45
|
+
questionId,
|
|
46
|
+
question,
|
|
47
|
+
options
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
return { content: `User answered: ${answer}`, isError: false };
|
|
51
|
+
} catch (error) {
|
|
52
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
53
|
+
return { content: `Failed to ask user: ${msg}`, isError: true };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
var submitPlanTool = createTool({
|
|
58
|
+
id: "submit_plan",
|
|
59
|
+
description: "Submit a completed implementation plan for user review. The plan will be rendered as markdown and the user can approve, reject, or request changes. Use this when your exploration is complete and you have a concrete plan ready for review. On approval, the system automatically switches to the default mode so you can implement.",
|
|
60
|
+
inputSchema: z.object({
|
|
61
|
+
title: z.string().optional().describe("Short title for the plan (e.g., 'Add dark mode toggle')"),
|
|
62
|
+
plan: z.string().min(1).describe("The full plan content in markdown format. Should include Overview, Steps, and Verification sections.")
|
|
63
|
+
}),
|
|
64
|
+
execute: async ({ title, plan }, context) => {
|
|
65
|
+
try {
|
|
66
|
+
const harnessCtx = context?.requestContext?.get("harness");
|
|
67
|
+
if (!harnessCtx?.emitEvent || !harnessCtx?.registerPlanApproval) {
|
|
68
|
+
return {
|
|
69
|
+
content: `[Plan submitted for review]
|
|
70
|
+
|
|
71
|
+
Title: ${title || "Implementation Plan"}
|
|
72
|
+
|
|
73
|
+
${plan}`,
|
|
74
|
+
isError: false
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const planId = `plan_${++planCounter}_${Date.now()}`;
|
|
78
|
+
const result = await new Promise((resolve, reject) => {
|
|
79
|
+
const signal = harnessCtx.abortSignal;
|
|
80
|
+
if (signal?.aborted) {
|
|
81
|
+
reject(new DOMException("Aborted", "AbortError"));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const onAbort = () => reject(new DOMException("Aborted", "AbortError"));
|
|
85
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
86
|
+
harnessCtx.registerPlanApproval(planId, (res) => {
|
|
87
|
+
signal?.removeEventListener("abort", onAbort);
|
|
88
|
+
resolve(res);
|
|
89
|
+
});
|
|
90
|
+
harnessCtx.emitEvent({
|
|
91
|
+
type: "plan_approval_required",
|
|
92
|
+
planId,
|
|
93
|
+
title: title || "Implementation Plan",
|
|
94
|
+
plan
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
if (result.action === "approved") {
|
|
98
|
+
return {
|
|
99
|
+
content: "Plan approved. Proceed with implementation following the approved plan.",
|
|
100
|
+
isError: false
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
const feedback = result.feedback ? `
|
|
104
|
+
|
|
105
|
+
User feedback: ${result.feedback}` : "";
|
|
106
|
+
return {
|
|
107
|
+
content: `Plan was not approved. The user wants revisions.${feedback}
|
|
108
|
+
|
|
109
|
+
Please revise the plan based on the feedback and submit again with submit_plan.`,
|
|
110
|
+
isError: false
|
|
111
|
+
};
|
|
112
|
+
} catch (error) {
|
|
113
|
+
const msg = error instanceof Error ? error.message : "Unknown error";
|
|
114
|
+
return { content: `Failed to submit plan: ${msg}`, isError: true };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
function createSubagentTool(opts) {
|
|
119
|
+
const { subagents, resolveModel, harnessTools, fallbackModelId } = opts;
|
|
120
|
+
const subagentIds = subagents.map((s) => s.id);
|
|
121
|
+
const typeDescriptions = subagents.map((s) => `- **${s.id}** (${s.name}): ${s.description}`).join("\n");
|
|
122
|
+
return createTool({
|
|
123
|
+
id: "subagent",
|
|
124
|
+
description: `Delegate a focused task to a specialized subagent. The subagent runs independently with a constrained toolset, then returns its findings as text.
|
|
125
|
+
|
|
126
|
+
Available agent types:
|
|
127
|
+
${typeDescriptions}
|
|
128
|
+
|
|
129
|
+
The subagent runs in its own context \u2014 it does NOT see the parent conversation history. Write a clear, self-contained task description.
|
|
130
|
+
|
|
131
|
+
Use this tool when:
|
|
132
|
+
- You want to run multiple investigations in parallel
|
|
133
|
+
- The task is self-contained and can be delegated`,
|
|
134
|
+
inputSchema: z.object({
|
|
135
|
+
agentType: z.enum(subagentIds).describe("Type of subagent to spawn"),
|
|
136
|
+
task: z.string().describe(
|
|
137
|
+
"Clear, self-contained description of what the subagent should do. Include all relevant context \u2014 the subagent cannot see the parent conversation."
|
|
138
|
+
),
|
|
139
|
+
modelId: z.string().optional().describe("Optional model ID override for this task.")
|
|
140
|
+
}),
|
|
141
|
+
execute: async ({ agentType, task, modelId }, context) => {
|
|
142
|
+
const definition = subagents.find((s) => s.id === agentType);
|
|
143
|
+
if (!definition) {
|
|
144
|
+
return {
|
|
145
|
+
content: `Unknown agent type: ${agentType}. Valid types: ${subagentIds.join(", ")}`,
|
|
146
|
+
isError: true
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
const harnessCtx = context?.requestContext?.get("harness");
|
|
150
|
+
const emitEvent = harnessCtx?.emitEvent;
|
|
151
|
+
const abortSignal = harnessCtx?.abortSignal;
|
|
152
|
+
const toolCallId = context?.agent?.toolCallId ?? "unknown";
|
|
153
|
+
const mergedTools = { ...definition.tools };
|
|
154
|
+
if (definition.allowedHarnessTools && harnessTools) {
|
|
155
|
+
for (const toolId of definition.allowedHarnessTools) {
|
|
156
|
+
if (harnessTools[toolId] && !mergedTools[toolId]) {
|
|
157
|
+
mergedTools[toolId] = harnessTools[toolId];
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const harnessModelId = harnessCtx?.getSubagentModelId?.(agentType) ?? void 0;
|
|
162
|
+
const resolvedModelId = modelId ?? harnessModelId ?? definition.defaultModelId ?? fallbackModelId;
|
|
163
|
+
if (!resolvedModelId) {
|
|
164
|
+
return { content: "No model ID available for subagent. Configure defaultModelId.", isError: true };
|
|
165
|
+
}
|
|
166
|
+
let model;
|
|
167
|
+
try {
|
|
168
|
+
model = resolveModel(resolvedModelId);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
return {
|
|
171
|
+
content: `Failed to resolve model "${resolvedModelId}": ${err instanceof Error ? err.message : String(err)}`,
|
|
172
|
+
isError: true
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const subagent = new Agent({
|
|
176
|
+
id: `subagent-${definition.id}`,
|
|
177
|
+
name: `${definition.name} Subagent`,
|
|
178
|
+
instructions: definition.instructions,
|
|
179
|
+
model,
|
|
180
|
+
tools: mergedTools
|
|
181
|
+
});
|
|
182
|
+
const startTime = Date.now();
|
|
183
|
+
emitEvent?.({
|
|
184
|
+
type: "subagent_start",
|
|
185
|
+
toolCallId,
|
|
186
|
+
agentType,
|
|
187
|
+
task,
|
|
188
|
+
modelId: resolvedModelId
|
|
189
|
+
});
|
|
190
|
+
let partialText = "";
|
|
191
|
+
const toolCallLog = [];
|
|
192
|
+
try {
|
|
193
|
+
const response = await subagent.stream(task, {
|
|
194
|
+
maxSteps: 50,
|
|
195
|
+
abortSignal,
|
|
196
|
+
requireToolApproval: false
|
|
197
|
+
});
|
|
198
|
+
for await (const chunk of response.fullStream) {
|
|
199
|
+
switch (chunk.type) {
|
|
200
|
+
case "text-delta":
|
|
201
|
+
partialText += chunk.payload.text;
|
|
202
|
+
emitEvent?.({
|
|
203
|
+
type: "subagent_text_delta",
|
|
204
|
+
toolCallId,
|
|
205
|
+
agentType,
|
|
206
|
+
textDelta: chunk.payload.text
|
|
207
|
+
});
|
|
208
|
+
break;
|
|
209
|
+
case "tool-call":
|
|
210
|
+
toolCallLog.push({ name: chunk.payload.toolName, toolCallId: chunk.payload.toolCallId });
|
|
211
|
+
emitEvent?.({
|
|
212
|
+
type: "subagent_tool_start",
|
|
213
|
+
toolCallId,
|
|
214
|
+
agentType,
|
|
215
|
+
subToolName: chunk.payload.toolName,
|
|
216
|
+
subToolArgs: chunk.payload.args
|
|
217
|
+
});
|
|
218
|
+
break;
|
|
219
|
+
case "tool-result": {
|
|
220
|
+
const isErr = chunk.payload.isError ?? false;
|
|
221
|
+
for (let i = toolCallLog.length - 1; i >= 0; i--) {
|
|
222
|
+
if (toolCallLog[i].toolCallId === chunk.payload.toolCallId && toolCallLog[i].isError === void 0) {
|
|
223
|
+
toolCallLog[i].isError = isErr;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
emitEvent?.({
|
|
228
|
+
type: "subagent_tool_end",
|
|
229
|
+
toolCallId,
|
|
230
|
+
agentType,
|
|
231
|
+
subToolName: chunk.payload.toolName,
|
|
232
|
+
subToolResult: chunk.payload.result,
|
|
233
|
+
isError: isErr
|
|
234
|
+
});
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (abortSignal?.aborted) {
|
|
240
|
+
const durationMs2 = Date.now() - startTime;
|
|
241
|
+
const abortResult = partialText ? `[Aborted by user]
|
|
242
|
+
|
|
243
|
+
Partial output:
|
|
244
|
+
${partialText}` : "[Aborted by user]";
|
|
245
|
+
emitEvent?.({ type: "subagent_end", toolCallId, agentType, result: abortResult, isError: false, durationMs: durationMs2 });
|
|
246
|
+
const meta2 = buildSubagentMeta(resolvedModelId, durationMs2, toolCallLog);
|
|
247
|
+
return { content: abortResult + meta2, isError: false };
|
|
248
|
+
}
|
|
249
|
+
const fullOutput = await response.getFullOutput();
|
|
250
|
+
const resultText = fullOutput.text || partialText;
|
|
251
|
+
const durationMs = Date.now() - startTime;
|
|
252
|
+
emitEvent?.({ type: "subagent_end", toolCallId, agentType, result: resultText, isError: false, durationMs });
|
|
253
|
+
const meta = buildSubagentMeta(resolvedModelId, durationMs, toolCallLog);
|
|
254
|
+
return { content: resultText + meta, isError: false };
|
|
255
|
+
} catch (err) {
|
|
256
|
+
const isAbort = err instanceof Error && (err.name === "AbortError" || err.message?.includes("abort") || err.message?.includes("cancel"));
|
|
257
|
+
const durationMs = Date.now() - startTime;
|
|
258
|
+
if (isAbort) {
|
|
259
|
+
const abortResult = partialText ? `[Aborted by user]
|
|
260
|
+
|
|
261
|
+
Partial output:
|
|
262
|
+
${partialText}` : "[Aborted by user]";
|
|
263
|
+
emitEvent?.({ type: "subagent_end", toolCallId, agentType, result: abortResult, isError: false, durationMs });
|
|
264
|
+
const meta2 = buildSubagentMeta(resolvedModelId, durationMs, toolCallLog);
|
|
265
|
+
return { content: abortResult + meta2, isError: false };
|
|
266
|
+
}
|
|
267
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
268
|
+
emitEvent?.({ type: "subagent_end", toolCallId, agentType, result: message, isError: true, durationMs });
|
|
269
|
+
const meta = buildSubagentMeta(resolvedModelId, durationMs, toolCallLog);
|
|
270
|
+
return { content: `Subagent "${definition.name}" failed: ${message}` + meta, isError: true };
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
function buildSubagentMeta(modelId, durationMs, toolCalls) {
|
|
276
|
+
const tools = toolCalls.map((tc) => `${tc.name}:${tc.isError ? "err" : "ok"}`).join(",");
|
|
277
|
+
return `
|
|
278
|
+
<subagent-meta modelId="${modelId}" durationMs="${durationMs}" tools="${tools}" />`;
|
|
279
|
+
}
|
|
280
|
+
function parseSubagentMeta(content) {
|
|
281
|
+
const match = content.match(/\n<subagent-meta modelId="([^"]*)" durationMs="(\d+)" tools="([^"]*)" \/>$/);
|
|
282
|
+
if (!match) return { text: content };
|
|
283
|
+
const text = content.slice(0, match.index);
|
|
284
|
+
const modelId = match[1];
|
|
285
|
+
const durationMs = parseInt(match[2], 10);
|
|
286
|
+
const toolCalls = match[3] ? match[3].split(",").filter(Boolean).map((entry) => {
|
|
287
|
+
const [name, status] = entry.split(":");
|
|
288
|
+
return { name, isError: status === "err" };
|
|
289
|
+
}) : [];
|
|
290
|
+
return { text, modelId, durationMs, toolCalls };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/harness/harness.ts
|
|
294
|
+
var Harness = class {
|
|
295
|
+
id;
|
|
296
|
+
config;
|
|
297
|
+
state;
|
|
298
|
+
currentModeId;
|
|
299
|
+
currentThreadId = null;
|
|
300
|
+
resourceId;
|
|
301
|
+
listeners = [];
|
|
302
|
+
abortController = null;
|
|
303
|
+
abortRequested = false;
|
|
304
|
+
currentRunId = null;
|
|
305
|
+
currentOperationId = 0;
|
|
306
|
+
followUpQueue = [];
|
|
307
|
+
pendingApprovalResolve = null;
|
|
308
|
+
pendingApprovalToolName = null;
|
|
309
|
+
pendingQuestions = /* @__PURE__ */ new Map();
|
|
310
|
+
pendingPlanApprovals = /* @__PURE__ */ new Map();
|
|
311
|
+
workspace = void 0;
|
|
312
|
+
workspaceFn = void 0;
|
|
313
|
+
workspaceInitialized = false;
|
|
314
|
+
heartbeatTimers = /* @__PURE__ */ new Map();
|
|
315
|
+
tokenUsage = {
|
|
316
|
+
promptTokens: 0,
|
|
317
|
+
completionTokens: 0,
|
|
318
|
+
totalTokens: 0
|
|
319
|
+
};
|
|
320
|
+
sessionGrantedCategories = /* @__PURE__ */ new Set();
|
|
321
|
+
sessionGrantedTools = /* @__PURE__ */ new Set();
|
|
322
|
+
constructor(config) {
|
|
323
|
+
this.id = config.id;
|
|
324
|
+
this.config = config;
|
|
325
|
+
this.resourceId = config.resourceId ?? config.id;
|
|
326
|
+
this.state = {
|
|
327
|
+
...this.getSchemaDefaults(),
|
|
328
|
+
...config.initialState
|
|
329
|
+
};
|
|
330
|
+
const defaultMode = config.modes.find((m) => m.default) ?? config.modes[0];
|
|
331
|
+
if (!defaultMode) {
|
|
332
|
+
throw new Error("Harness requires at least one agent mode");
|
|
333
|
+
}
|
|
334
|
+
this.currentModeId = defaultMode.id;
|
|
335
|
+
if (config.workspace instanceof Workspace) {
|
|
336
|
+
this.workspace = config.workspace;
|
|
337
|
+
} else if (typeof config.workspace === "function") {
|
|
338
|
+
this.workspaceFn = config.workspace;
|
|
339
|
+
}
|
|
340
|
+
const currentModel = this.state.currentModelId;
|
|
341
|
+
if (!currentModel && defaultMode.defaultModelId) {
|
|
342
|
+
void this.setState({ currentModelId: defaultMode.defaultModelId });
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// ===========================================================================
|
|
346
|
+
// Initialization
|
|
347
|
+
// ===========================================================================
|
|
348
|
+
/**
|
|
349
|
+
* Initialize the harness — loads storage and workspace.
|
|
350
|
+
* Must be called before using the harness.
|
|
351
|
+
*/
|
|
352
|
+
async init() {
|
|
353
|
+
if (this.config.storage) {
|
|
354
|
+
await this.config.storage.init();
|
|
355
|
+
}
|
|
356
|
+
if (this.config.workspace && !this.workspaceInitialized && !this.workspaceFn) {
|
|
357
|
+
try {
|
|
358
|
+
if (!this.workspace) {
|
|
359
|
+
this.workspace = new Workspace(this.config.workspace);
|
|
360
|
+
}
|
|
361
|
+
this.emit({ type: "workspace_status_changed", status: "initializing" });
|
|
362
|
+
await this.workspace.init();
|
|
363
|
+
this.workspaceInitialized = true;
|
|
364
|
+
this.emit({ type: "workspace_status_changed", status: "ready" });
|
|
365
|
+
this.emit({
|
|
366
|
+
type: "workspace_ready",
|
|
367
|
+
workspaceId: this.workspace.id,
|
|
368
|
+
workspaceName: this.workspace.name
|
|
369
|
+
});
|
|
370
|
+
} catch (error) {
|
|
371
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
372
|
+
this.workspace = void 0;
|
|
373
|
+
this.workspaceInitialized = false;
|
|
374
|
+
this.emit({ type: "workspace_status_changed", status: "error", error: err });
|
|
375
|
+
this.emit({ type: "workspace_error", error: err });
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const workspaceForAgents = this.workspaceFn ?? this.workspace;
|
|
379
|
+
for (const mode of this.config.modes) {
|
|
380
|
+
const agent = typeof mode.agent === "function" ? null : mode.agent;
|
|
381
|
+
if (!agent) continue;
|
|
382
|
+
if (this.config.memory && !agent.hasOwnMemory()) {
|
|
383
|
+
agent.__setMemory(this.config.memory);
|
|
384
|
+
}
|
|
385
|
+
if (workspaceForAgents && !agent.hasOwnWorkspace()) {
|
|
386
|
+
agent.__setWorkspace(workspaceForAgents);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
this.startHeartbeats();
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Select the most recent thread, or create one if none exist.
|
|
393
|
+
*/
|
|
394
|
+
async selectOrCreateThread() {
|
|
395
|
+
const threads = await this.listThreads();
|
|
396
|
+
if (threads.length === 0) {
|
|
397
|
+
return await this.createThread();
|
|
398
|
+
}
|
|
399
|
+
const sortedThreads = [...threads].sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
400
|
+
const mostRecent = sortedThreads[0];
|
|
401
|
+
this.currentThreadId = mostRecent.id;
|
|
402
|
+
await this.loadThreadMetadata();
|
|
403
|
+
return mostRecent;
|
|
404
|
+
}
|
|
405
|
+
async getMemoryStorage() {
|
|
406
|
+
if (!this.config.storage) {
|
|
407
|
+
throw new Error("Storage is not configured on this Harness");
|
|
408
|
+
}
|
|
409
|
+
const memoryStorage = await this.config.storage.getStore("memory");
|
|
410
|
+
if (!memoryStorage) {
|
|
411
|
+
throw new Error("Storage does not have a memory domain configured");
|
|
412
|
+
}
|
|
413
|
+
return memoryStorage;
|
|
414
|
+
}
|
|
415
|
+
// ===========================================================================
|
|
416
|
+
// State Management
|
|
417
|
+
// ===========================================================================
|
|
418
|
+
/**
|
|
419
|
+
* Get current harness state (read-only snapshot).
|
|
420
|
+
*/
|
|
421
|
+
getState() {
|
|
422
|
+
return { ...this.state };
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Update harness state. Validates against schema if provided.
|
|
426
|
+
* Emits state_changed event.
|
|
427
|
+
*/
|
|
428
|
+
async setState(updates) {
|
|
429
|
+
const changedKeys = Object.keys(updates);
|
|
430
|
+
const newState = { ...this.state, ...updates };
|
|
431
|
+
if (this.config.stateSchema) {
|
|
432
|
+
const result = this.config.stateSchema.safeParse(newState);
|
|
433
|
+
if (!result.success) {
|
|
434
|
+
throw new Error(`Invalid state update: ${result.error.message}`);
|
|
435
|
+
}
|
|
436
|
+
this.state = result.data;
|
|
437
|
+
} else {
|
|
438
|
+
this.state = newState;
|
|
439
|
+
}
|
|
440
|
+
this.emit({ type: "state_changed", state: this.state, changedKeys });
|
|
441
|
+
}
|
|
442
|
+
getSchemaDefaults() {
|
|
443
|
+
if (!this.config.stateSchema) return {};
|
|
444
|
+
const shape = this.config.stateSchema.shape;
|
|
445
|
+
const defaults = {};
|
|
446
|
+
for (const [key, field] of Object.entries(shape)) {
|
|
447
|
+
try {
|
|
448
|
+
const result = field.safeParse(void 0);
|
|
449
|
+
if (result.success && result.data !== void 0) {
|
|
450
|
+
defaults[key] = result.data;
|
|
451
|
+
}
|
|
452
|
+
} catch {
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return defaults;
|
|
456
|
+
}
|
|
457
|
+
// ===========================================================================
|
|
458
|
+
// Mode Management
|
|
459
|
+
// ===========================================================================
|
|
460
|
+
getModes() {
|
|
461
|
+
return this.config.modes;
|
|
462
|
+
}
|
|
463
|
+
getCurrentModeId() {
|
|
464
|
+
return this.currentModeId;
|
|
465
|
+
}
|
|
466
|
+
getCurrentMode() {
|
|
467
|
+
const mode = this.config.modes.find((m) => m.id === this.currentModeId);
|
|
468
|
+
if (!mode) {
|
|
469
|
+
throw new Error(`Mode not found: ${this.currentModeId}`);
|
|
470
|
+
}
|
|
471
|
+
return mode;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Switch to a different mode.
|
|
475
|
+
* Aborts any in-progress generation and switches to the mode's default model.
|
|
476
|
+
*/
|
|
477
|
+
async switchMode(modeId) {
|
|
478
|
+
const mode = this.config.modes.find((m) => m.id === modeId);
|
|
479
|
+
if (!mode) {
|
|
480
|
+
throw new Error(`Mode not found: ${modeId}`);
|
|
481
|
+
}
|
|
482
|
+
this.abort();
|
|
483
|
+
const currentModelId = this.getCurrentModelId();
|
|
484
|
+
if (currentModelId) {
|
|
485
|
+
await this.persistThreadSetting(`modeModelId_${this.currentModeId}`, currentModelId);
|
|
486
|
+
}
|
|
487
|
+
const previousModeId = this.currentModeId;
|
|
488
|
+
this.currentModeId = modeId;
|
|
489
|
+
await this.persistThreadSetting("currentModeId", modeId);
|
|
490
|
+
const modeModelId = await this.loadModeModelId(modeId);
|
|
491
|
+
if (modeModelId) {
|
|
492
|
+
void this.setState({ currentModelId: modeModelId });
|
|
493
|
+
this.emit({ type: "model_changed", modelId: modeModelId });
|
|
494
|
+
}
|
|
495
|
+
this.emit({ type: "mode_changed", modeId, previousModeId });
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Load the stored model ID for a specific mode.
|
|
499
|
+
* Falls back to: thread metadata -> mode's defaultModelId -> current model.
|
|
500
|
+
*/
|
|
501
|
+
async loadModeModelId(modeId) {
|
|
502
|
+
if (this.currentThreadId && this.config.storage) {
|
|
503
|
+
try {
|
|
504
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
505
|
+
const thread = await memoryStorage.getThreadById({ threadId: this.currentThreadId });
|
|
506
|
+
const meta = thread?.metadata;
|
|
507
|
+
const stored = meta?.[`modeModelId_${modeId}`];
|
|
508
|
+
if (stored) return stored;
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
const mode = this.config.modes.find((m) => m.id === modeId);
|
|
513
|
+
if (mode?.defaultModelId) return mode.defaultModelId;
|
|
514
|
+
return null;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Get the agent for the current mode.
|
|
518
|
+
*/
|
|
519
|
+
getCurrentAgent() {
|
|
520
|
+
const mode = this.getCurrentMode();
|
|
521
|
+
if (typeof mode.agent === "function") {
|
|
522
|
+
return mode.agent(this.state);
|
|
523
|
+
}
|
|
524
|
+
return mode.agent;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Get a short display name from the current model ID.
|
|
528
|
+
*/
|
|
529
|
+
getModelName() {
|
|
530
|
+
const modelId = this.getCurrentModelId();
|
|
531
|
+
if (!modelId || modelId === "unknown") return modelId || "unknown";
|
|
532
|
+
const parts = modelId.split("/");
|
|
533
|
+
return parts[parts.length - 1] || modelId;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Get the full model ID (e.g., "anthropic/claude-sonnet-4").
|
|
537
|
+
*/
|
|
538
|
+
getFullModelId() {
|
|
539
|
+
return this.getCurrentModelId();
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Switch to a different model at runtime.
|
|
543
|
+
*/
|
|
544
|
+
async switchModel(modelId, scope = "thread", modeId) {
|
|
545
|
+
const targetModeId = modeId ?? this.currentModeId;
|
|
546
|
+
if (targetModeId === this.currentModeId) {
|
|
547
|
+
void this.setState({ currentModelId: modelId });
|
|
548
|
+
}
|
|
549
|
+
if (scope === "thread") {
|
|
550
|
+
await this.persistThreadSetting(`modeModelId_${targetModeId}`, modelId);
|
|
551
|
+
}
|
|
552
|
+
this.emit({ type: "model_changed", modelId, scope, modeId: targetModeId });
|
|
553
|
+
}
|
|
554
|
+
getCurrentModelId() {
|
|
555
|
+
const state = this.getState();
|
|
556
|
+
return state.currentModelId ?? "";
|
|
557
|
+
}
|
|
558
|
+
hasModelSelected() {
|
|
559
|
+
return this.getCurrentModelId() !== "";
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Check if the current model's provider has authentication configured.
|
|
563
|
+
* Uses the provider registry's `apiKeyEnvVar` and the optional `modelAuthChecker` hook.
|
|
564
|
+
*/
|
|
565
|
+
async getCurrentModelAuthStatus() {
|
|
566
|
+
const modelId = this.getCurrentModelId();
|
|
567
|
+
const provider = modelId.split("/")[0];
|
|
568
|
+
if (!provider) return { hasAuth: true };
|
|
569
|
+
if (this.config.modelAuthChecker) {
|
|
570
|
+
const result = this.config.modelAuthChecker(provider);
|
|
571
|
+
if (result === true) return { hasAuth: true };
|
|
572
|
+
if (result === false) {
|
|
573
|
+
const apiKeyEnvVar = await this.getProviderApiKeyEnvVar(provider);
|
|
574
|
+
return { hasAuth: false, apiKeyEnvVar };
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
try {
|
|
578
|
+
const { PROVIDER_REGISTRY } = await import('../provider-registry-VEJ3PN4S.js');
|
|
579
|
+
const registry = PROVIDER_REGISTRY;
|
|
580
|
+
const providerConfig = registry[provider];
|
|
581
|
+
const envVars = providerConfig?.apiKeyEnvVar;
|
|
582
|
+
const apiKeyEnvVar = Array.isArray(envVars) ? envVars[0] : envVars;
|
|
583
|
+
if (apiKeyEnvVar && process.env[apiKeyEnvVar]) {
|
|
584
|
+
return { hasAuth: true };
|
|
585
|
+
}
|
|
586
|
+
return { hasAuth: false, apiKeyEnvVar: apiKeyEnvVar || void 0 };
|
|
587
|
+
} catch {
|
|
588
|
+
return { hasAuth: true };
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Get all available models from the provider registry with auth status.
|
|
593
|
+
* Uses the optional `modelAuthChecker` and `modelUseCountProvider` hooks.
|
|
594
|
+
*/
|
|
595
|
+
async getAvailableModels() {
|
|
596
|
+
try {
|
|
597
|
+
const { PROVIDER_REGISTRY } = await import('../provider-registry-VEJ3PN4S.js');
|
|
598
|
+
if (!PROVIDER_REGISTRY) return [];
|
|
599
|
+
const registry = PROVIDER_REGISTRY;
|
|
600
|
+
const providers = Object.keys(registry);
|
|
601
|
+
const useCounts = this.config.modelUseCountProvider?.() ?? {};
|
|
602
|
+
const models = [];
|
|
603
|
+
for (const provider of providers) {
|
|
604
|
+
const providerConfig = registry[provider];
|
|
605
|
+
const envVars = providerConfig?.apiKeyEnvVar;
|
|
606
|
+
const apiKeyEnvVar = Array.isArray(envVars) ? envVars[0] : envVars;
|
|
607
|
+
const hasEnvKey = apiKeyEnvVar ? !!process.env[apiKeyEnvVar] : false;
|
|
608
|
+
let hasApiKey = hasEnvKey;
|
|
609
|
+
if (!hasApiKey && this.config.modelAuthChecker) {
|
|
610
|
+
const customAuth = this.config.modelAuthChecker(provider);
|
|
611
|
+
if (customAuth === true) hasApiKey = true;
|
|
612
|
+
}
|
|
613
|
+
if (providerConfig?.models && Array.isArray(providerConfig.models)) {
|
|
614
|
+
for (const modelName of providerConfig.models) {
|
|
615
|
+
const id = `${provider}/${modelName}`;
|
|
616
|
+
models.push({
|
|
617
|
+
id,
|
|
618
|
+
provider,
|
|
619
|
+
modelName,
|
|
620
|
+
hasApiKey,
|
|
621
|
+
apiKeyEnvVar: apiKeyEnvVar || void 0,
|
|
622
|
+
useCount: useCounts[id] ?? 0
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
return models;
|
|
628
|
+
} catch (error) {
|
|
629
|
+
console.warn("Failed to load available models:", error);
|
|
630
|
+
return [];
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
async getProviderApiKeyEnvVar(provider) {
|
|
634
|
+
try {
|
|
635
|
+
const { PROVIDER_REGISTRY } = await import('../provider-registry-VEJ3PN4S.js');
|
|
636
|
+
const registry = PROVIDER_REGISTRY;
|
|
637
|
+
const envVars = registry[provider]?.apiKeyEnvVar;
|
|
638
|
+
return Array.isArray(envVars) ? envVars[0] : envVars;
|
|
639
|
+
} catch {
|
|
640
|
+
return void 0;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// ===========================================================================
|
|
644
|
+
// Thread Management
|
|
645
|
+
// ===========================================================================
|
|
646
|
+
getCurrentThreadId() {
|
|
647
|
+
return this.currentThreadId;
|
|
648
|
+
}
|
|
649
|
+
getResourceId() {
|
|
650
|
+
return this.resourceId;
|
|
651
|
+
}
|
|
652
|
+
setResourceId(resourceId) {
|
|
653
|
+
this.resourceId = resourceId;
|
|
654
|
+
this.currentThreadId = null;
|
|
655
|
+
}
|
|
656
|
+
async createThread(title) {
|
|
657
|
+
const now = /* @__PURE__ */ new Date();
|
|
658
|
+
const thread = {
|
|
659
|
+
id: this.generateId(),
|
|
660
|
+
resourceId: this.resourceId,
|
|
661
|
+
title: title || "New Thread",
|
|
662
|
+
createdAt: now,
|
|
663
|
+
updatedAt: now
|
|
664
|
+
};
|
|
665
|
+
const currentStateModel = this.state.currentModelId;
|
|
666
|
+
const currentMode = this.getCurrentMode();
|
|
667
|
+
const modelId = currentStateModel || currentMode.defaultModelId;
|
|
668
|
+
const metadata = {};
|
|
669
|
+
if (modelId) {
|
|
670
|
+
metadata.currentModelId = modelId;
|
|
671
|
+
metadata[`modeModelId_${this.currentModeId}`] = modelId;
|
|
672
|
+
}
|
|
673
|
+
if (this.config.storage) {
|
|
674
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
675
|
+
await memoryStorage.saveThread({
|
|
676
|
+
thread: {
|
|
677
|
+
id: thread.id,
|
|
678
|
+
resourceId: thread.resourceId,
|
|
679
|
+
title: thread.title,
|
|
680
|
+
createdAt: thread.createdAt,
|
|
681
|
+
updatedAt: thread.updatedAt,
|
|
682
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
this.currentThreadId = thread.id;
|
|
687
|
+
if (modelId && !currentStateModel) {
|
|
688
|
+
void this.setState({ currentModelId: modelId });
|
|
689
|
+
}
|
|
690
|
+
this.tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
691
|
+
this.emit({ type: "thread_created", thread });
|
|
692
|
+
return thread;
|
|
693
|
+
}
|
|
694
|
+
async renameThread(title) {
|
|
695
|
+
if (!this.currentThreadId || !this.config.storage) return;
|
|
696
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
697
|
+
const thread = await memoryStorage.getThreadById({ threadId: this.currentThreadId });
|
|
698
|
+
if (thread) {
|
|
699
|
+
await memoryStorage.saveThread({
|
|
700
|
+
thread: { ...thread, title, updatedAt: /* @__PURE__ */ new Date() }
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async switchThread(threadId) {
|
|
705
|
+
this.abort();
|
|
706
|
+
if (this.config.storage) {
|
|
707
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
708
|
+
const thread = await memoryStorage.getThreadById({ threadId });
|
|
709
|
+
if (!thread) {
|
|
710
|
+
throw new Error(`Thread not found: ${threadId}`);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const previousThreadId = this.currentThreadId;
|
|
714
|
+
this.currentThreadId = threadId;
|
|
715
|
+
await this.loadThreadMetadata();
|
|
716
|
+
this.emit({ type: "thread_changed", threadId, previousThreadId });
|
|
717
|
+
}
|
|
718
|
+
async listThreads(options) {
|
|
719
|
+
if (!this.config.storage) return [];
|
|
720
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
721
|
+
const filter = options?.allResources ? void 0 : { resourceId: this.resourceId };
|
|
722
|
+
const result = await memoryStorage.listThreads({ filter });
|
|
723
|
+
return result.threads.map((thread) => ({
|
|
724
|
+
id: thread.id,
|
|
725
|
+
resourceId: thread.resourceId,
|
|
726
|
+
title: thread.title,
|
|
727
|
+
createdAt: thread.createdAt,
|
|
728
|
+
updatedAt: thread.updatedAt,
|
|
729
|
+
metadata: thread.metadata
|
|
730
|
+
}));
|
|
731
|
+
}
|
|
732
|
+
async persistThreadSetting(key, value) {
|
|
733
|
+
if (!this.currentThreadId || !this.config.storage) return;
|
|
734
|
+
try {
|
|
735
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
736
|
+
const thread = await memoryStorage.getThreadById({ threadId: this.currentThreadId });
|
|
737
|
+
if (thread) {
|
|
738
|
+
await memoryStorage.saveThread({
|
|
739
|
+
thread: {
|
|
740
|
+
...thread,
|
|
741
|
+
metadata: { ...thread.metadata, [key]: value },
|
|
742
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
} catch {
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
async removeThreadSetting(key) {
|
|
750
|
+
if (!this.currentThreadId || !this.config.storage) return;
|
|
751
|
+
try {
|
|
752
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
753
|
+
const thread = await memoryStorage.getThreadById({ threadId: this.currentThreadId });
|
|
754
|
+
if (thread && thread.metadata) {
|
|
755
|
+
const metadata = { ...thread.metadata };
|
|
756
|
+
delete metadata[key];
|
|
757
|
+
await memoryStorage.saveThread({
|
|
758
|
+
thread: {
|
|
759
|
+
...thread,
|
|
760
|
+
metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
|
|
761
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
} catch {
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
async loadThreadMetadata() {
|
|
769
|
+
if (!this.currentThreadId || !this.config.storage) {
|
|
770
|
+
this.tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
775
|
+
const thread = await memoryStorage.getThreadById({ threadId: this.currentThreadId });
|
|
776
|
+
const savedUsage = thread?.metadata?.tokenUsage;
|
|
777
|
+
if (savedUsage) {
|
|
778
|
+
this.tokenUsage = {
|
|
779
|
+
promptTokens: savedUsage.promptTokens ?? 0,
|
|
780
|
+
completionTokens: savedUsage.completionTokens ?? 0,
|
|
781
|
+
totalTokens: savedUsage.totalTokens ?? 0
|
|
782
|
+
};
|
|
783
|
+
} else {
|
|
784
|
+
this.tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
785
|
+
}
|
|
786
|
+
const meta = thread?.metadata;
|
|
787
|
+
const updates = {};
|
|
788
|
+
const modeModelKey = `modeModelId_${this.currentModeId}`;
|
|
789
|
+
if (meta?.[modeModelKey]) {
|
|
790
|
+
updates.currentModelId = meta[modeModelKey];
|
|
791
|
+
} else if (meta?.currentModelId) {
|
|
792
|
+
updates.currentModelId = meta.currentModelId;
|
|
793
|
+
}
|
|
794
|
+
if (meta?.currentModeId) {
|
|
795
|
+
const savedModeId = meta.currentModeId;
|
|
796
|
+
const modeExists = this.config.modes.some((m) => m.id === savedModeId);
|
|
797
|
+
if (modeExists && savedModeId !== this.currentModeId) {
|
|
798
|
+
this.currentModeId = savedModeId;
|
|
799
|
+
const restoredModeModelKey = `modeModelId_${savedModeId}`;
|
|
800
|
+
if (meta[restoredModeModelKey]) {
|
|
801
|
+
updates.currentModelId = meta[restoredModeModelKey];
|
|
802
|
+
}
|
|
803
|
+
this.emit({
|
|
804
|
+
type: "mode_changed",
|
|
805
|
+
modeId: savedModeId,
|
|
806
|
+
previousModeId: this.config.modes.find((m) => m.default)?.id || this.config.modes[0].id
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (Object.keys(updates).length > 0) {
|
|
811
|
+
void this.setState(updates);
|
|
812
|
+
}
|
|
813
|
+
} catch {
|
|
814
|
+
this.tokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// ===========================================================================
|
|
818
|
+
// Observational Memory
|
|
819
|
+
// ===========================================================================
|
|
820
|
+
/**
|
|
821
|
+
* Load observational memory progress for the current thread.
|
|
822
|
+
* Reads the OM record and recent messages to reconstruct status,
|
|
823
|
+
* then emits an `om_status` event for the UI.
|
|
824
|
+
*/
|
|
825
|
+
async loadOMProgress() {
|
|
826
|
+
if (!this.currentThreadId) return;
|
|
827
|
+
try {
|
|
828
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
829
|
+
const record = await memoryStorage.getObservationalMemory(this.currentThreadId, this.resourceId);
|
|
830
|
+
if (!record) return;
|
|
831
|
+
const config = record.config;
|
|
832
|
+
const getThreshold = (val, fallback) => {
|
|
833
|
+
if (!val) return fallback;
|
|
834
|
+
if (typeof val === "number") return val;
|
|
835
|
+
return val.max;
|
|
836
|
+
};
|
|
837
|
+
let observationThreshold = getThreshold(config?.observationThreshold, 3e4);
|
|
838
|
+
let reflectionThreshold = getThreshold(config?.reflectionThreshold, 4e4);
|
|
839
|
+
let messageTokens = record.pendingMessageTokens ?? 0;
|
|
840
|
+
let observationTokens = record.observationTokenCount ?? 0;
|
|
841
|
+
let bufferedObs = {
|
|
842
|
+
status: "idle",
|
|
843
|
+
chunks: 0,
|
|
844
|
+
messageTokens: 0,
|
|
845
|
+
projectedMessageRemoval: 0,
|
|
846
|
+
observationTokens: 0
|
|
847
|
+
};
|
|
848
|
+
let bufferedRef = {
|
|
849
|
+
status: "idle",
|
|
850
|
+
inputObservationTokens: 0,
|
|
851
|
+
observationTokens: 0
|
|
852
|
+
};
|
|
853
|
+
let generationCount = 0;
|
|
854
|
+
let stepNumber = 0;
|
|
855
|
+
const messagesResult = await memoryStorage.listMessages({
|
|
856
|
+
threadId: this.currentThreadId,
|
|
857
|
+
perPage: 70,
|
|
858
|
+
page: 0,
|
|
859
|
+
orderBy: { field: "createdAt", direction: "DESC" }
|
|
860
|
+
});
|
|
861
|
+
const messages = messagesResult.messages;
|
|
862
|
+
let foundStatus = false;
|
|
863
|
+
for (const msg of messages) {
|
|
864
|
+
if (msg.role !== "assistant") continue;
|
|
865
|
+
const content = msg.content;
|
|
866
|
+
if (typeof content === "string" || !content?.parts) continue;
|
|
867
|
+
for (let i = content.parts.length - 1; i >= 0; i--) {
|
|
868
|
+
const part = content.parts[i];
|
|
869
|
+
if (part.type === "data-om-status" && part.data?.windows) {
|
|
870
|
+
const w = part.data.windows;
|
|
871
|
+
messageTokens = w.active?.messages?.tokens ?? messageTokens;
|
|
872
|
+
observationTokens = w.active?.observations?.tokens ?? observationTokens;
|
|
873
|
+
const msgThresh = w.active?.messages?.threshold;
|
|
874
|
+
const obsThresh = w.active?.observations?.threshold;
|
|
875
|
+
if (msgThresh) observationThreshold = msgThresh;
|
|
876
|
+
if (obsThresh) reflectionThreshold = obsThresh;
|
|
877
|
+
const bo = w.buffered?.observations;
|
|
878
|
+
if (bo) {
|
|
879
|
+
bufferedObs = {
|
|
880
|
+
status: bo.status ?? "idle",
|
|
881
|
+
chunks: bo.chunks ?? 0,
|
|
882
|
+
messageTokens: bo.messageTokens ?? 0,
|
|
883
|
+
projectedMessageRemoval: bo.projectedMessageRemoval ?? 0,
|
|
884
|
+
observationTokens: bo.observationTokens ?? 0
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
const br = w.buffered?.reflection;
|
|
888
|
+
if (br) {
|
|
889
|
+
bufferedRef = {
|
|
890
|
+
status: br.status ?? "idle",
|
|
891
|
+
inputObservationTokens: br.inputObservationTokens ?? 0,
|
|
892
|
+
observationTokens: br.observationTokens ?? 0
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
generationCount = part.data.generationCount ?? 0;
|
|
896
|
+
stepNumber = part.data.stepNumber ?? 0;
|
|
897
|
+
foundStatus = true;
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
if (foundStatus) break;
|
|
902
|
+
}
|
|
903
|
+
this.emit({
|
|
904
|
+
type: "om_status",
|
|
905
|
+
windows: {
|
|
906
|
+
active: {
|
|
907
|
+
messages: { tokens: messageTokens, threshold: observationThreshold },
|
|
908
|
+
observations: { tokens: observationTokens, threshold: reflectionThreshold }
|
|
909
|
+
},
|
|
910
|
+
buffered: { observations: bufferedObs, reflection: bufferedRef }
|
|
911
|
+
},
|
|
912
|
+
recordId: record.id ?? "",
|
|
913
|
+
threadId: this.currentThreadId,
|
|
914
|
+
stepNumber,
|
|
915
|
+
generationCount
|
|
916
|
+
});
|
|
917
|
+
} catch {
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Returns the observer model ID from state, falling back to omConfig defaults.
|
|
922
|
+
*/
|
|
923
|
+
getObserverModelId() {
|
|
924
|
+
return this.state.observerModelId ?? this.config.omConfig?.defaultObserverModelId;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Returns the reflector model ID from state, falling back to omConfig defaults.
|
|
928
|
+
*/
|
|
929
|
+
getReflectorModelId() {
|
|
930
|
+
return this.state.reflectorModelId ?? this.config.omConfig?.defaultReflectorModelId;
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Returns the observation threshold from state, falling back to omConfig defaults.
|
|
934
|
+
*/
|
|
935
|
+
getObservationThreshold() {
|
|
936
|
+
return this.state.observationThreshold ?? this.config.omConfig?.defaultObservationThreshold;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Returns the reflection threshold from state, falling back to omConfig defaults.
|
|
940
|
+
*/
|
|
941
|
+
getReflectionThreshold() {
|
|
942
|
+
return this.state.reflectionThreshold ?? this.config.omConfig?.defaultReflectionThreshold;
|
|
943
|
+
}
|
|
944
|
+
/**
|
|
945
|
+
* Resolves the observer model ID to a language model instance via `resolveModel`.
|
|
946
|
+
*/
|
|
947
|
+
getResolvedObserverModel() {
|
|
948
|
+
const modelId = this.getObserverModelId();
|
|
949
|
+
if (!modelId || !this.config.resolveModel) return void 0;
|
|
950
|
+
return this.config.resolveModel(modelId);
|
|
951
|
+
}
|
|
952
|
+
/**
|
|
953
|
+
* Resolves the reflector model ID to a language model instance via `resolveModel`.
|
|
954
|
+
*/
|
|
955
|
+
getResolvedReflectorModel() {
|
|
956
|
+
const modelId = this.getReflectorModelId();
|
|
957
|
+
if (!modelId || !this.config.resolveModel) return void 0;
|
|
958
|
+
return this.config.resolveModel(modelId);
|
|
959
|
+
}
|
|
960
|
+
// ===========================================================================
|
|
961
|
+
// Subagent Model Management
|
|
962
|
+
// ===========================================================================
|
|
963
|
+
getSubagentModelId(agentType) {
|
|
964
|
+
const state = this.state;
|
|
965
|
+
if (agentType) {
|
|
966
|
+
const perType = state[`subagentModelId_${agentType}`];
|
|
967
|
+
if (typeof perType === "string") return perType;
|
|
968
|
+
}
|
|
969
|
+
const global = state.subagentModelId;
|
|
970
|
+
return typeof global === "string" ? global : null;
|
|
971
|
+
}
|
|
972
|
+
async setSubagentModelId(modelId, agentType) {
|
|
973
|
+
const key = agentType ? `subagentModelId_${agentType}` : "subagentModelId";
|
|
974
|
+
void this.setState({ [key]: modelId });
|
|
975
|
+
await this.persistThreadSetting(key, modelId);
|
|
976
|
+
this.emit({ type: "subagent_model_changed", modelId, scope: "thread", agentType });
|
|
977
|
+
}
|
|
978
|
+
// ===========================================================================
|
|
979
|
+
// Permissions
|
|
980
|
+
// ===========================================================================
|
|
981
|
+
grantSessionCategory(category) {
|
|
982
|
+
this.sessionGrantedCategories.add(category);
|
|
983
|
+
}
|
|
984
|
+
grantSessionTool(toolName) {
|
|
985
|
+
this.sessionGrantedTools.add(toolName);
|
|
986
|
+
}
|
|
987
|
+
getSessionGrants() {
|
|
988
|
+
return {
|
|
989
|
+
categories: [...this.sessionGrantedCategories],
|
|
990
|
+
tools: [...this.sessionGrantedTools]
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
getToolCategory(toolName) {
|
|
994
|
+
return this.config.toolCategoryResolver?.(toolName) ?? null;
|
|
995
|
+
}
|
|
996
|
+
setPermissionCategory(category, policy) {
|
|
997
|
+
const rules = this.getPermissionRules();
|
|
998
|
+
rules.categories[category] = policy;
|
|
999
|
+
void this.setState({ permissionRules: rules });
|
|
1000
|
+
}
|
|
1001
|
+
setPermissionTool(toolName, policy) {
|
|
1002
|
+
const rules = this.getPermissionRules();
|
|
1003
|
+
rules.tools[toolName] = policy;
|
|
1004
|
+
void this.setState({ permissionRules: rules });
|
|
1005
|
+
}
|
|
1006
|
+
getPermissionRules() {
|
|
1007
|
+
const state = this.state;
|
|
1008
|
+
const rules = state.permissionRules;
|
|
1009
|
+
return rules ?? { categories: {}, tools: {} };
|
|
1010
|
+
}
|
|
1011
|
+
/**
|
|
1012
|
+
* Resolve whether a tool call should be auto-approved, denied, or asked.
|
|
1013
|
+
* Resolution chain: yolo → per-tool policy → session tool grant →
|
|
1014
|
+
* session category grant → category policy → "ask"
|
|
1015
|
+
*/
|
|
1016
|
+
resolveToolApproval(toolName) {
|
|
1017
|
+
const state = this.state;
|
|
1018
|
+
if (state.yolo === true) return "allow";
|
|
1019
|
+
const rules = this.getPermissionRules();
|
|
1020
|
+
const toolPolicy = rules.tools[toolName];
|
|
1021
|
+
if (toolPolicy) return toolPolicy;
|
|
1022
|
+
if (this.sessionGrantedTools.has(toolName)) return "allow";
|
|
1023
|
+
const category = this.getToolCategory(toolName);
|
|
1024
|
+
if (category) {
|
|
1025
|
+
if (this.sessionGrantedCategories.has(category)) return "allow";
|
|
1026
|
+
const categoryPolicy = rules.categories[category];
|
|
1027
|
+
if (categoryPolicy) return categoryPolicy;
|
|
1028
|
+
}
|
|
1029
|
+
return "ask";
|
|
1030
|
+
}
|
|
1031
|
+
// ===========================================================================
|
|
1032
|
+
// Message Handling
|
|
1033
|
+
// ===========================================================================
|
|
1034
|
+
/**
|
|
1035
|
+
* Send a message to the current agent.
|
|
1036
|
+
* Streams the response and emits events.
|
|
1037
|
+
*/
|
|
1038
|
+
async sendMessage(content, options) {
|
|
1039
|
+
if (!this.currentThreadId) {
|
|
1040
|
+
const thread = await this.createThread();
|
|
1041
|
+
this.currentThreadId = thread.id;
|
|
1042
|
+
}
|
|
1043
|
+
const operationId = ++this.currentOperationId;
|
|
1044
|
+
this.abortController = new AbortController();
|
|
1045
|
+
const agent = this.getCurrentAgent();
|
|
1046
|
+
this.emit({ type: "agent_start" });
|
|
1047
|
+
try {
|
|
1048
|
+
const requestContext = await this.buildRequestContext();
|
|
1049
|
+
const isYolo = this.state.yolo === true;
|
|
1050
|
+
const streamOptions = {
|
|
1051
|
+
memory: { thread: this.currentThreadId, resource: this.resourceId },
|
|
1052
|
+
abortSignal: this.abortController.signal,
|
|
1053
|
+
requestContext,
|
|
1054
|
+
maxSteps: 1e3,
|
|
1055
|
+
requireToolApproval: !isYolo,
|
|
1056
|
+
modelSettings: { temperature: 1 }
|
|
1057
|
+
};
|
|
1058
|
+
streamOptions.toolsets = await this.buildToolsets(requestContext);
|
|
1059
|
+
let messageInput = content;
|
|
1060
|
+
if (options?.images?.length) {
|
|
1061
|
+
messageInput = {
|
|
1062
|
+
role: "user",
|
|
1063
|
+
content: [
|
|
1064
|
+
{ type: "text", text: content },
|
|
1065
|
+
...options.images.map((img) => ({ type: "file", data: img.data, mediaType: img.mimeType }))
|
|
1066
|
+
]
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
const response = await agent.stream(messageInput, streamOptions);
|
|
1070
|
+
await this.processStream(response);
|
|
1071
|
+
if (this.currentOperationId === operationId) {
|
|
1072
|
+
const reason = this.abortRequested ? "aborted" : "complete";
|
|
1073
|
+
this.emit({ type: "agent_end", reason });
|
|
1074
|
+
}
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
if (this.currentOperationId !== operationId) return;
|
|
1077
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
1078
|
+
this.emit({ type: "agent_end", reason: "aborted" });
|
|
1079
|
+
} else if (error instanceof Error && error.message.match(/^Tool .+ not found$/)) {
|
|
1080
|
+
const badTool = error.message.replace("Tool ", "").replace(" not found", "");
|
|
1081
|
+
this.emit({
|
|
1082
|
+
type: "error",
|
|
1083
|
+
error: new Error(`Unknown tool "${badTool}".`),
|
|
1084
|
+
retryable: true
|
|
1085
|
+
});
|
|
1086
|
+
this.followUpQueue.push(
|
|
1087
|
+
`[System] Your previous tool call used "${badTool}" which is not a valid tool. Please retry with the correct tool name.`
|
|
1088
|
+
);
|
|
1089
|
+
this.emit({ type: "agent_end", reason: "error" });
|
|
1090
|
+
} else {
|
|
1091
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1092
|
+
this.emit({ type: "error", error: err });
|
|
1093
|
+
this.emit({ type: "agent_end", reason: "error" });
|
|
1094
|
+
}
|
|
1095
|
+
} finally {
|
|
1096
|
+
if (this.currentOperationId === operationId) {
|
|
1097
|
+
this.abortController = null;
|
|
1098
|
+
this.abortRequested = false;
|
|
1099
|
+
}
|
|
1100
|
+
if (this.currentOperationId === operationId && this.followUpQueue.length > 0) {
|
|
1101
|
+
const next = this.followUpQueue.shift();
|
|
1102
|
+
await this.sendMessage(next);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
async getMessages(options) {
|
|
1107
|
+
if (!this.currentThreadId) return [];
|
|
1108
|
+
return this.getMessagesForThread(this.currentThreadId, options);
|
|
1109
|
+
}
|
|
1110
|
+
async getMessagesForThread(threadId, options) {
|
|
1111
|
+
if (!this.config.storage) return [];
|
|
1112
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
1113
|
+
const limit = options?.limit;
|
|
1114
|
+
if (limit) {
|
|
1115
|
+
const result2 = await memoryStorage.listMessages({
|
|
1116
|
+
threadId,
|
|
1117
|
+
perPage: limit,
|
|
1118
|
+
page: 0,
|
|
1119
|
+
orderBy: { field: "createdAt", direction: "DESC" }
|
|
1120
|
+
});
|
|
1121
|
+
return result2.messages.map((msg) => this.convertToHarnessMessage(msg)).reverse();
|
|
1122
|
+
}
|
|
1123
|
+
const result = await memoryStorage.listMessages({ threadId, perPage: false });
|
|
1124
|
+
return result.messages.map((msg) => this.convertToHarnessMessage(msg));
|
|
1125
|
+
}
|
|
1126
|
+
async getFirstUserMessageForThread(threadId) {
|
|
1127
|
+
if (!this.config.storage) return null;
|
|
1128
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
1129
|
+
const result = await memoryStorage.listMessages({
|
|
1130
|
+
threadId,
|
|
1131
|
+
perPage: 5,
|
|
1132
|
+
page: 0,
|
|
1133
|
+
orderBy: { field: "createdAt", direction: "ASC" }
|
|
1134
|
+
});
|
|
1135
|
+
const userMsg = result.messages.find((m) => m.role === "user");
|
|
1136
|
+
return userMsg ? this.convertToHarnessMessage(userMsg) : null;
|
|
1137
|
+
}
|
|
1138
|
+
convertToHarnessMessage(msg) {
|
|
1139
|
+
const content = [];
|
|
1140
|
+
for (const part of msg.content.parts) {
|
|
1141
|
+
switch (part.type) {
|
|
1142
|
+
case "text":
|
|
1143
|
+
if (part.text) {
|
|
1144
|
+
content.push({ type: "text", text: part.text });
|
|
1145
|
+
}
|
|
1146
|
+
break;
|
|
1147
|
+
case "reasoning":
|
|
1148
|
+
if (part.reasoning) {
|
|
1149
|
+
content.push({ type: "thinking", thinking: part.reasoning });
|
|
1150
|
+
}
|
|
1151
|
+
break;
|
|
1152
|
+
case "tool-invocation":
|
|
1153
|
+
if (part.toolInvocation) {
|
|
1154
|
+
const inv = part.toolInvocation;
|
|
1155
|
+
content.push({ type: "tool_call", id: inv.toolCallId, name: inv.toolName, args: inv.args });
|
|
1156
|
+
if (inv.state === "result" && inv.result !== void 0) {
|
|
1157
|
+
content.push({
|
|
1158
|
+
type: "tool_result",
|
|
1159
|
+
id: inv.toolCallId,
|
|
1160
|
+
name: inv.toolName,
|
|
1161
|
+
result: inv.result,
|
|
1162
|
+
isError: inv.isError ?? false
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
} else if (part.toolCallId && part.toolName) {
|
|
1166
|
+
content.push({ type: "tool_call", id: part.toolCallId, name: part.toolName, args: part.args });
|
|
1167
|
+
}
|
|
1168
|
+
break;
|
|
1169
|
+
case "tool-call":
|
|
1170
|
+
if (part.toolCallId && part.toolName) {
|
|
1171
|
+
content.push({ type: "tool_call", id: part.toolCallId, name: part.toolName, args: part.args });
|
|
1172
|
+
}
|
|
1173
|
+
break;
|
|
1174
|
+
case "tool-result":
|
|
1175
|
+
if (part.toolCallId && part.toolName) {
|
|
1176
|
+
content.push({
|
|
1177
|
+
type: "tool_result",
|
|
1178
|
+
id: part.toolCallId,
|
|
1179
|
+
name: part.toolName,
|
|
1180
|
+
result: part.result,
|
|
1181
|
+
isError: part.isError ?? false
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
break;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return { id: msg.id, role: msg.role, content, createdAt: msg.createdAt };
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Process a stream response (shared between sendMessage and tool approval).
|
|
1191
|
+
*/
|
|
1192
|
+
async processStream(response) {
|
|
1193
|
+
let currentMessage = {
|
|
1194
|
+
id: this.generateId(),
|
|
1195
|
+
role: "assistant",
|
|
1196
|
+
content: [],
|
|
1197
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
1198
|
+
};
|
|
1199
|
+
const textContentById = /* @__PURE__ */ new Map();
|
|
1200
|
+
const thinkingContentById = /* @__PURE__ */ new Map();
|
|
1201
|
+
for await (const chunk of response.fullStream) {
|
|
1202
|
+
if ("runId" in chunk && chunk.runId) {
|
|
1203
|
+
this.currentRunId = chunk.runId;
|
|
1204
|
+
}
|
|
1205
|
+
switch (chunk.type) {
|
|
1206
|
+
case "text-start": {
|
|
1207
|
+
const textIndex = currentMessage.content.length;
|
|
1208
|
+
currentMessage.content.push({ type: "text", text: "" });
|
|
1209
|
+
textContentById.set(chunk.payload.id, { index: textIndex, text: "" });
|
|
1210
|
+
this.emit({ type: "message_start", message: { ...currentMessage } });
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
case "text-delta": {
|
|
1214
|
+
const textState = textContentById.get(chunk.payload.id);
|
|
1215
|
+
if (textState) {
|
|
1216
|
+
textState.text += chunk.payload.text;
|
|
1217
|
+
const textContent = currentMessage.content[textState.index];
|
|
1218
|
+
if (textContent && textContent.type === "text") {
|
|
1219
|
+
textContent.text = textState.text;
|
|
1220
|
+
}
|
|
1221
|
+
this.emit({ type: "message_update", message: { ...currentMessage } });
|
|
1222
|
+
}
|
|
1223
|
+
break;
|
|
1224
|
+
}
|
|
1225
|
+
case "reasoning-start": {
|
|
1226
|
+
const thinkingIndex = currentMessage.content.length;
|
|
1227
|
+
currentMessage.content.push({ type: "thinking", thinking: "" });
|
|
1228
|
+
thinkingContentById.set(chunk.payload.id, { index: thinkingIndex, text: "" });
|
|
1229
|
+
this.emit({ type: "message_update", message: { ...currentMessage } });
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
case "reasoning-delta": {
|
|
1233
|
+
const thinkingState = thinkingContentById.get(chunk.payload.id);
|
|
1234
|
+
if (thinkingState) {
|
|
1235
|
+
thinkingState.text += chunk.payload.text;
|
|
1236
|
+
const thinkingContent = currentMessage.content[thinkingState.index];
|
|
1237
|
+
if (thinkingContent && thinkingContent.type === "thinking") {
|
|
1238
|
+
thinkingContent.thinking = thinkingState.text;
|
|
1239
|
+
}
|
|
1240
|
+
this.emit({ type: "message_update", message: { ...currentMessage } });
|
|
1241
|
+
}
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
case "tool-call": {
|
|
1245
|
+
const toolCall = chunk.payload;
|
|
1246
|
+
currentMessage.content.push({
|
|
1247
|
+
type: "tool_call",
|
|
1248
|
+
id: toolCall.toolCallId,
|
|
1249
|
+
name: toolCall.toolName,
|
|
1250
|
+
args: toolCall.args
|
|
1251
|
+
});
|
|
1252
|
+
this.emit({
|
|
1253
|
+
type: "tool_start",
|
|
1254
|
+
toolCallId: toolCall.toolCallId,
|
|
1255
|
+
toolName: toolCall.toolName,
|
|
1256
|
+
args: toolCall.args
|
|
1257
|
+
});
|
|
1258
|
+
this.emit({ type: "message_update", message: { ...currentMessage } });
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
1261
|
+
case "tool-result": {
|
|
1262
|
+
const toolResult = chunk.payload;
|
|
1263
|
+
currentMessage.content.push({
|
|
1264
|
+
type: "tool_result",
|
|
1265
|
+
id: toolResult.toolCallId,
|
|
1266
|
+
name: toolResult.toolName,
|
|
1267
|
+
result: toolResult.result,
|
|
1268
|
+
isError: toolResult.isError ?? false
|
|
1269
|
+
});
|
|
1270
|
+
this.emit({
|
|
1271
|
+
type: "tool_end",
|
|
1272
|
+
toolCallId: toolResult.toolCallId,
|
|
1273
|
+
result: toolResult.result,
|
|
1274
|
+
isError: toolResult.isError ?? false
|
|
1275
|
+
});
|
|
1276
|
+
this.emit({ type: "message_update", message: { ...currentMessage } });
|
|
1277
|
+
break;
|
|
1278
|
+
}
|
|
1279
|
+
case "tool-error": {
|
|
1280
|
+
const toolError = chunk.payload;
|
|
1281
|
+
this.emit({ type: "tool_end", toolCallId: toolError.toolCallId, result: toolError.error, isError: true });
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
case "tool-call-approval": {
|
|
1285
|
+
const toolCallId = chunk.payload.toolCallId;
|
|
1286
|
+
const toolName = chunk.payload.toolName;
|
|
1287
|
+
const toolArgs = chunk.payload.args;
|
|
1288
|
+
const policy = this.resolveToolApproval(toolName);
|
|
1289
|
+
if (policy === "allow") {
|
|
1290
|
+
const result = await this.handleToolApprove(toolCallId);
|
|
1291
|
+
currentMessage = result.message;
|
|
1292
|
+
return { message: currentMessage };
|
|
1293
|
+
}
|
|
1294
|
+
if (policy === "deny") {
|
|
1295
|
+
const result = await this.handleToolDecline(toolCallId);
|
|
1296
|
+
currentMessage = result.message;
|
|
1297
|
+
return { message: currentMessage };
|
|
1298
|
+
}
|
|
1299
|
+
this.pendingApprovalToolName = toolName;
|
|
1300
|
+
this.emit({ type: "tool_approval_required", toolCallId, toolName, args: toolArgs });
|
|
1301
|
+
const decision = await new Promise((resolve) => {
|
|
1302
|
+
this.pendingApprovalResolve = resolve;
|
|
1303
|
+
});
|
|
1304
|
+
this.pendingApprovalToolName = null;
|
|
1305
|
+
if (decision === "approve") {
|
|
1306
|
+
const result = await this.handleToolApprove(toolCallId);
|
|
1307
|
+
currentMessage = result.message;
|
|
1308
|
+
return { message: currentMessage };
|
|
1309
|
+
} else {
|
|
1310
|
+
const result = await this.handleToolDecline(toolCallId);
|
|
1311
|
+
currentMessage = result.message;
|
|
1312
|
+
return { message: currentMessage };
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
case "error": {
|
|
1316
|
+
const streamError = chunk.payload.error instanceof Error ? chunk.payload.error : new Error(String(chunk.payload.error));
|
|
1317
|
+
this.emit({ type: "error", error: streamError });
|
|
1318
|
+
break;
|
|
1319
|
+
}
|
|
1320
|
+
case "step-finish": {
|
|
1321
|
+
const usage = chunk.payload?.output?.usage;
|
|
1322
|
+
if (usage) {
|
|
1323
|
+
const promptTokens = usage.promptTokens ?? 0;
|
|
1324
|
+
const completionTokens = usage.completionTokens ?? 0;
|
|
1325
|
+
const totalTokens = promptTokens + completionTokens;
|
|
1326
|
+
this.tokenUsage.promptTokens += promptTokens;
|
|
1327
|
+
this.tokenUsage.completionTokens += completionTokens;
|
|
1328
|
+
this.tokenUsage.totalTokens += totalTokens;
|
|
1329
|
+
this.persistTokenUsage().catch(() => {
|
|
1330
|
+
});
|
|
1331
|
+
this.emit({ type: "usage_update", usage: { promptTokens, completionTokens, totalTokens } });
|
|
1332
|
+
}
|
|
1333
|
+
break;
|
|
1334
|
+
}
|
|
1335
|
+
case "finish": {
|
|
1336
|
+
const finishReason = chunk.payload.stepResult?.reason;
|
|
1337
|
+
if (finishReason === "stop" || finishReason === "end-turn") {
|
|
1338
|
+
currentMessage.stopReason = "complete";
|
|
1339
|
+
} else if (finishReason === "tool-calls") {
|
|
1340
|
+
currentMessage.stopReason = "tool_use";
|
|
1341
|
+
} else {
|
|
1342
|
+
currentMessage.stopReason = "complete";
|
|
1343
|
+
}
|
|
1344
|
+
break;
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
this.emit({ type: "message_end", message: currentMessage });
|
|
1349
|
+
return { message: currentMessage };
|
|
1350
|
+
}
|
|
1351
|
+
// ===========================================================================
|
|
1352
|
+
// Control
|
|
1353
|
+
// ===========================================================================
|
|
1354
|
+
/**
|
|
1355
|
+
* Abort the current operation.
|
|
1356
|
+
*/
|
|
1357
|
+
abort() {
|
|
1358
|
+
if (this.abortController) {
|
|
1359
|
+
this.abortRequested = true;
|
|
1360
|
+
try {
|
|
1361
|
+
this.abortController.abort();
|
|
1362
|
+
} catch {
|
|
1363
|
+
}
|
|
1364
|
+
this.abortController = null;
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Steer the agent mid-stream: aborts current run and sends a new message.
|
|
1369
|
+
*/
|
|
1370
|
+
async steer(content) {
|
|
1371
|
+
this.abort();
|
|
1372
|
+
this.followUpQueue = [];
|
|
1373
|
+
await this.sendMessage(content);
|
|
1374
|
+
}
|
|
1375
|
+
/**
|
|
1376
|
+
* Queue a follow-up message to be processed after the current operation completes.
|
|
1377
|
+
*/
|
|
1378
|
+
async followUp(content) {
|
|
1379
|
+
if (this.isRunning()) {
|
|
1380
|
+
this.followUpQueue.push(content);
|
|
1381
|
+
this.emit({ type: "follow_up_queued", count: this.followUpQueue.length });
|
|
1382
|
+
} else {
|
|
1383
|
+
await this.sendMessage(content);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
getFollowUpCount() {
|
|
1387
|
+
return this.followUpQueue.length;
|
|
1388
|
+
}
|
|
1389
|
+
isRunning() {
|
|
1390
|
+
return this.abortController !== null;
|
|
1391
|
+
}
|
|
1392
|
+
getCurrentRunId() {
|
|
1393
|
+
return this.currentRunId;
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Respond to a pending tool approval from the UI.
|
|
1397
|
+
* "always_allow_category" grants the tool's category for the rest of the session, then approves.
|
|
1398
|
+
*/
|
|
1399
|
+
resolveToolApprovalDecision(decision) {
|
|
1400
|
+
if (!this.pendingApprovalResolve) return;
|
|
1401
|
+
if (decision === "always_allow_category") {
|
|
1402
|
+
const tn = this.pendingApprovalToolName;
|
|
1403
|
+
if (tn) {
|
|
1404
|
+
const category = this.getToolCategory(tn);
|
|
1405
|
+
if (category) {
|
|
1406
|
+
this.grantSessionCategory(category);
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
this.pendingApprovalResolve("approve");
|
|
1410
|
+
} else {
|
|
1411
|
+
this.pendingApprovalResolve(decision);
|
|
1412
|
+
}
|
|
1413
|
+
this.pendingApprovalResolve = null;
|
|
1414
|
+
}
|
|
1415
|
+
// ===========================================================================
|
|
1416
|
+
// Question & Plan Approval
|
|
1417
|
+
// ===========================================================================
|
|
1418
|
+
/**
|
|
1419
|
+
* Register a pending question resolver.
|
|
1420
|
+
* Called by agent tools (e.g., ask_user) to pause execution until the UI responds.
|
|
1421
|
+
*/
|
|
1422
|
+
registerQuestion(questionId, resolve) {
|
|
1423
|
+
this.pendingQuestions.set(questionId, resolve);
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Resolve a pending question with the user's answer.
|
|
1427
|
+
* Called by the UI when the user responds to a question dialog.
|
|
1428
|
+
*/
|
|
1429
|
+
respondToQuestion(questionId, answer) {
|
|
1430
|
+
const resolve = this.pendingQuestions.get(questionId);
|
|
1431
|
+
if (resolve) {
|
|
1432
|
+
this.pendingQuestions.delete(questionId);
|
|
1433
|
+
resolve(answer);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Register a pending plan approval resolver.
|
|
1438
|
+
* Called by agent tools (e.g., submit_plan) to pause execution until approval.
|
|
1439
|
+
*/
|
|
1440
|
+
registerPlanApproval(planId, resolve) {
|
|
1441
|
+
this.pendingPlanApprovals.set(planId, resolve);
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Respond to a pending plan approval.
|
|
1445
|
+
* On approval: switches to the default mode, then resolves the promise.
|
|
1446
|
+
* On rejection: resolves with feedback (stays in current mode).
|
|
1447
|
+
*/
|
|
1448
|
+
async respondToPlanApproval(planId, response) {
|
|
1449
|
+
const resolve = this.pendingPlanApprovals.get(planId);
|
|
1450
|
+
if (!resolve) return;
|
|
1451
|
+
if (response.action === "approved") {
|
|
1452
|
+
const defaultMode = this.config.modes.find((m) => m.default) ?? this.config.modes[0];
|
|
1453
|
+
if (defaultMode && defaultMode.id !== this.currentModeId) {
|
|
1454
|
+
await this.switchMode(defaultMode.id);
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
this.pendingPlanApprovals.delete(planId);
|
|
1458
|
+
resolve(response);
|
|
1459
|
+
}
|
|
1460
|
+
async handleToolApprove(toolCallId) {
|
|
1461
|
+
if (!this.currentRunId) {
|
|
1462
|
+
throw new Error("No active run to approve tool call for");
|
|
1463
|
+
}
|
|
1464
|
+
const agent = this.getCurrentAgent();
|
|
1465
|
+
if (!this.abortController) {
|
|
1466
|
+
this.abortController = new AbortController();
|
|
1467
|
+
}
|
|
1468
|
+
const requestContext = await this.buildRequestContext();
|
|
1469
|
+
const response = await agent.approveToolCall({
|
|
1470
|
+
runId: this.currentRunId,
|
|
1471
|
+
toolCallId,
|
|
1472
|
+
requireToolApproval: true,
|
|
1473
|
+
memory: this.currentThreadId ? { thread: this.currentThreadId, resource: this.resourceId } : void 0,
|
|
1474
|
+
abortSignal: this.abortController.signal,
|
|
1475
|
+
requestContext,
|
|
1476
|
+
toolsets: await this.buildToolsets(requestContext)
|
|
1477
|
+
});
|
|
1478
|
+
return await this.processStream(response);
|
|
1479
|
+
}
|
|
1480
|
+
async handleToolDecline(toolCallId) {
|
|
1481
|
+
if (!this.currentRunId) {
|
|
1482
|
+
throw new Error("No active run to decline tool call for");
|
|
1483
|
+
}
|
|
1484
|
+
const agent = this.getCurrentAgent();
|
|
1485
|
+
if (!this.abortController) {
|
|
1486
|
+
this.abortController = new AbortController();
|
|
1487
|
+
}
|
|
1488
|
+
const requestContext = await this.buildRequestContext();
|
|
1489
|
+
const response = await agent.declineToolCall({
|
|
1490
|
+
runId: this.currentRunId,
|
|
1491
|
+
toolCallId,
|
|
1492
|
+
requireToolApproval: true,
|
|
1493
|
+
memory: this.currentThreadId ? { thread: this.currentThreadId, resource: this.resourceId } : void 0,
|
|
1494
|
+
abortSignal: this.abortController.signal,
|
|
1495
|
+
requestContext,
|
|
1496
|
+
toolsets: await this.buildToolsets(requestContext)
|
|
1497
|
+
});
|
|
1498
|
+
return await this.processStream(response);
|
|
1499
|
+
}
|
|
1500
|
+
// ===========================================================================
|
|
1501
|
+
// Event System
|
|
1502
|
+
// ===========================================================================
|
|
1503
|
+
/**
|
|
1504
|
+
* Subscribe to harness events. Returns an unsubscribe function.
|
|
1505
|
+
*/
|
|
1506
|
+
subscribe(listener) {
|
|
1507
|
+
this.listeners.push(listener);
|
|
1508
|
+
return () => {
|
|
1509
|
+
const index = this.listeners.indexOf(listener);
|
|
1510
|
+
if (index !== -1) {
|
|
1511
|
+
this.listeners.splice(index, 1);
|
|
1512
|
+
}
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
emit(event) {
|
|
1516
|
+
for (const listener of this.listeners) {
|
|
1517
|
+
try {
|
|
1518
|
+
const result = listener(event);
|
|
1519
|
+
if (result && typeof result === "object" && "catch" in result) {
|
|
1520
|
+
result.catch((err) => console.error("Error in harness event listener:", err));
|
|
1521
|
+
}
|
|
1522
|
+
} catch (err) {
|
|
1523
|
+
console.error("Error in harness event listener:", err);
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
// ===========================================================================
|
|
1528
|
+
// Runtime Context
|
|
1529
|
+
// ===========================================================================
|
|
1530
|
+
/**
|
|
1531
|
+
* Build the toolsets object that includes built-in harness tools (ask_user, submit_plan,
|
|
1532
|
+
* and optionally subagent) plus any user-configured tools.
|
|
1533
|
+
* Used by sendMessage, handleToolApprove, and handleToolDecline.
|
|
1534
|
+
*/
|
|
1535
|
+
async buildToolsets(requestContext) {
|
|
1536
|
+
const builtInTools = {
|
|
1537
|
+
ask_user: askUserTool,
|
|
1538
|
+
submit_plan: submitPlanTool
|
|
1539
|
+
};
|
|
1540
|
+
let resolvedHarnessTools = void 0;
|
|
1541
|
+
if (this.config.tools) {
|
|
1542
|
+
const tools = typeof this.config.tools === "function" ? await this.config.tools({ requestContext }) : this.config.tools;
|
|
1543
|
+
if (tools) {
|
|
1544
|
+
resolvedHarnessTools = tools;
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
if (this.config.subagents?.length && this.config.resolveModel) {
|
|
1548
|
+
const currentMode = this.getCurrentMode();
|
|
1549
|
+
builtInTools.subagent = createSubagentTool({
|
|
1550
|
+
subagents: this.config.subagents,
|
|
1551
|
+
resolveModel: this.config.resolveModel,
|
|
1552
|
+
harnessTools: resolvedHarnessTools,
|
|
1553
|
+
fallbackModelId: currentMode?.defaultModelId
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
if (resolvedHarnessTools) {
|
|
1557
|
+
return { harnessBuiltIn: builtInTools, harness: resolvedHarnessTools };
|
|
1558
|
+
}
|
|
1559
|
+
return { harnessBuiltIn: builtInTools };
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Build request context for agent execution.
|
|
1563
|
+
* Tools can access harness state via requestContext.get('harness').
|
|
1564
|
+
*/
|
|
1565
|
+
async buildRequestContext() {
|
|
1566
|
+
const harnessContext = {
|
|
1567
|
+
harnessId: this.id,
|
|
1568
|
+
state: this.getState(),
|
|
1569
|
+
getState: () => this.getState(),
|
|
1570
|
+
setState: (updates) => this.setState(updates),
|
|
1571
|
+
threadId: this.currentThreadId,
|
|
1572
|
+
resourceId: this.resourceId,
|
|
1573
|
+
modeId: this.currentModeId,
|
|
1574
|
+
abortSignal: this.abortController?.signal,
|
|
1575
|
+
workspace: this.workspace,
|
|
1576
|
+
emitEvent: (event) => this.emit(event),
|
|
1577
|
+
registerQuestion: (questionId, resolve) => this.registerQuestion(questionId, resolve),
|
|
1578
|
+
registerPlanApproval: (planId, resolve) => this.registerPlanApproval(planId, resolve),
|
|
1579
|
+
getSubagentModelId: (agentType) => this.getSubagentModelId(agentType)
|
|
1580
|
+
};
|
|
1581
|
+
const requestContext = new RequestContext([["harness", harnessContext]]);
|
|
1582
|
+
if (this.workspaceFn) {
|
|
1583
|
+
harnessContext.workspace = await Promise.resolve(this.workspaceFn({ requestContext }));
|
|
1584
|
+
}
|
|
1585
|
+
return requestContext;
|
|
1586
|
+
}
|
|
1587
|
+
// ===========================================================================
|
|
1588
|
+
// Token Usage
|
|
1589
|
+
// ===========================================================================
|
|
1590
|
+
getTokenUsage() {
|
|
1591
|
+
return { ...this.tokenUsage };
|
|
1592
|
+
}
|
|
1593
|
+
async persistTokenUsage() {
|
|
1594
|
+
if (!this.currentThreadId || !this.config.storage) return;
|
|
1595
|
+
try {
|
|
1596
|
+
const memoryStorage = await this.getMemoryStorage();
|
|
1597
|
+
const thread = await memoryStorage.getThreadById({ threadId: this.currentThreadId });
|
|
1598
|
+
if (thread) {
|
|
1599
|
+
await memoryStorage.saveThread({
|
|
1600
|
+
thread: {
|
|
1601
|
+
...thread,
|
|
1602
|
+
metadata: { ...thread.metadata, tokenUsage: this.tokenUsage },
|
|
1603
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
} catch {
|
|
1608
|
+
}
|
|
1609
|
+
}
|
|
1610
|
+
// ===========================================================================
|
|
1611
|
+
// Workspace
|
|
1612
|
+
// ===========================================================================
|
|
1613
|
+
getWorkspace() {
|
|
1614
|
+
return this.workspace;
|
|
1615
|
+
}
|
|
1616
|
+
hasWorkspace() {
|
|
1617
|
+
return this.config.workspace !== void 0;
|
|
1618
|
+
}
|
|
1619
|
+
isWorkspaceReady() {
|
|
1620
|
+
if (this.workspaceFn) return true;
|
|
1621
|
+
return this.workspaceInitialized && this.workspace !== void 0;
|
|
1622
|
+
}
|
|
1623
|
+
async destroyWorkspace() {
|
|
1624
|
+
if (this.workspaceFn) return;
|
|
1625
|
+
if (this.workspace && this.workspaceInitialized) {
|
|
1626
|
+
try {
|
|
1627
|
+
this.emit({ type: "workspace_status_changed", status: "destroying" });
|
|
1628
|
+
await this.workspace.destroy();
|
|
1629
|
+
this.emit({ type: "workspace_status_changed", status: "destroyed" });
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
console.warn("Workspace destroy failed:", error);
|
|
1632
|
+
} finally {
|
|
1633
|
+
this.workspaceInitialized = false;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
// ===========================================================================
|
|
1638
|
+
// Heartbeat Handlers
|
|
1639
|
+
// ===========================================================================
|
|
1640
|
+
startHeartbeats() {
|
|
1641
|
+
const handlers = this.config.heartbeatHandlers;
|
|
1642
|
+
if (!handlers?.length) return;
|
|
1643
|
+
for (const hb of handlers) {
|
|
1644
|
+
if (this.heartbeatTimers.has(hb.id)) continue;
|
|
1645
|
+
const run = async () => {
|
|
1646
|
+
try {
|
|
1647
|
+
await hb.handler();
|
|
1648
|
+
} catch (error) {
|
|
1649
|
+
console.error(`[Heartbeat:${hb.id}] failed:`, error);
|
|
1650
|
+
}
|
|
1651
|
+
};
|
|
1652
|
+
if (hb.immediate !== false) {
|
|
1653
|
+
void run();
|
|
1654
|
+
}
|
|
1655
|
+
const timer = setInterval(run, hb.intervalMs);
|
|
1656
|
+
timer.unref();
|
|
1657
|
+
this.heartbeatTimers.set(hb.id, { timer, shutdown: hb.shutdown });
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
registerHeartbeat(handler) {
|
|
1661
|
+
void this.removeHeartbeat(handler.id);
|
|
1662
|
+
const run = async () => {
|
|
1663
|
+
try {
|
|
1664
|
+
await handler.handler();
|
|
1665
|
+
} catch (error) {
|
|
1666
|
+
console.error(`[Heartbeat:${handler.id}] failed:`, error);
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
if (handler.immediate !== false) {
|
|
1670
|
+
void run();
|
|
1671
|
+
}
|
|
1672
|
+
const timer = setInterval(run, handler.intervalMs);
|
|
1673
|
+
timer.unref();
|
|
1674
|
+
this.heartbeatTimers.set(handler.id, { timer, shutdown: handler.shutdown });
|
|
1675
|
+
}
|
|
1676
|
+
async removeHeartbeat(id) {
|
|
1677
|
+
const entry = this.heartbeatTimers.get(id);
|
|
1678
|
+
if (entry) {
|
|
1679
|
+
clearInterval(entry.timer);
|
|
1680
|
+
this.heartbeatTimers.delete(id);
|
|
1681
|
+
try {
|
|
1682
|
+
await entry.shutdown?.();
|
|
1683
|
+
} catch (error) {
|
|
1684
|
+
console.error(`[Heartbeat:${id}] shutdown failed:`, error);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
async stopHeartbeats() {
|
|
1689
|
+
const entries = [...this.heartbeatTimers.entries()];
|
|
1690
|
+
this.heartbeatTimers.clear();
|
|
1691
|
+
for (const [id, entry] of entries) {
|
|
1692
|
+
clearInterval(entry.timer);
|
|
1693
|
+
try {
|
|
1694
|
+
await entry.shutdown?.();
|
|
1695
|
+
} catch (error) {
|
|
1696
|
+
console.error(`[Heartbeat:${id}] shutdown failed:`, error);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
// ===========================================================================
|
|
1701
|
+
// Session
|
|
1702
|
+
// ===========================================================================
|
|
1703
|
+
async getSession() {
|
|
1704
|
+
return {
|
|
1705
|
+
currentThreadId: this.currentThreadId,
|
|
1706
|
+
currentModeId: this.currentModeId,
|
|
1707
|
+
threads: await this.listThreads()
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
// ===========================================================================
|
|
1711
|
+
// Utilities
|
|
1712
|
+
// ===========================================================================
|
|
1713
|
+
generateId() {
|
|
1714
|
+
if (this.config.idGenerator) {
|
|
1715
|
+
return this.config.idGenerator();
|
|
1716
|
+
}
|
|
1717
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
|
|
1721
|
+
export { Harness, askUserTool, parseSubagentMeta, submitPlanTool };
|
|
1722
|
+
//# sourceMappingURL=index.js.map
|
|
1723
|
+
//# sourceMappingURL=index.js.map
|