@cursor/july 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +290 -0
- package/LICENSE.md +3 -0
- package/README.md +1233 -0
- package/dist/ab.d.ts +290 -0
- package/dist/ab.d.ts.map +1 -0
- package/dist/ab.js +386 -0
- package/dist/bin/agent-serve.d.ts +15 -0
- package/dist/bin/agent-serve.d.ts.map +1 -0
- package/dist/bin/agent-serve.js +754 -0
- package/dist/channels/github/api.d.ts +101 -0
- package/dist/channels/github/api.d.ts.map +1 -0
- package/dist/channels/github/api.js +229 -0
- package/dist/channels/github/auth.d.ts +67 -0
- package/dist/channels/github/auth.d.ts.map +1 -0
- package/dist/channels/github/auth.js +239 -0
- package/dist/channels/github/binding.d.ts +73 -0
- package/dist/channels/github/binding.d.ts.map +1 -0
- package/dist/channels/github/binding.js +114 -0
- package/dist/channels/github/defaults.d.ts +24 -0
- package/dist/channels/github/defaults.d.ts.map +1 -0
- package/dist/channels/github/defaults.js +100 -0
- package/dist/channels/github/events.d.ts +18 -0
- package/dist/channels/github/events.d.ts.map +1 -0
- package/dist/channels/github/events.js +59 -0
- package/dist/channels/github/forward.d.ts +137 -0
- package/dist/channels/github/forward.d.ts.map +1 -0
- package/dist/channels/github/forward.js +271 -0
- package/dist/channels/github/github-channel.d.ts +127 -0
- package/dist/channels/github/github-channel.d.ts.map +1 -0
- package/dist/channels/github/github-channel.js +1688 -0
- package/dist/channels/github/index.d.ts +52 -0
- package/dist/channels/github/index.d.ts.map +1 -0
- package/dist/channels/github/index.js +51 -0
- package/dist/channels/github/replay.d.ts +115 -0
- package/dist/channels/github/replay.d.ts.map +1 -0
- package/dist/channels/github/replay.js +282 -0
- package/dist/channels/github/state.d.ts +26 -0
- package/dist/channels/github/state.d.ts.map +1 -0
- package/dist/channels/github/state.js +50 -0
- package/dist/channels/github/types.d.ts +385 -0
- package/dist/channels/github/types.d.ts.map +1 -0
- package/dist/channels/github/types.js +1 -0
- package/dist/channels/slack/api.d.ts +117 -0
- package/dist/channels/slack/api.d.ts.map +1 -0
- package/dist/channels/slack/api.js +437 -0
- package/dist/channels/slack/approvals.d.ts +32 -0
- package/dist/channels/slack/approvals.d.ts.map +1 -0
- package/dist/channels/slack/approvals.js +157 -0
- package/dist/channels/slack/assistant.d.ts +23 -0
- package/dist/channels/slack/assistant.d.ts.map +1 -0
- package/dist/channels/slack/assistant.js +67 -0
- package/dist/channels/slack/auth.d.ts +40 -0
- package/dist/channels/slack/auth.d.ts.map +1 -0
- package/dist/channels/slack/auth.js +71 -0
- package/dist/channels/slack/channel-watch.d.ts +46 -0
- package/dist/channels/slack/channel-watch.d.ts.map +1 -0
- package/dist/channels/slack/channel-watch.js +180 -0
- package/dist/channels/slack/constants.d.ts +17 -0
- package/dist/channels/slack/constants.d.ts.map +1 -0
- package/dist/channels/slack/constants.js +27 -0
- package/dist/channels/slack/credentials.d.ts +35 -0
- package/dist/channels/slack/credentials.d.ts.map +1 -0
- package/dist/channels/slack/credentials.js +88 -0
- package/dist/channels/slack/cursor-account.d.ts +76 -0
- package/dist/channels/slack/cursor-account.d.ts.map +1 -0
- package/dist/channels/slack/cursor-account.js +88 -0
- package/dist/channels/slack/defaults.d.ts +32 -0
- package/dist/channels/slack/defaults.d.ts.map +1 -0
- package/dist/channels/slack/defaults.js +380 -0
- package/dist/channels/slack/dispatch.d.ts +43 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -0
- package/dist/channels/slack/dispatch.js +185 -0
- package/dist/channels/slack/doctor.d.ts +24 -0
- package/dist/channels/slack/doctor.d.ts.map +1 -0
- package/dist/channels/slack/doctor.js +115 -0
- package/dist/channels/slack/event-id-dedupe.d.ts +10 -0
- package/dist/channels/slack/event-id-dedupe.d.ts.map +1 -0
- package/dist/channels/slack/event-id-dedupe.js +23 -0
- package/dist/channels/slack/inbound.d.ts +41 -0
- package/dist/channels/slack/inbound.d.ts.map +1 -0
- package/dist/channels/slack/inbound.js +166 -0
- package/dist/channels/slack/index.d.ts +45 -0
- package/dist/channels/slack/index.d.ts.map +1 -0
- package/dist/channels/slack/index.js +44 -0
- package/dist/channels/slack/init.d.ts +32 -0
- package/dist/channels/slack/init.d.ts.map +1 -0
- package/dist/channels/slack/init.js +82 -0
- package/dist/channels/slack/interactive.d.ts +19 -0
- package/dist/channels/slack/interactive.d.ts.map +1 -0
- package/dist/channels/slack/interactive.js +142 -0
- package/dist/channels/slack/links.d.ts +10 -0
- package/dist/channels/slack/links.d.ts.map +1 -0
- package/dist/channels/slack/links.js +19 -0
- package/dist/channels/slack/live-delivery.d.ts +75 -0
- package/dist/channels/slack/live-delivery.d.ts.map +1 -0
- package/dist/channels/slack/live-delivery.js +143 -0
- package/dist/channels/slack/log.d.ts +25 -0
- package/dist/channels/slack/log.d.ts.map +1 -0
- package/dist/channels/slack/log.js +47 -0
- package/dist/channels/slack/manifest.d.ts +57 -0
- package/dist/channels/slack/manifest.d.ts.map +1 -0
- package/dist/channels/slack/manifest.js +110 -0
- package/dist/channels/slack/pr-ref.d.ts +18 -0
- package/dist/channels/slack/pr-ref.d.ts.map +1 -0
- package/dist/channels/slack/pr-ref.js +36 -0
- package/dist/channels/slack/setup.d.ts +14 -0
- package/dist/channels/slack/setup.d.ts.map +1 -0
- package/dist/channels/slack/setup.js +86 -0
- package/dist/channels/slack/slack-channel.d.ts +15 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -0
- package/dist/channels/slack/slack-channel.js +281 -0
- package/dist/channels/slack/socket-mode.d.ts +26 -0
- package/dist/channels/slack/socket-mode.d.ts.map +1 -0
- package/dist/channels/slack/socket-mode.js +116 -0
- package/dist/channels/slack/stream.d.ts +26 -0
- package/dist/channels/slack/stream.d.ts.map +1 -0
- package/dist/channels/slack/stream.js +39 -0
- package/dist/channels/slack/tool-approval-events.d.ts +33 -0
- package/dist/channels/slack/tool-approval-events.d.ts.map +1 -0
- package/dist/channels/slack/tool-approval-events.js +129 -0
- package/dist/channels/slack/types.d.ts +326 -0
- package/dist/channels/slack/types.d.ts.map +1 -0
- package/dist/channels/slack/types.js +4 -0
- package/dist/channels.d.ts +116 -0
- package/dist/channels.d.ts.map +1 -0
- package/dist/channels.js +294 -0
- package/dist/connections.d.ts +75 -0
- package/dist/connections.d.ts.map +1 -0
- package/dist/connections.js +75 -0
- package/dist/docs/404.html +22 -0
- package/dist/docs/ab.html +87 -0
- package/dist/docs/assets/ab.md.COdXkces.js +63 -0
- package/dist/docs/assets/ab.md.COdXkces.lean.js +1 -0
- package/dist/docs/assets/app.DqfFEmJd.js +1 -0
- package/dist/docs/assets/building-with-agents.md.CJCtZCyi.js +13 -0
- package/dist/docs/assets/building-with-agents.md.CJCtZCyi.lean.js +1 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.CcVk1uKq.js +1 -0
- package/dist/docs/assets/chunks/VPLocalSearchBox.BaLEdS15.js +9 -0
- package/dist/docs/assets/chunks/framework.CAZyNGu9.js +19 -0
- package/dist/docs/assets/chunks/theme.CZRvu_0q.js +2 -0
- package/dist/docs/assets/concepts.md.Cfb9b-k1.js +4 -0
- package/dist/docs/assets/concepts.md.Cfb9b-k1.lean.js +1 -0
- package/dist/docs/assets/deployment.md.Dx1TYNk5.js +14 -0
- package/dist/docs/assets/deployment.md.Dx1TYNk5.lean.js +1 -0
- package/dist/docs/assets/evals.md.DPZ_MAnI.js +85 -0
- package/dist/docs/assets/evals.md.DPZ_MAnI.lean.js +1 -0
- package/dist/docs/assets/guides_agent-to-agent.md.CrtrsySy.js +10 -0
- package/dist/docs/assets/guides_agent-to-agent.md.CrtrsySy.lean.js +1 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CYlNMTNp.js +9 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CYlNMTNp.lean.js +1 -0
- package/dist/docs/assets/guides_github.md.DwbKhCeS.js +20 -0
- package/dist/docs/assets/guides_github.md.DwbKhCeS.lean.js +1 -0
- package/dist/docs/assets/guides_human-in-the-loop.md.Dvuctx7s.js +28 -0
- package/dist/docs/assets/guides_human-in-the-loop.md.Dvuctx7s.lean.js +1 -0
- package/dist/docs/assets/guides_slack.md.bv41fHfW.js +37 -0
- package/dist/docs/assets/guides_slack.md.bv41fHfW.lean.js +1 -0
- package/dist/docs/assets/guides_webhooks.md.hFTik3lf.js +50 -0
- package/dist/docs/assets/guides_webhooks.md.hFTik3lf.lean.js +1 -0
- package/dist/docs/assets/hillclimbing.md.DHNast08.js +4 -0
- package/dist/docs/assets/hillclimbing.md.DHNast08.lean.js +1 -0
- package/dist/docs/assets/index.md.BPKcj5AI.js +20 -0
- package/dist/docs/assets/index.md.BPKcj5AI.lean.js +1 -0
- package/dist/docs/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/dist/docs/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/dist/docs/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/dist/docs/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/dist/docs/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/dist/docs/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/dist/docs/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/dist/docs/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/dist/docs/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/dist/docs/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/dist/docs/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/dist/docs/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/dist/docs/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/dist/docs/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/dist/docs/assets/quickstart.md.tVPiGK_L.js +35 -0
- package/dist/docs/assets/quickstart.md.tVPiGK_L.lean.js +1 -0
- package/dist/docs/assets/reference_agent-config.md.Bpd7HQwf.js +34 -0
- package/dist/docs/assets/reference_agent-config.md.Bpd7HQwf.lean.js +1 -0
- package/dist/docs/assets/reference_channels.md.D7JTR03W.js +53 -0
- package/dist/docs/assets/reference_channels.md.D7JTR03W.lean.js +1 -0
- package/dist/docs/assets/reference_cli.md.DA730zCu.js +33 -0
- package/dist/docs/assets/reference_cli.md.DA730zCu.lean.js +1 -0
- package/dist/docs/assets/reference_connections.md.C3vNH_DE.js +26 -0
- package/dist/docs/assets/reference_connections.md.C3vNH_DE.lean.js +1 -0
- package/dist/docs/assets/reference_hooks.md.BCEc3MyM.js +12 -0
- package/dist/docs/assets/reference_hooks.md.BCEc3MyM.lean.js +1 -0
- package/dist/docs/assets/reference_http-api.md.DBAahtdz.js +11 -0
- package/dist/docs/assets/reference_http-api.md.DBAahtdz.lean.js +1 -0
- package/dist/docs/assets/reference_instructions.md.BC05LEQ8.js +12 -0
- package/dist/docs/assets/reference_instructions.md.BC05LEQ8.lean.js +1 -0
- package/dist/docs/assets/reference_playground.md.CPZhfYaO.js +1 -0
- package/dist/docs/assets/reference_playground.md.CPZhfYaO.lean.js +1 -0
- package/dist/docs/assets/reference_project-layout.md.Bd_CKtNS.js +31 -0
- package/dist/docs/assets/reference_project-layout.md.Bd_CKtNS.lean.js +1 -0
- package/dist/docs/assets/reference_schedules.md.D7qijxLk.js +82 -0
- package/dist/docs/assets/reference_schedules.md.D7qijxLk.lean.js +1 -0
- package/dist/docs/assets/reference_sessions.md.DdjWabnN.js +8 -0
- package/dist/docs/assets/reference_sessions.md.DdjWabnN.lean.js +1 -0
- package/dist/docs/assets/reference_skills.md.VQnlBT3Q.js +15 -0
- package/dist/docs/assets/reference_skills.md.VQnlBT3Q.lean.js +1 -0
- package/dist/docs/assets/reference_subagents.md.CIRAVcPK.js +10 -0
- package/dist/docs/assets/reference_subagents.md.CIRAVcPK.lean.js +1 -0
- package/dist/docs/assets/reference_tools.md.DF5kwlt0.js +46 -0
- package/dist/docs/assets/reference_tools.md.DF5kwlt0.lean.js +1 -0
- package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.js +1 -0
- package/dist/docs/assets/scaffolding-agents.md.CyYfWGdc.lean.js +1 -0
- package/dist/docs/assets/style.Bj-IXa5W.css +1 -0
- package/dist/docs/assets/troubleshooting.md.DkovjS7h.js +1 -0
- package/dist/docs/assets/troubleshooting.md.DkovjS7h.lean.js +1 -0
- package/dist/docs/building-with-agents.html +37 -0
- package/dist/docs/concepts.html +28 -0
- package/dist/docs/deployment.html +38 -0
- package/dist/docs/evals.html +109 -0
- package/dist/docs/guides/agent-to-agent.html +34 -0
- package/dist/docs/guides/cloud-runtime.html +33 -0
- package/dist/docs/guides/github.html +44 -0
- package/dist/docs/guides/human-in-the-loop.html +52 -0
- package/dist/docs/guides/slack.html +61 -0
- package/dist/docs/guides/webhooks.html +74 -0
- package/dist/docs/hashmap.json +1 -0
- package/dist/docs/hillclimbing.html +28 -0
- package/dist/docs/index.html +44 -0
- package/dist/docs/quickstart.html +59 -0
- package/dist/docs/reference/agent-config.html +58 -0
- package/dist/docs/reference/channels.html +77 -0
- package/dist/docs/reference/cli.html +57 -0
- package/dist/docs/reference/connections.html +50 -0
- package/dist/docs/reference/hooks.html +36 -0
- package/dist/docs/reference/http-api.html +35 -0
- package/dist/docs/reference/instructions.html +36 -0
- package/dist/docs/reference/playground.html +25 -0
- package/dist/docs/reference/project-layout.html +55 -0
- package/dist/docs/reference/schedules.html +106 -0
- package/dist/docs/reference/sessions.html +32 -0
- package/dist/docs/reference/skills.html +39 -0
- package/dist/docs/reference/subagents.html +34 -0
- package/dist/docs/reference/tools.html +70 -0
- package/dist/docs/scaffolding-agents.html +25 -0
- package/dist/docs/troubleshooting.html +25 -0
- package/dist/docs/vp-icons.css +0 -0
- package/dist/evals.d.ts +312 -0
- package/dist/evals.d.ts.map +1 -0
- package/dist/evals.js +254 -0
- package/dist/hooks.d.ts +23 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +23 -0
- package/dist/index.d.ts +71 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/internal/ab-collector.d.ts +42 -0
- package/dist/internal/ab-collector.d.ts.map +1 -0
- package/dist/internal/ab-collector.js +153 -0
- package/dist/internal/ab-fold.d.ts +36 -0
- package/dist/internal/ab-fold.d.ts.map +1 -0
- package/dist/internal/ab-fold.js +171 -0
- package/dist/internal/ab-snapshot.d.ts +70 -0
- package/dist/internal/ab-snapshot.d.ts.map +1 -0
- package/dist/internal/ab-snapshot.js +208 -0
- package/dist/internal/approval-gate.d.ts +34 -0
- package/dist/internal/approval-gate.d.ts.map +1 -0
- package/dist/internal/approval-gate.js +85 -0
- package/dist/internal/auth.d.ts +8 -0
- package/dist/internal/auth.d.ts.map +1 -0
- package/dist/internal/auth.js +27 -0
- package/dist/internal/authored-alias-hooks.d.ts +31 -0
- package/dist/internal/authored-alias-hooks.d.ts.map +1 -0
- package/dist/internal/authored-alias-hooks.js +43 -0
- package/dist/internal/authored-loaders.d.ts +25 -0
- package/dist/internal/authored-loaders.d.ts.map +1 -0
- package/dist/internal/authored-loaders.js +129 -0
- package/dist/internal/bounded-int.d.ts +11 -0
- package/dist/internal/bounded-int.d.ts.map +1 -0
- package/dist/internal/bounded-int.js +13 -0
- package/dist/internal/brand.d.ts +6 -0
- package/dist/internal/brand.d.ts.map +1 -0
- package/dist/internal/brand.js +17 -0
- package/dist/internal/chat-client.d.ts +19 -0
- package/dist/internal/chat-client.d.ts.map +1 -0
- package/dist/internal/chat-client.js +327 -0
- package/dist/internal/cli-ax.d.ts +58 -0
- package/dist/internal/cli-ax.d.ts.map +1 -0
- package/dist/internal/cli-ax.js +345 -0
- package/dist/internal/cli-cursor.d.ts +20 -0
- package/dist/internal/cli-cursor.d.ts.map +1 -0
- package/dist/internal/cli-cursor.js +227 -0
- package/dist/internal/cli-github.d.ts +47 -0
- package/dist/internal/cli-github.d.ts.map +1 -0
- package/dist/internal/cli-github.js +644 -0
- package/dist/internal/cli-slack.d.ts +18 -0
- package/dist/internal/cli-slack.d.ts.map +1 -0
- package/dist/internal/cli-slack.js +144 -0
- package/dist/internal/cloud-merge.d.ts +23 -0
- package/dist/internal/cloud-merge.d.ts.map +1 -0
- package/dist/internal/cloud-merge.js +28 -0
- package/dist/internal/cron.d.ts +17 -0
- package/dist/internal/cron.d.ts.map +1 -0
- package/dist/internal/cron.js +60 -0
- package/dist/internal/cursor/account-mcp.d.ts +95 -0
- package/dist/internal/cursor/account-mcp.d.ts.map +1 -0
- package/dist/internal/cursor/account-mcp.js +342 -0
- package/dist/internal/cursor/backend-client.d.ts +118 -0
- package/dist/internal/cursor/backend-client.d.ts.map +1 -0
- package/dist/internal/cursor/backend-client.js +477 -0
- package/dist/internal/cursor/credentials.d.ts +112 -0
- package/dist/internal/cursor/credentials.d.ts.map +1 -0
- package/dist/internal/cursor/credentials.js +329 -0
- package/dist/internal/cursor-event-relay.d.ts +111 -0
- package/dist/internal/cursor-event-relay.d.ts.map +1 -0
- package/dist/internal/cursor-event-relay.js +181 -0
- package/dist/internal/cursor-relay-core.d.ts +113 -0
- package/dist/internal/cursor-relay-core.d.ts.map +1 -0
- package/dist/internal/cursor-relay-core.js +364 -0
- package/dist/internal/cursor-slack-relay.d.ts +94 -0
- package/dist/internal/cursor-slack-relay.d.ts.map +1 -0
- package/dist/internal/cursor-slack-relay.js +171 -0
- package/dist/internal/describe-error.d.ts +3 -0
- package/dist/internal/describe-error.d.ts.map +1 -0
- package/dist/internal/describe-error.js +4 -0
- package/dist/internal/dev-mode.d.ts +13 -0
- package/dist/internal/dev-mode.d.ts.map +1 -0
- package/dist/internal/dev-mode.js +17 -0
- package/dist/internal/discovery.d.ts +55 -0
- package/dist/internal/discovery.d.ts.map +1 -0
- package/dist/internal/discovery.js +1427 -0
- package/dist/internal/distribution.d.ts +47 -0
- package/dist/internal/distribution.d.ts.map +1 -0
- package/dist/internal/distribution.js +82 -0
- package/dist/internal/docs-site.d.ts +28 -0
- package/dist/internal/docs-site.d.ts.map +1 -0
- package/dist/internal/docs-site.js +169 -0
- package/dist/internal/duration.d.ts +8 -0
- package/dist/internal/duration.d.ts.map +1 -0
- package/dist/internal/duration.js +35 -0
- package/dist/internal/eval-run-store.d.ts +81 -0
- package/dist/internal/eval-run-store.d.ts.map +1 -0
- package/dist/internal/eval-run-store.js +396 -0
- package/dist/internal/eval-runner.d.ts +76 -0
- package/dist/internal/eval-runner.d.ts.map +1 -0
- package/dist/internal/eval-runner.js +336 -0
- package/dist/internal/event-log.d.ts +51 -0
- package/dist/internal/event-log.d.ts.map +1 -0
- package/dist/internal/event-log.js +167 -0
- package/dist/internal/event-mapper.d.ts +46 -0
- package/dist/internal/event-mapper.d.ts.map +1 -0
- package/dist/internal/event-mapper.js +226 -0
- package/dist/internal/frontmatter.d.ts +12 -0
- package/dist/internal/frontmatter.d.ts.map +1 -0
- package/dist/internal/frontmatter.js +61 -0
- package/dist/internal/github-fanout.d.ts +32 -0
- package/dist/internal/github-fanout.d.ts.map +1 -0
- package/dist/internal/github-fanout.js +122 -0
- package/dist/internal/handleAgentServeTrigger.d.ts +59 -0
- package/dist/internal/handleAgentServeTrigger.d.ts.map +1 -0
- package/dist/internal/handleAgentServeTrigger.js +115 -0
- package/dist/internal/host-platforms.d.ts +40 -0
- package/dist/internal/host-platforms.d.ts.map +1 -0
- package/dist/internal/host-platforms.js +113 -0
- package/dist/internal/http-channel.d.ts +54 -0
- package/dist/internal/http-channel.d.ts.map +1 -0
- package/dist/internal/http-channel.js +382 -0
- package/dist/internal/ids.d.ts +19 -0
- package/dist/internal/ids.d.ts.map +1 -0
- package/dist/internal/ids.js +34 -0
- package/dist/internal/init-project.d.ts +22 -0
- package/dist/internal/init-project.d.ts.map +1 -0
- package/dist/internal/init-project.js +125 -0
- package/dist/internal/json-dir-store.d.ts +32 -0
- package/dist/internal/json-dir-store.d.ts.map +1 -0
- package/dist/internal/json-dir-store.js +100 -0
- package/dist/internal/json-schema.d.ts +18 -0
- package/dist/internal/json-schema.d.ts.map +1 -0
- package/dist/internal/json-schema.js +53 -0
- package/dist/internal/log-ring.d.ts +23 -0
- package/dist/internal/log-ring.d.ts.map +1 -0
- package/dist/internal/log-ring.js +42 -0
- package/dist/internal/map-pool.d.ts +6 -0
- package/dist/internal/map-pool.d.ts.map +1 -0
- package/dist/internal/map-pool.js +41 -0
- package/dist/internal/mcp-endpoint.d.ts +59 -0
- package/dist/internal/mcp-endpoint.d.ts.map +1 -0
- package/dist/internal/mcp-endpoint.js +355 -0
- package/dist/internal/mcp-host.d.ts +28 -0
- package/dist/internal/mcp-host.d.ts.map +1 -0
- package/dist/internal/mcp-host.js +162 -0
- package/dist/internal/net.d.ts +5 -0
- package/dist/internal/net.d.ts.map +1 -0
- package/dist/internal/net.js +30 -0
- package/dist/internal/peer-connections.d.ts +44 -0
- package/dist/internal/peer-connections.d.ts.map +1 -0
- package/dist/internal/peer-connections.js +67 -0
- package/dist/internal/playground/static.d.ts +35 -0
- package/dist/internal/playground/static.d.ts.map +1 -0
- package/dist/internal/playground/static.js +162 -0
- package/dist/internal/playground/toolchain.d.ts +57 -0
- package/dist/internal/playground/toolchain.d.ts.map +1 -0
- package/dist/internal/playground/toolchain.js +226 -0
- package/dist/internal/reminder-runner.d.ts +62 -0
- package/dist/internal/reminder-runner.d.ts.map +1 -0
- package/dist/internal/reminder-runner.js +582 -0
- package/dist/internal/reminder-schedule.d.ts +22 -0
- package/dist/internal/reminder-schedule.d.ts.map +1 -0
- package/dist/internal/reminder-schedule.js +82 -0
- package/dist/internal/reminder-store.d.ts +58 -0
- package/dist/internal/reminder-store.d.ts.map +1 -0
- package/dist/internal/reminder-store.js +106 -0
- package/dist/internal/resolved-connections.d.ts +41 -0
- package/dist/internal/resolved-connections.d.ts.map +1 -0
- package/dist/internal/resolved-connections.js +73 -0
- package/dist/internal/review-comments.d.ts +50 -0
- package/dist/internal/review-comments.d.ts.map +1 -0
- package/dist/internal/review-comments.js +194 -0
- package/dist/internal/router.d.ts +33 -0
- package/dist/internal/router.d.ts.map +1 -0
- package/dist/internal/router.js +93 -0
- package/dist/internal/run-client.d.ts +49 -0
- package/dist/internal/run-client.d.ts.map +1 -0
- package/dist/internal/run-client.js +208 -0
- package/dist/internal/schedule-runner.d.ts +31 -0
- package/dist/internal/schedule-runner.d.ts.map +1 -0
- package/dist/internal/schedule-runner.js +126 -0
- package/dist/internal/sdk-runner.d.ts +41 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -0
- package/dist/internal/sdk-runner.js +221 -0
- package/dist/internal/server.d.ts +25 -0
- package/dist/internal/server.d.ts.map +1 -0
- package/dist/internal/server.js +1374 -0
- package/dist/internal/session-engine.d.ts +287 -0
- package/dist/internal/session-engine.d.ts.map +1 -0
- package/dist/internal/session-engine.js +1377 -0
- package/dist/internal/session-store.d.ts +29 -0
- package/dist/internal/session-store.d.ts.map +1 -0
- package/dist/internal/session-store.js +140 -0
- package/dist/internal/session-urls.d.ts +10 -0
- package/dist/internal/session-urls.d.ts.map +1 -0
- package/dist/internal/session-urls.js +25 -0
- package/dist/internal/stream-progress.d.ts +14 -0
- package/dist/internal/stream-progress.d.ts.map +1 -0
- package/dist/internal/stream-progress.js +195 -0
- package/dist/internal/tool-result.d.ts +20 -0
- package/dist/internal/tool-result.d.ts.map +1 -0
- package/dist/internal/tool-result.js +45 -0
- package/dist/internal/trajectory.d.ts +56 -0
- package/dist/internal/trajectory.d.ts.map +1 -0
- package/dist/internal/trajectory.js +248 -0
- package/dist/internal/workspace.d.ts +42 -0
- package/dist/internal/workspace.d.ts.map +1 -0
- package/dist/internal/workspace.js +199 -0
- package/dist/playground/assets/cursor-icons-outline-oY2V_mvK.woff2 +0 -0
- package/dist/playground/assets/index-1K-hG-7p.css +1 -0
- package/dist/playground/assets/index-FlWjhg3x.js +79 -0
- package/dist/playground/index.html +17 -0
- package/dist/reminders.d.ts +23 -0
- package/dist/reminders.d.ts.map +1 -0
- package/dist/reminders.js +22 -0
- package/dist/schedules.d.ts +23 -0
- package/dist/schedules.d.ts.map +1 -0
- package/dist/schedules.js +26 -0
- package/dist/skills.d.ts +22 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +22 -0
- package/dist/tools.d.ts +92 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +68 -0
- package/dist/types.d.ts +1609 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +80 -0
- package/docs/README.md +174 -0
- package/docs/ab.md +355 -0
- package/docs/building-with-agents.md +129 -0
- package/docs/concepts.md +177 -0
- package/docs/deployment.md +218 -0
- package/docs/evals.md +383 -0
- package/docs/guides/agent-to-agent.md +118 -0
- package/docs/guides/cloud-runtime.md +122 -0
- package/docs/guides/github.md +172 -0
- package/docs/guides/human-in-the-loop.md +131 -0
- package/docs/guides/slack.md +245 -0
- package/docs/guides/webhooks.md +201 -0
- package/docs/hillclimbing.md +93 -0
- package/docs/quickstart.md +183 -0
- package/docs/reference/agent-config.md +154 -0
- package/docs/reference/channels.md +206 -0
- package/docs/reference/cli.md +257 -0
- package/docs/reference/connections.md +137 -0
- package/docs/reference/hooks.md +74 -0
- package/docs/reference/http-api.md +219 -0
- package/docs/reference/instructions.md +77 -0
- package/docs/reference/playground.md +67 -0
- package/docs/reference/project-layout.md +119 -0
- package/docs/reference/schedules.md +220 -0
- package/docs/reference/sessions.md +165 -0
- package/docs/reference/skills.md +79 -0
- package/docs/reference/subagents.md +74 -0
- package/docs/reference/tools.md +203 -0
- package/docs/scaffolding-agents.md +113 -0
- package/docs/troubleshooting.md +95 -0
- package/package.json +185 -0
- package/skills/ab/SKILL.md +139 -0
- package/skills/create-agent/SKILL.md +219 -0
- package/skills/debug/SKILL.md +71 -0
- package/skills/evals/SKILL.md +217 -0
- package/skills/framework-map/SKILL.md +142 -0
- package/skills/github/SKILL.md +144 -0
- package/skills/hillclimb/SKILL.md +162 -0
- package/skills/setup-slack/SKILL.md +180 -0
- package/src/ab.ts +675 -0
- package/src/bin/agent-serve.ts +949 -0
- package/src/channels/github/api.ts +374 -0
- package/src/channels/github/auth.ts +275 -0
- package/src/channels/github/binding.ts +223 -0
- package/src/channels/github/defaults.ts +111 -0
- package/src/channels/github/events.ts +72 -0
- package/src/channels/github/forward.ts +381 -0
- package/src/channels/github/github-channel.ts +2270 -0
- package/src/channels/github/index.ts +52 -0
- package/src/channels/github/replay.ts +387 -0
- package/src/channels/github/state.ts +95 -0
- package/src/channels/github/types.ts +490 -0
- package/src/channels/slack/api.ts +580 -0
- package/src/channels/slack/approvals.ts +198 -0
- package/src/channels/slack/assistant.ts +76 -0
- package/src/channels/slack/auth.ts +112 -0
- package/src/channels/slack/channel-watch.ts +219 -0
- package/src/channels/slack/constants.ts +30 -0
- package/src/channels/slack/credentials.ts +90 -0
- package/src/channels/slack/cursor-account.ts +166 -0
- package/src/channels/slack/defaults.ts +450 -0
- package/src/channels/slack/dispatch.ts +293 -0
- package/src/channels/slack/doctor.ts +135 -0
- package/src/channels/slack/event-id-dedupe.ts +30 -0
- package/src/channels/slack/inbound.ts +219 -0
- package/src/channels/slack/index.ts +45 -0
- package/src/channels/slack/init.ts +133 -0
- package/src/channels/slack/interactive.ts +194 -0
- package/src/channels/slack/links.ts +23 -0
- package/src/channels/slack/live-delivery.ts +201 -0
- package/src/channels/slack/log.ts +83 -0
- package/src/channels/slack/manifest.ts +179 -0
- package/src/channels/slack/pr-ref.ts +50 -0
- package/src/channels/slack/setup.ts +97 -0
- package/src/channels/slack/slack-channel.ts +374 -0
- package/src/channels/slack/socket-mode.ts +154 -0
- package/src/channels/slack/stream.ts +63 -0
- package/src/channels/slack/tool-approval-events.ts +136 -0
- package/src/channels/slack/types.ts +349 -0
- package/src/channels.ts +385 -0
- package/src/connections.ts +89 -0
- package/src/evals.ts +501 -0
- package/src/hooks.ts +33 -0
- package/src/index.ts +184 -0
- package/src/internal/ab-collector.ts +210 -0
- package/src/internal/ab-fold.ts +228 -0
- package/src/internal/ab-snapshot.ts +335 -0
- package/src/internal/approval-gate.ts +115 -0
- package/src/internal/auth.ts +39 -0
- package/src/internal/authored-alias-hooks.ts +69 -0
- package/src/internal/authored-loaders.ts +138 -0
- package/src/internal/bounded-int.ts +27 -0
- package/src/internal/brand.ts +29 -0
- package/src/internal/chat-client.ts +407 -0
- package/src/internal/cli-ax.ts +474 -0
- package/src/internal/cli-cursor.ts +275 -0
- package/src/internal/cli-github.ts +849 -0
- package/src/internal/cli-slack.ts +175 -0
- package/src/internal/cloud-merge.ts +41 -0
- package/src/internal/cron.ts +78 -0
- package/src/internal/cursor/account-mcp.ts +472 -0
- package/src/internal/cursor/backend-client.ts +614 -0
- package/src/internal/cursor/credentials.ts +401 -0
- package/src/internal/cursor-event-relay.ts +281 -0
- package/src/internal/cursor-relay-core.ts +444 -0
- package/src/internal/cursor-slack-relay.ts +242 -0
- package/src/internal/describe-error.ts +4 -0
- package/src/internal/dev-mode.ts +20 -0
- package/src/internal/discovery.ts +1709 -0
- package/src/internal/distribution.ts +97 -0
- package/src/internal/docs-site.ts +191 -0
- package/src/internal/duration.ts +39 -0
- package/src/internal/eval-run-store.ts +519 -0
- package/src/internal/eval-runner.ts +522 -0
- package/src/internal/event-log.ts +175 -0
- package/src/internal/event-mapper.ts +272 -0
- package/src/internal/frontmatter.ts +77 -0
- package/src/internal/github-fanout.ts +159 -0
- package/src/internal/handleAgentServeTrigger.ts +156 -0
- package/src/internal/host-platforms.ts +144 -0
- package/src/internal/http-channel.ts +546 -0
- package/src/internal/ids.ts +44 -0
- package/src/internal/init-project.ts +165 -0
- package/src/internal/json-dir-store.ts +109 -0
- package/src/internal/json-schema.ts +69 -0
- package/src/internal/log-ring.ts +59 -0
- package/src/internal/map-pool.ts +32 -0
- package/src/internal/mcp-endpoint.ts +518 -0
- package/src/internal/mcp-host.ts +211 -0
- package/src/internal/net.ts +31 -0
- package/src/internal/peer-connections.ts +100 -0
- package/src/internal/playground/static.ts +189 -0
- package/src/internal/playground/toolchain.ts +277 -0
- package/src/internal/reminder-runner.ts +756 -0
- package/src/internal/reminder-schedule.ts +106 -0
- package/src/internal/reminder-store.ts +145 -0
- package/src/internal/resolved-connections.ts +145 -0
- package/src/internal/review-comments.ts +268 -0
- package/src/internal/router.ts +145 -0
- package/src/internal/run-client.ts +320 -0
- package/src/internal/schedule-runner.ts +161 -0
- package/src/internal/sdk-runner.ts +315 -0
- package/src/internal/server.ts +1737 -0
- package/src/internal/session-engine.ts +1810 -0
- package/src/internal/session-store.ts +147 -0
- package/src/internal/session-urls.ts +40 -0
- package/src/internal/stream-progress.ts +254 -0
- package/src/internal/tool-result.ts +54 -0
- package/src/internal/trajectory.ts +306 -0
- package/src/internal/workspace.ts +223 -0
- package/src/reminders.ts +35 -0
- package/src/schedules.ts +37 -0
- package/src/skills.ts +27 -0
- package/src/tools.ts +127 -0
- package/src/types.ts +1848 -0
|
@@ -0,0 +1,1377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session engine owns durable sessions and turn execution. A session is
|
|
3
|
+
* one Cursor agent conversation plus a materialized workspace, an event log,
|
|
4
|
+
* and channel bookkeeping (continuation keys, per-session channel state).
|
|
5
|
+
* Turns are executed by the Cursor SDK runner and their streaming updates
|
|
6
|
+
* are mapped onto the channel event vocabulary and dispatched to channel
|
|
7
|
+
* event handlers and hooks.
|
|
8
|
+
*/
|
|
9
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
10
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
11
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
12
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
13
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
14
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
15
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
19
|
+
var t = {};
|
|
20
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
21
|
+
t[p] = s[p];
|
|
22
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
23
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
24
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
25
|
+
t[p[i]] = s[p[i]];
|
|
26
|
+
}
|
|
27
|
+
return t;
|
|
28
|
+
};
|
|
29
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
30
|
+
import { join } from "node:path";
|
|
31
|
+
import { mergeABInstructions, resolveABMaxPlaygroundSessions, resolveABSplit, } from "../ab.js";
|
|
32
|
+
import { slackThreadPermalink } from "../channels/slack/links.js";
|
|
33
|
+
import { DIRECT_TOOL_CALL_CHANNEL_ID, isSymbolicConnectionTransport, } from "../types.js";
|
|
34
|
+
import { ABCollector } from "./ab-collector.js";
|
|
35
|
+
import { foldABStream } from "./ab-fold.js";
|
|
36
|
+
import { buildABSnapshot } from "./ab-snapshot.js";
|
|
37
|
+
import { ApprovalGate } from "./approval-gate.js";
|
|
38
|
+
import { samePrincipal } from "./auth.js";
|
|
39
|
+
import { mergeCloudOptions, resolveSessionRuntime } from "./cloud-merge.js";
|
|
40
|
+
import { EventLogStore } from "./event-log.js";
|
|
41
|
+
import { TurnEventMapper } from "./event-mapper.js";
|
|
42
|
+
import { createHostPlatforms } from "./host-platforms.js";
|
|
43
|
+
import { formatContinuationToken, newContinuationKey, newSessionId, newToolCallId, } from "./ids.js";
|
|
44
|
+
import { validateToolInput } from "./json-schema.js";
|
|
45
|
+
import { McpHost } from "./mcp-host.js";
|
|
46
|
+
import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections.js";
|
|
47
|
+
import { SessionStore } from "./session-store.js";
|
|
48
|
+
import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
|
|
49
|
+
import { buildAgentToolsCatalog, materializeWorkspace, writeWorkspaceFiles, } from "./workspace.js";
|
|
50
|
+
export const HTTP_CHANNEL_ID = "http";
|
|
51
|
+
export const SCHEDULE_CHANNEL_PREFIX = "schedule:";
|
|
52
|
+
export class SessionBusyError extends Error {
|
|
53
|
+
constructor(sessionId) {
|
|
54
|
+
super(`Session ${sessionId} already has a running turn; wait for session.waiting before sending a follow-up`);
|
|
55
|
+
this.name = "SessionBusyError";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class SessionAuthError extends Error {
|
|
59
|
+
constructor(sessionId) {
|
|
60
|
+
super(`Session ${sessionId} belongs to a different principal; pass the original caller auth on follow-up`);
|
|
61
|
+
this.name = "SessionAuthError";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export class UnknownChannelError extends Error {
|
|
65
|
+
constructor(channelId) {
|
|
66
|
+
super(`Unknown channel "${channelId}"`);
|
|
67
|
+
this.name = "UnknownChannelError";
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export class UnknownToolError extends Error {
|
|
71
|
+
constructor(toolName, availableToolNames) {
|
|
72
|
+
super(`Unknown tool "${toolName}"`);
|
|
73
|
+
this.availableToolNames = availableToolNames;
|
|
74
|
+
this.name = "UnknownToolError";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export class ToolNotDirectlyCallableError extends Error {
|
|
78
|
+
constructor(toolName) {
|
|
79
|
+
super(`Tool "${toolName}" runs in the agent environment (execution: "agent") and cannot be called directly on the host`);
|
|
80
|
+
this.name = "ToolNotDirectlyCallableError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export class ToolInputError extends Error {
|
|
84
|
+
constructor(args) {
|
|
85
|
+
super(`Invalid input for tool "${args.toolName}": ${args.detail}`);
|
|
86
|
+
this.name = "ToolInputError";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export class UnknownSessionError extends Error {
|
|
90
|
+
constructor(sessionId) {
|
|
91
|
+
super(`Unknown session ${sessionId}`);
|
|
92
|
+
this.name = "UnknownSessionError";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export class ApprovalNotFoundError extends Error {
|
|
96
|
+
constructor(sessionId, callId) {
|
|
97
|
+
super(`No pending approval for session ${sessionId} call ${callId}`);
|
|
98
|
+
this.name = "ApprovalNotFoundError";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
export class ApprovalInterruptedError extends Error {
|
|
102
|
+
constructor(sessionId, callId) {
|
|
103
|
+
super(`Approval for session ${sessionId} call ${callId} is no longer live (host may have restarted while the tool was parked)`);
|
|
104
|
+
this.name = "ApprovalInterruptedError";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export class SessionEngine {
|
|
108
|
+
constructor(options) {
|
|
109
|
+
var _a;
|
|
110
|
+
var _b;
|
|
111
|
+
this.channelsById = new Map();
|
|
112
|
+
/** Sessions with a turn being admitted (before the run promise exists). */
|
|
113
|
+
this.pendingTurns = new Set();
|
|
114
|
+
this.activeTurns = new Map();
|
|
115
|
+
/** Aborts the in-flight turn per session so a follow-up can preempt it. */
|
|
116
|
+
this.turnAborts = new Map();
|
|
117
|
+
/** Serializes turn admission per session (latest follow-up wins). */
|
|
118
|
+
this.admissionChains = new Map();
|
|
119
|
+
this.lastOutcomes = new Map();
|
|
120
|
+
this.updateChains = new Map();
|
|
121
|
+
this.dispatchChains = new Map();
|
|
122
|
+
this.backgroundWork = new Set();
|
|
123
|
+
this.approvals = new ApprovalGate();
|
|
124
|
+
/** Serve-time resolved connections (see {@link setResolvedConnections}). */
|
|
125
|
+
this.resolvedConnections = [];
|
|
126
|
+
this.project = options.project;
|
|
127
|
+
this.stateRoot = options.stateRoot;
|
|
128
|
+
this.runner = options.runner;
|
|
129
|
+
this.logger = (_b = options.logger) !== null && _b !== void 0 ? _b : (() => { });
|
|
130
|
+
this.sessions = new SessionStore(join(options.stateRoot, "sessions"));
|
|
131
|
+
this.logs = new EventLogStore((sessionId) => this.sessions.eventFilePath(sessionId), (sessionId, error) => this.logger(`[agent-serve] failed to persist events for ${sessionId}: ${describeError(error)}`));
|
|
132
|
+
this.abCollector = new ABCollector(options.project.abs, this.logger, (sessionId) => __awaiter(this, void 0, void 0, function* () { return (yield this.logs.get(sessionId)).snapshot(0); }), Object.assign({ projectRoot: options.project.rootDir }, (((_a = options.project.abConfig) === null || _a === void 0 ? void 0 : _a.persistSamples) === undefined
|
|
133
|
+
? {}
|
|
134
|
+
: { persistSamples: options.project.abConfig.persistSamples })));
|
|
135
|
+
// Peer and Cursor-account transports are symbolic until the serve host
|
|
136
|
+
// resolves them; the host-side MCP registry starts with the concrete
|
|
137
|
+
// connections and gains the rest via setResolvedConnections.
|
|
138
|
+
this.mcpHost = new McpHost(options.project.agent.connections.filter((connection) => !isSymbolicConnectionTransport(connection.transport)));
|
|
139
|
+
this.platforms = createHostPlatforms();
|
|
140
|
+
for (const channel of options.project.channels) {
|
|
141
|
+
this.channelsById.set(channel.id, channel.definition);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Attach serve-time resolved connections (peers, Cursor account bridge).
|
|
146
|
+
* They join the per-turn SDK `mcpServers` map (loopback URL for local
|
|
147
|
+
* turns, `cloudUrl` for cloud turns) and the host-side registry
|
|
148
|
+
* (`ctx.host.mcp`, always loopback).
|
|
149
|
+
*/
|
|
150
|
+
setResolvedConnections(connections) {
|
|
151
|
+
this.resolvedConnections = connections;
|
|
152
|
+
for (const connection of connections) {
|
|
153
|
+
this.mcpHost.addConnection({
|
|
154
|
+
name: connection.name,
|
|
155
|
+
transport: Object.assign({ url: connection.localUrl }, (connection.headers === undefined
|
|
156
|
+
? {}
|
|
157
|
+
: { headers: connection.headers })),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/** Shared host services (MCP / GitHub / Slack / reminders). */
|
|
162
|
+
get host() {
|
|
163
|
+
return {
|
|
164
|
+
mcp: this.mcpHost,
|
|
165
|
+
github: this.platforms.github,
|
|
166
|
+
slack: this.platforms.slack,
|
|
167
|
+
reminders: this.reminderApi,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** Attach the serve-host reminder API (called from startServer). */
|
|
171
|
+
setReminders(api) {
|
|
172
|
+
this.reminderApi = api;
|
|
173
|
+
}
|
|
174
|
+
// ==========================================================================
|
|
175
|
+
// Sending messages
|
|
176
|
+
// ==========================================================================
|
|
177
|
+
/**
|
|
178
|
+
* Start or resume a session on a channel. When `continuationToken`
|
|
179
|
+
* addresses an existing session this is a follow-up turn; otherwise a new
|
|
180
|
+
* session is created (keyed by the provided token, or a fresh one).
|
|
181
|
+
*/
|
|
182
|
+
send(channelId_1, message_1) {
|
|
183
|
+
return __awaiter(this, arguments, void 0, function* (channelId, message, options = {}) {
|
|
184
|
+
var _a, _b, _c;
|
|
185
|
+
const auth = (_a = options.auth) !== null && _a !== void 0 ? _a : null;
|
|
186
|
+
const existing = options.continuationToken === undefined
|
|
187
|
+
? undefined
|
|
188
|
+
: yield this.sessions.findByContinuation(channelId, options.continuationToken);
|
|
189
|
+
if (existing !== undefined) {
|
|
190
|
+
if (!samePrincipal(existing.auth, auth)) {
|
|
191
|
+
throw new SessionAuthError(existing.sessionId);
|
|
192
|
+
}
|
|
193
|
+
// Refresh harness cwd when the caller supplies a newer worktree (e.g. PR
|
|
194
|
+
// synchronize). Leave the durable continuation key unchanged.
|
|
195
|
+
let record = existing;
|
|
196
|
+
if (options.workspaceDir !== undefined &&
|
|
197
|
+
options.workspaceDir !== existing.workspaceOverride) {
|
|
198
|
+
record = yield this.updateRecord(existing.sessionId, (current) => { var _a; return (Object.assign(Object.assign({}, current), { workspaceOverride: options.workspaceDir, pendingWorkspaceFiles: (_a = options.workspaceFiles) !== null && _a !== void 0 ? _a : current.pendingWorkspaceFiles })); });
|
|
199
|
+
}
|
|
200
|
+
else if (options.workspaceFiles !== undefined) {
|
|
201
|
+
record = yield this.updateRecord(existing.sessionId, (current) => (Object.assign(Object.assign({}, current), { pendingWorkspaceFiles: options.workspaceFiles })));
|
|
202
|
+
}
|
|
203
|
+
yield this.startTurn(record.sessionId, message);
|
|
204
|
+
return this.channelSession(record.sessionId, record, false);
|
|
205
|
+
}
|
|
206
|
+
const now = new Date().toISOString();
|
|
207
|
+
const channelState = this.initialChannelState(channelId, options.state);
|
|
208
|
+
const purpose = options.purpose === "eval" ? "eval" : "live";
|
|
209
|
+
let record = {
|
|
210
|
+
sessionId: newSessionId(),
|
|
211
|
+
channelId,
|
|
212
|
+
mode: (_b = options.mode) !== null && _b !== void 0 ? _b : "chat",
|
|
213
|
+
purpose,
|
|
214
|
+
continuationKey: (_c = options.continuationToken) !== null && _c !== void 0 ? _c : newContinuationKey(),
|
|
215
|
+
auth,
|
|
216
|
+
title: options.title,
|
|
217
|
+
channelState,
|
|
218
|
+
workspaceOverride: options.workspaceDir,
|
|
219
|
+
pendingWorkspaceFiles: options.workspaceFiles,
|
|
220
|
+
cloudOverride: options.cloud,
|
|
221
|
+
sdkAgentId: options.sdkAgentId,
|
|
222
|
+
turnCount: 0,
|
|
223
|
+
createdAt: now,
|
|
224
|
+
updatedAt: now,
|
|
225
|
+
};
|
|
226
|
+
yield this.sessions.save(record);
|
|
227
|
+
yield this.appendEvent(record.sessionId, {
|
|
228
|
+
type: "session.started",
|
|
229
|
+
data: { channelId },
|
|
230
|
+
});
|
|
231
|
+
record = yield this.enrollABs(record);
|
|
232
|
+
yield this.startTurn(record.sessionId, message);
|
|
233
|
+
return this.channelSession(record.sessionId, record, true);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Sticky A/B enrollment at session creation — before the first turn, so
|
|
238
|
+
* variants are in the durable stream (and on {@link SessionRecord.abs})
|
|
239
|
+
* ahead of model/instructions work. Failures persist as `variant: null`
|
|
240
|
+
* (sticky skip). Eval harness sessions (`purpose: "eval"`) are never
|
|
241
|
+
* enrolled.
|
|
242
|
+
*/
|
|
243
|
+
enrollABs(record) {
|
|
244
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
245
|
+
if (!this.abCollector.enabled || record.purpose === "eval") {
|
|
246
|
+
return record;
|
|
247
|
+
}
|
|
248
|
+
const sessionInfo = this.sessionInfo(record);
|
|
249
|
+
const abs = {};
|
|
250
|
+
for (const experiment of this.project.abs) {
|
|
251
|
+
const def = experiment.definition;
|
|
252
|
+
const variantKeys = Object.keys(def.variants);
|
|
253
|
+
let variant = null;
|
|
254
|
+
try {
|
|
255
|
+
variant = yield resolveABSplit(def.split, {
|
|
256
|
+
agent: { name: this.project.name },
|
|
257
|
+
channel: { id: record.channelId },
|
|
258
|
+
session: sessionInfo,
|
|
259
|
+
experiment: experiment.name,
|
|
260
|
+
variants: variantKeys,
|
|
261
|
+
});
|
|
262
|
+
if (variant !== null && !(variant in def.variants)) {
|
|
263
|
+
throw new Error(`split resolved to ${JSON.stringify(variant)}; expected one of ${variantKeys.join(", ")} or null to skip`);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
this.logger(`[agent-serve] ab "${experiment.name}" enrollment failed (sticky skip): ${describeError(error)}`);
|
|
268
|
+
variant = null;
|
|
269
|
+
}
|
|
270
|
+
abs[experiment.name] = variant;
|
|
271
|
+
yield this.appendEvent(record.sessionId, {
|
|
272
|
+
type: "ab.assigned",
|
|
273
|
+
data: { experiment: experiment.name, variant },
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { abs })));
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Ensure {@link SessionRecord.abs} is populated (hydrate from the durable
|
|
281
|
+
* `ab.assigned` stream when missing — e.g. older sessions after upgrade).
|
|
282
|
+
*/
|
|
283
|
+
ensureAbs(record) {
|
|
284
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
285
|
+
if (record.abs !== undefined || record.purpose === "eval") {
|
|
286
|
+
return record;
|
|
287
|
+
}
|
|
288
|
+
if (!this.abCollector.enabled) {
|
|
289
|
+
return record;
|
|
290
|
+
}
|
|
291
|
+
const events = yield this.listEvents(record.sessionId, 0);
|
|
292
|
+
const folded = foldABStream(events);
|
|
293
|
+
if (folded.arms.size === 0) {
|
|
294
|
+
return record;
|
|
295
|
+
}
|
|
296
|
+
const abs = Object.fromEntries(folded.arms);
|
|
297
|
+
return this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { abs })));
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Agent config for this session: base project agent plus any per-variant
|
|
302
|
+
* `instructions` overlays from enrolled A/B arms.
|
|
303
|
+
*/
|
|
304
|
+
agentForSession(record) {
|
|
305
|
+
const base = this.project.agent;
|
|
306
|
+
const instructions = mergeABInstructions(base.instructions, record.abs, this.project.abs.map((experiment) => ({
|
|
307
|
+
name: experiment.name,
|
|
308
|
+
definition: experiment.definition,
|
|
309
|
+
})));
|
|
310
|
+
if (instructions === base.instructions) {
|
|
311
|
+
return base;
|
|
312
|
+
}
|
|
313
|
+
return Object.assign(Object.assign({}, base), { instructions });
|
|
314
|
+
}
|
|
315
|
+
/** Look up a session as a channel-scoped handle. */
|
|
316
|
+
getChannelSession(channelId, sessionId) {
|
|
317
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
318
|
+
const record = yield this.sessions.get(sessionId);
|
|
319
|
+
if (record === undefined || record.channelId !== channelId) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return this.channelSession(sessionId, record, false);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
/** Cross-channel hand-off (schedules and channel route handlers). */
|
|
326
|
+
receive(channel, input) {
|
|
327
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
328
|
+
const channelId = this.resolveChannelId(channel);
|
|
329
|
+
const definition = this.channelsById.get(channelId);
|
|
330
|
+
if ((definition === null || definition === void 0 ? void 0 : definition.receive) !== undefined) {
|
|
331
|
+
let lastCreated;
|
|
332
|
+
const send = (message_1, ...args_1) => __awaiter(this, [message_1, ...args_1], void 0, function* (message, options = {}) {
|
|
333
|
+
const session = yield this.send(channelId, message, options);
|
|
334
|
+
lastCreated = session;
|
|
335
|
+
return session;
|
|
336
|
+
});
|
|
337
|
+
const returned = yield definition.receive(input, { send });
|
|
338
|
+
const session = returned !== null && returned !== void 0 ? returned : lastCreated;
|
|
339
|
+
if (session === undefined) {
|
|
340
|
+
throw new Error(`Channel "${channelId}" receive hook did not start a session`);
|
|
341
|
+
}
|
|
342
|
+
return session;
|
|
343
|
+
}
|
|
344
|
+
return this.send(channelId, input.message, {
|
|
345
|
+
auth: input.auth,
|
|
346
|
+
});
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
/** Fire-and-forget task session (schedules' markdown form). */
|
|
350
|
+
runTask(args) {
|
|
351
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
352
|
+
const session = yield this.send(args.channelId, args.prompt, Object.assign({ auth: args.auth, mode: "task" }, (args.title === undefined ? {} : { title: args.title })));
|
|
353
|
+
return { sessionId: session.id, done: this.waitForCompletion(session.id) };
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
// ==========================================================================
|
|
357
|
+
// Turn execution
|
|
358
|
+
// ==========================================================================
|
|
359
|
+
/**
|
|
360
|
+
* Admit a turn for `sessionId`. A follow-up that arrives while a turn is in
|
|
361
|
+
* flight preempts it: the running turn is cancelled and its SDK run marked
|
|
362
|
+
* terminal before the new turn starts. Admissions are serialized per session
|
|
363
|
+
* so a burst of follow-ups can't race two runs onto the same SDK agent — the
|
|
364
|
+
* last message wins. Resolves once the new turn has started (not finished).
|
|
365
|
+
*/
|
|
366
|
+
startTurn(sessionId, message) {
|
|
367
|
+
var _a;
|
|
368
|
+
const prior = (_a = this.admissionChains.get(sessionId)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
369
|
+
const admitted = prior
|
|
370
|
+
.catch(() => { })
|
|
371
|
+
.then(() => this.admitTurn(sessionId, message));
|
|
372
|
+
this.admissionChains.set(sessionId, admitted.catch(() => { }));
|
|
373
|
+
return admitted;
|
|
374
|
+
}
|
|
375
|
+
admitTurn(sessionId, message) {
|
|
376
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
377
|
+
// Interrupt any turn in flight and wait for it to settle so the SDK run is
|
|
378
|
+
// terminal before this turn's send (the follow-up also force-expires as a
|
|
379
|
+
// belt-and-suspenders — see buildTurnRequest).
|
|
380
|
+
yield this.interruptActiveTurn(sessionId);
|
|
381
|
+
this.pendingTurns.add(sessionId);
|
|
382
|
+
try {
|
|
383
|
+
let record = yield this.updateRecord(sessionId, (current) => (Object.assign(Object.assign({}, current), { turnCount: current.turnCount + 1 })));
|
|
384
|
+
// Arms must be on the record before tools/instructions see SessionInfo.
|
|
385
|
+
record = yield this.ensureAbs(record);
|
|
386
|
+
const turnId = `turn_${record.turnCount}`;
|
|
387
|
+
const isFirstTurn = record.turnCount === 1;
|
|
388
|
+
yield this.appendEvent(sessionId, {
|
|
389
|
+
type: "message.received",
|
|
390
|
+
turnId,
|
|
391
|
+
data: { text: message },
|
|
392
|
+
});
|
|
393
|
+
yield this.appendEvent(sessionId, {
|
|
394
|
+
type: "turn.started",
|
|
395
|
+
turnId,
|
|
396
|
+
data: {},
|
|
397
|
+
});
|
|
398
|
+
const abort = new AbortController();
|
|
399
|
+
this.turnAborts.set(sessionId, abort);
|
|
400
|
+
const turnPromise = this.executeTurn(record, turnId, message, isFirstTurn, abort.signal);
|
|
401
|
+
this.activeTurns.set(sessionId, turnPromise);
|
|
402
|
+
void turnPromise
|
|
403
|
+
.then((outcome) => {
|
|
404
|
+
this.lastOutcomes.set(sessionId, outcome);
|
|
405
|
+
})
|
|
406
|
+
.catch(() => { })
|
|
407
|
+
.finally(() => {
|
|
408
|
+
// Guard against clobbering a newer turn that already replaced us.
|
|
409
|
+
if (this.activeTurns.get(sessionId) === turnPromise) {
|
|
410
|
+
this.activeTurns.delete(sessionId);
|
|
411
|
+
}
|
|
412
|
+
if (this.turnAborts.get(sessionId) === abort) {
|
|
413
|
+
this.turnAborts.delete(sessionId);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
finally {
|
|
418
|
+
this.pendingTurns.delete(sessionId);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Cancel the turn in flight for `sessionId` (if any) and wait for it to
|
|
424
|
+
* settle, so its SDK run is terminal before the next turn's send. Safe to
|
|
425
|
+
* call when nothing is running.
|
|
426
|
+
*/
|
|
427
|
+
interruptActiveTurn(sessionId) {
|
|
428
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
429
|
+
var _a;
|
|
430
|
+
(_a = this.turnAborts.get(sessionId)) === null || _a === void 0 ? void 0 : _a.abort();
|
|
431
|
+
const active = this.activeTurns.get(sessionId);
|
|
432
|
+
if (active !== undefined) {
|
|
433
|
+
yield active.catch(() => { });
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Abort the in-flight turn without starting a follow-up. Returns whether
|
|
439
|
+
* a turn was active (or pending abort) when called.
|
|
440
|
+
*/
|
|
441
|
+
interrupt(sessionId) {
|
|
442
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
443
|
+
const hadWork = this.turnAborts.has(sessionId) || this.activeTurns.has(sessionId);
|
|
444
|
+
yield this.interruptActiveTurn(sessionId);
|
|
445
|
+
return hadWork;
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Local harness cwd for a session: per-send override, else
|
|
450
|
+
* `<agent.local.cwd>/<sessionId>`, else the durable session workspace.
|
|
451
|
+
*/
|
|
452
|
+
harnessCwd(record) {
|
|
453
|
+
var _a;
|
|
454
|
+
if (record.workspaceOverride !== undefined) {
|
|
455
|
+
return record.workspaceOverride;
|
|
456
|
+
}
|
|
457
|
+
const agentCwd = (_a = this.project.agent.local) === null || _a === void 0 ? void 0 : _a.cwd;
|
|
458
|
+
if (agentCwd !== undefined) {
|
|
459
|
+
return join(agentCwd, record.sessionId);
|
|
460
|
+
}
|
|
461
|
+
return this.sessions.workspaceDir(record.sessionId);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Directory that receives AGENTS.md / skills / agent tools. When the
|
|
465
|
+
* harness cwd is a one-off override (e.g. PR worktree), scaffold stays in
|
|
466
|
+
* the durable session workspace; otherwise scaffold lands in the harness.
|
|
467
|
+
*/
|
|
468
|
+
scaffoldDir(record) {
|
|
469
|
+
if (record.workspaceOverride !== undefined) {
|
|
470
|
+
return this.sessions.workspaceDir(record.sessionId);
|
|
471
|
+
}
|
|
472
|
+
return this.harnessCwd(record);
|
|
473
|
+
}
|
|
474
|
+
executeTurn(record, turnId, message, isFirstTurn, signal) {
|
|
475
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
476
|
+
var _a, _b, _c, _d, _e;
|
|
477
|
+
const sessionId = record.sessionId;
|
|
478
|
+
const workspaceDir = this.scaffoldDir(record);
|
|
479
|
+
const harnessCwd = this.harnessCwd(record);
|
|
480
|
+
// Hold the open log so tool events append synchronously. Fire-and-forget
|
|
481
|
+
// `appendEvent` races parallel tool completions and delays the live stream
|
|
482
|
+
// until the next await tick — often after *all* parallel tools finish.
|
|
483
|
+
const log = yield this.logs.get(sessionId);
|
|
484
|
+
const emit = (payload) => {
|
|
485
|
+
try {
|
|
486
|
+
const event = log.append(Object.assign(Object.assign({}, payload), { turnId }));
|
|
487
|
+
this.queueDispatch(sessionId, event);
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
this.logger(`[agent-serve] failed to record event for ${sessionId}: ${describeError(error)}`);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
const mapper = new TurnEventMapper(emit);
|
|
494
|
+
let outcome;
|
|
495
|
+
try {
|
|
496
|
+
// Scaffold agent files into the session workspace even when the harness
|
|
497
|
+
// cwd is overridden (e.g. a PR worktree). A/B variant instruction
|
|
498
|
+
// overlays are applied via agentForSession.
|
|
499
|
+
const agent = this.agentForSession(record);
|
|
500
|
+
yield materializeWorkspace({
|
|
501
|
+
agent,
|
|
502
|
+
workspaceDir,
|
|
503
|
+
seed: isFirstTurn,
|
|
504
|
+
});
|
|
505
|
+
const pendingFiles = record.pendingWorkspaceFiles;
|
|
506
|
+
if (pendingFiles !== undefined && Object.keys(pendingFiles).length > 0) {
|
|
507
|
+
yield writeWorkspaceFiles(harnessCwd, pendingFiles);
|
|
508
|
+
yield this.updateRecord(sessionId, (current) => {
|
|
509
|
+
const { pendingWorkspaceFiles: _cleared } = current, rest = __rest(current, ["pendingWorkspaceFiles"]);
|
|
510
|
+
return rest;
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
const request = this.buildTurnRequest(record, agent, turnId, message, isFirstTurn, emit, (update) => __awaiter(this, void 0, void 0, function* () {
|
|
514
|
+
mapper.handleUpdate(update);
|
|
515
|
+
// Yield so the HTTP NDJSON writer can flush tool start/result
|
|
516
|
+
// events while sibling parallel tools are still executing.
|
|
517
|
+
if (update.type === "tool-call-started" ||
|
|
518
|
+
update.type === "tool-call-completed") {
|
|
519
|
+
yield yieldEventLoop();
|
|
520
|
+
}
|
|
521
|
+
}), signal);
|
|
522
|
+
outcome = yield this.runner.runTurn(request);
|
|
523
|
+
}
|
|
524
|
+
catch (error) {
|
|
525
|
+
this.approvals.cancelSession(sessionId, "deny");
|
|
526
|
+
outcome = { status: "error", errorMessage: describeError(error) };
|
|
527
|
+
}
|
|
528
|
+
finally {
|
|
529
|
+
this.approvals.cancelSession(sessionId, "deny");
|
|
530
|
+
yield this.clearPendingApprovals(sessionId);
|
|
531
|
+
}
|
|
532
|
+
mapper.finish();
|
|
533
|
+
if (mapper.title !== undefined) {
|
|
534
|
+
const title = mapper.title;
|
|
535
|
+
yield this.updateRecord(sessionId, (current) => (Object.assign(Object.assign({}, current), { title })));
|
|
536
|
+
}
|
|
537
|
+
const finalResult = (_a = outcome.result) !== null && _a !== void 0 ? _a : mapper.finalText;
|
|
538
|
+
const finalUsage = (_b = outcome.usage) !== null && _b !== void 0 ? _b : mapper.usage;
|
|
539
|
+
if (outcome.status === "finished") {
|
|
540
|
+
yield this.appendEvent(sessionId, {
|
|
541
|
+
type: "turn.completed",
|
|
542
|
+
turnId,
|
|
543
|
+
data: Object.assign(Object.assign({}, (finalResult === undefined ? {} : { result: finalResult })), (finalUsage === undefined ? {} : { usage: finalUsage })),
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
const interrupted = signal.aborted && outcome.status === "cancelled";
|
|
548
|
+
yield this.appendEvent(sessionId, {
|
|
549
|
+
type: "turn.failed",
|
|
550
|
+
turnId,
|
|
551
|
+
data: {
|
|
552
|
+
message: interrupted
|
|
553
|
+
? "turn interrupted"
|
|
554
|
+
: ((_c = outcome.errorMessage) !== null && _c !== void 0 ? _c : (outcome.status === "cancelled"
|
|
555
|
+
? "turn cancelled"
|
|
556
|
+
: "turn failed")),
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
const current = yield this.sessions.get(sessionId);
|
|
561
|
+
const mode = (_d = current === null || current === void 0 ? void 0 : current.mode) !== null && _d !== void 0 ? _d : record.mode;
|
|
562
|
+
if (mode === "task") {
|
|
563
|
+
yield this.appendEvent(sessionId, outcome.status === "finished"
|
|
564
|
+
? { type: "session.completed", turnId, data: {} }
|
|
565
|
+
: {
|
|
566
|
+
type: "session.failed",
|
|
567
|
+
turnId,
|
|
568
|
+
data: { message: (_e = outcome.errorMessage) !== null && _e !== void 0 ? _e : "task failed" },
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
yield this.appendEvent(sessionId, {
|
|
573
|
+
type: "session.waiting",
|
|
574
|
+
turnId,
|
|
575
|
+
data: {},
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return Object.assign(Object.assign(Object.assign({}, outcome), (finalResult === undefined ? {} : { result: finalResult })), (finalUsage === undefined ? {} : { usage: finalUsage }));
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
buildTurnRequest(record, agent, turnId, prompt, isFirstTurn, emit, onUpdate, signal) {
|
|
582
|
+
var _a;
|
|
583
|
+
var _b;
|
|
584
|
+
// Per-session cloud attach (channel/tool handoff) forces cloud runtime
|
|
585
|
+
// even when the agent defaults to local for chat server tools.
|
|
586
|
+
const runtime = resolveSessionRuntime({
|
|
587
|
+
agentRuntime: agent.runtime,
|
|
588
|
+
cloudOverride: record.cloudOverride,
|
|
589
|
+
});
|
|
590
|
+
const mergedCloud = runtime === "cloud"
|
|
591
|
+
? mergeCloudOptions({
|
|
592
|
+
base: agent.cloud,
|
|
593
|
+
override: record.cloudOverride,
|
|
594
|
+
})
|
|
595
|
+
: undefined;
|
|
596
|
+
let turnPrompt = prompt;
|
|
597
|
+
if (runtime === "cloud" && isFirstTurn) {
|
|
598
|
+
const preamble = [
|
|
599
|
+
(_b = (_a = agent.instructions) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : "",
|
|
600
|
+
buildAgentToolsCatalog(agent.tools, { includeScripts: true }),
|
|
601
|
+
]
|
|
602
|
+
.filter((part) => part !== "")
|
|
603
|
+
.join("\n\n");
|
|
604
|
+
turnPrompt = preamble === "" ? prompt : `${preamble}\n\n${prompt}`;
|
|
605
|
+
}
|
|
606
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId }, (record.sdkAgentId === undefined
|
|
607
|
+
? {}
|
|
608
|
+
: { sdkAgentId: record.sdkAgentId })), { workspaceDir: this.harnessCwd(record), runnerStateDir: join(this.stateRoot, "runner"), prompt: turnPrompt, isFirstTurn }), (agent.model === undefined ? {} : { model: agent.model })), { runtime }), (mergedCloud === undefined ? {} : { cloud: mergedCloud })), { customTools: runtime === "local" ? this.buildCustomTools(record, turnId, emit) : {}, mcpServers: this.buildMcpServers(runtime), subagents: agent.subagents.map((subagent) => { var _a; return (Object.assign({ name: subagent.name, description: (_a = subagent.description) !== null && _a !== void 0 ? _a : subagent.name, prompt: subagentPrompt(subagent) }, (subagent.model === undefined ? {} : { model: subagent.model }))); }), onUpdate,
|
|
609
|
+
signal,
|
|
610
|
+
// Local follow-ups expire any lingering active run so a preempted turn
|
|
611
|
+
// (or one orphaned by a crashed process) never blocks this send.
|
|
612
|
+
forceExpireActiveRun: runtime === "local" && !isFirstTurn, onSdkAgentId: (sdkAgentId) => __awaiter(this, void 0, void 0, function* () {
|
|
613
|
+
if (record.sdkAgentId === sdkAgentId) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
yield this.updateRecord(record.sessionId, (current) => (Object.assign(Object.assign({}, current), { sdkAgentId })));
|
|
617
|
+
record.sdkAgentId = sdkAgentId;
|
|
618
|
+
yield this.appendEvent(record.sessionId, {
|
|
619
|
+
type: "agent.bound",
|
|
620
|
+
turnId,
|
|
621
|
+
data: Object.assign({ sdkAgentId }, (sdkAgentId.startsWith("bc-")
|
|
622
|
+
? { url: `https://cursor.com/agents/${sdkAgentId}` }
|
|
623
|
+
: {})),
|
|
624
|
+
});
|
|
625
|
+
}) });
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* MCP servers for a turn: authored concrete connections plus resolved
|
|
629
|
+
* serve-time resolved connections (peers, Cursor account bridge).
|
|
630
|
+
* Resolved connections pick their URL by runtime — loopback for local
|
|
631
|
+
* turns, `cloudUrl` for cloud turns — and are omitted from cloud turns
|
|
632
|
+
* when no cloud URL exists unless a `servers` filter requires the bridge
|
|
633
|
+
* (see {@link assertCloudCanHonorAccountServersFilters}).
|
|
634
|
+
*/
|
|
635
|
+
buildMcpServers(runtime) {
|
|
636
|
+
assertCloudCanHonorAccountServersFilters({
|
|
637
|
+
runtime,
|
|
638
|
+
resolvedConnections: this.resolvedConnections,
|
|
639
|
+
});
|
|
640
|
+
const servers = {};
|
|
641
|
+
for (const connection of this.project.agent.connections) {
|
|
642
|
+
if (isSymbolicConnectionTransport(connection.transport)) {
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
servers[connection.name] = connection.transport;
|
|
646
|
+
}
|
|
647
|
+
for (const resolved of this.resolvedConnections) {
|
|
648
|
+
const url = runtime === "cloud" ? resolved.cloudUrl : resolved.localUrl;
|
|
649
|
+
if (url === undefined) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
servers[resolved.name] = Object.assign({ url }, (resolved.headers === undefined
|
|
653
|
+
? {}
|
|
654
|
+
: { headers: resolved.headers }));
|
|
655
|
+
}
|
|
656
|
+
return servers;
|
|
657
|
+
}
|
|
658
|
+
buildCustomTools(record, turnId, emit) {
|
|
659
|
+
const tools = {};
|
|
660
|
+
for (const tool of this.project.agent.tools) {
|
|
661
|
+
if (tool.execution !== "server") {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
const execute = tool.definition.execute;
|
|
665
|
+
if (typeof execute !== "function") {
|
|
666
|
+
continue;
|
|
667
|
+
}
|
|
668
|
+
tools[tool.name] = {
|
|
669
|
+
description: tool.description,
|
|
670
|
+
inputSchema: tool.inputSchema,
|
|
671
|
+
execute: (args, context) => __awaiter(this, void 0, void 0, function* () {
|
|
672
|
+
var _a;
|
|
673
|
+
const validation = validateToolInput(tool.definition.inputSchema, args);
|
|
674
|
+
if (!validation.ok) {
|
|
675
|
+
return {
|
|
676
|
+
content: [
|
|
677
|
+
{
|
|
678
|
+
type: "text",
|
|
679
|
+
text: `Invalid input for tool "${tool.name}": ${validation.message}`,
|
|
680
|
+
},
|
|
681
|
+
],
|
|
682
|
+
isError: true,
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
const toolCallId = (_a = context.toolCallId) !== null && _a !== void 0 ? _a : newToolCallId(tool.name);
|
|
686
|
+
const toolCtx = {
|
|
687
|
+
toolCallId,
|
|
688
|
+
session: this.sessionInfo(record),
|
|
689
|
+
workspaceDir: this.harnessCwd(record),
|
|
690
|
+
host: this.host,
|
|
691
|
+
send: (channelId, message, sendOptions = {}) => this.send(channelId, message, Object.assign(Object.assign({}, sendOptions), { auth: sendOptions.auth !== undefined
|
|
692
|
+
? sendOptions.auth
|
|
693
|
+
: record.auth })),
|
|
694
|
+
getSession: (channelId, sessionId) => this.getChannelSession(channelId, sessionId),
|
|
695
|
+
};
|
|
696
|
+
try {
|
|
697
|
+
const needsGate = yield evaluateNeedsApproval(tool.definition.needsApproval, validation.value);
|
|
698
|
+
if (needsGate) {
|
|
699
|
+
const decision = yield this.parkForApproval({
|
|
700
|
+
sessionId: record.sessionId,
|
|
701
|
+
turnId,
|
|
702
|
+
callId: toolCallId,
|
|
703
|
+
toolName: tool.name,
|
|
704
|
+
args: validation.value,
|
|
705
|
+
emit,
|
|
706
|
+
});
|
|
707
|
+
if (decision === "deny") {
|
|
708
|
+
return {
|
|
709
|
+
content: [
|
|
710
|
+
{
|
|
711
|
+
type: "text",
|
|
712
|
+
text: `Tool "${tool.name}" was denied by a human approver.`,
|
|
713
|
+
},
|
|
714
|
+
],
|
|
715
|
+
isError: true,
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const started = Date.now();
|
|
720
|
+
this.logger(`[session] tool ${tool.name} start`);
|
|
721
|
+
const result = yield execute(validation.value, toolCtx);
|
|
722
|
+
this.logger(`[session] tool ${tool.name} done (${Date.now() - started}ms)`);
|
|
723
|
+
return normalizeToolResult(result);
|
|
724
|
+
}
|
|
725
|
+
catch (error) {
|
|
726
|
+
this.logger(`[session] tool ${tool.name} failed: ${describeError(error)}`);
|
|
727
|
+
return {
|
|
728
|
+
content: [
|
|
729
|
+
{
|
|
730
|
+
type: "text",
|
|
731
|
+
text: `Tool "${tool.name}" failed: ${describeError(error)}`,
|
|
732
|
+
},
|
|
733
|
+
],
|
|
734
|
+
isError: true,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
}),
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
return tools;
|
|
741
|
+
}
|
|
742
|
+
/**
|
|
743
|
+
* Park a server-tool call until a human approves or denies. Emits
|
|
744
|
+
* `action.approval_requested` / `action.approval_resolved` on the
|
|
745
|
+
* held turn log so the playground and stream see the gate.
|
|
746
|
+
*/
|
|
747
|
+
parkForApproval(args) {
|
|
748
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
749
|
+
const requestedAt = new Date().toISOString();
|
|
750
|
+
const pendingRecord = {
|
|
751
|
+
callId: args.callId,
|
|
752
|
+
turnId: args.turnId,
|
|
753
|
+
toolName: args.toolName,
|
|
754
|
+
args: args.args,
|
|
755
|
+
requestedAt,
|
|
756
|
+
};
|
|
757
|
+
const resolutionPromise = this.approvals.wait(Object.assign({ sessionId: args.sessionId }, pendingRecord));
|
|
758
|
+
yield this.updateRecord(args.sessionId, (current) => { var _a; return (Object.assign(Object.assign({}, current), { pendingApprovals: [
|
|
759
|
+
...((_a = current.pendingApprovals) !== null && _a !== void 0 ? _a : []).filter((item) => item.callId !== args.callId),
|
|
760
|
+
pendingRecord,
|
|
761
|
+
] })); });
|
|
762
|
+
args.emit({
|
|
763
|
+
type: "action.approval_requested",
|
|
764
|
+
data: {
|
|
765
|
+
callId: args.callId,
|
|
766
|
+
toolName: args.toolName,
|
|
767
|
+
args: args.args,
|
|
768
|
+
},
|
|
769
|
+
});
|
|
770
|
+
this.logger(`[session] tool ${args.toolName} awaiting approval (${args.callId})`);
|
|
771
|
+
const { decision, by } = yield resolutionPromise;
|
|
772
|
+
yield this.updateRecord(args.sessionId, (current) => { var _a; return (Object.assign(Object.assign({}, current), { pendingApprovals: ((_a = current.pendingApprovals) !== null && _a !== void 0 ? _a : []).filter((item) => item.callId !== args.callId) })); });
|
|
773
|
+
args.emit({
|
|
774
|
+
type: "action.approval_resolved",
|
|
775
|
+
data: Object.assign({ callId: args.callId, toolName: args.toolName, decision }, (by === undefined ? {} : { by })),
|
|
776
|
+
});
|
|
777
|
+
this.logger(`[session] tool ${args.toolName} approval ${decision} (${args.callId})`);
|
|
778
|
+
return decision;
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
clearPendingApprovals(sessionId) {
|
|
782
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
783
|
+
const current = yield this.sessions.get(sessionId);
|
|
784
|
+
if (current === undefined ||
|
|
785
|
+
current.pendingApprovals === undefined ||
|
|
786
|
+
current.pendingApprovals.length === 0) {
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
yield this.updateRecord(sessionId, (record) => (Object.assign(Object.assign({}, record), { pendingApprovals: [] })));
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
/** Pending tool approvals for a session (live waiters ∪ persisted). */
|
|
793
|
+
listPendingApprovals(sessionId) {
|
|
794
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
795
|
+
var _a;
|
|
796
|
+
const record = yield this.sessions.get(sessionId);
|
|
797
|
+
if (record === undefined) {
|
|
798
|
+
return [];
|
|
799
|
+
}
|
|
800
|
+
const live = this.approvals.list(sessionId);
|
|
801
|
+
if (live.length > 0) {
|
|
802
|
+
return live;
|
|
803
|
+
}
|
|
804
|
+
return (_a = record.pendingApprovals) !== null && _a !== void 0 ? _a : [];
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Approve or deny a parked server-tool call. Unblocks the in-flight
|
|
809
|
+
* SDK custom-tool callback for the same `callId`.
|
|
810
|
+
*
|
|
811
|
+
* Pass `asSessionOwner: true` only from an explicit host bridge (e.g. `--dev`
|
|
812
|
+
* playground resolving a Slack-owned parked tool). Ownership is checked
|
|
813
|
+
* against the stored session principal; `by` still records the clicker.
|
|
814
|
+
*/
|
|
815
|
+
resolveApproval(sessionId, callId, decision, auth, options) {
|
|
816
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
817
|
+
var _a, _b;
|
|
818
|
+
const record = yield this.sessions.get(sessionId);
|
|
819
|
+
if (record === undefined) {
|
|
820
|
+
throw new ApprovalNotFoundError(sessionId, callId);
|
|
821
|
+
}
|
|
822
|
+
const ownershipAuth = (options === null || options === void 0 ? void 0 : options.asSessionOwner) === true ? record.auth : auth;
|
|
823
|
+
if (!samePrincipal(record.auth, ownershipAuth)) {
|
|
824
|
+
throw new SessionAuthError(sessionId);
|
|
825
|
+
}
|
|
826
|
+
const pending = (_b = ((_a = record.pendingApprovals) !== null && _a !== void 0 ? _a : []).find((item) => item.callId === callId)) !== null && _b !== void 0 ? _b : this.approvals.list(sessionId).find((item) => item.callId === callId);
|
|
827
|
+
if (pending === undefined) {
|
|
828
|
+
throw new ApprovalNotFoundError(sessionId, callId);
|
|
829
|
+
}
|
|
830
|
+
const resolvedBy = approvalResolverLabel(auth);
|
|
831
|
+
if (!this.approvals.resolve(sessionId, callId, decision, resolvedBy)) {
|
|
832
|
+
yield this.removePendingApproval(sessionId, callId);
|
|
833
|
+
throw new ApprovalInterruptedError(sessionId, callId);
|
|
834
|
+
}
|
|
835
|
+
return pending;
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
removePendingApproval(sessionId, callId) {
|
|
839
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
840
|
+
yield this.updateRecord(sessionId, (current) => { var _a; return (Object.assign(Object.assign({}, current), { pendingApprovals: ((_a = current.pendingApprovals) !== null && _a !== void 0 ? _a : []).filter((item) => item.callId !== callId) })); });
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
// ==========================================================================
|
|
844
|
+
// Direct tool calls (deterministic invocation, no model turn)
|
|
845
|
+
// ==========================================================================
|
|
846
|
+
/**
|
|
847
|
+
* Call an authored server tool deterministically: validate `input`
|
|
848
|
+
* against the tool's schema (zod schemas validate and transform; plain
|
|
849
|
+
* JSON Schema inputs pass through unvalidated, matching the model path),
|
|
850
|
+
* run `execute` in-process, and return the normalized result exactly as
|
|
851
|
+
* the model would receive it.
|
|
852
|
+
*
|
|
853
|
+
* With `options.sessionId` the tool runs inside that session (its info
|
|
854
|
+
* and materialized workspace) and the call is recorded on the session's
|
|
855
|
+
* event stream as `actions.requested` / `action.result` under a
|
|
856
|
+
* per-call `turnId` (the call id), so it shows up in trajectories.
|
|
857
|
+
* Rejected with {@link SessionBusyError} while a model turn is running.
|
|
858
|
+
* Without a session, the tool runs against an **ephemeral** scratch
|
|
859
|
+
* workspace under `<stateRoot>/tool-calls/<callId>` (materialized with
|
|
860
|
+
* seed files, removed after the call returns).
|
|
861
|
+
*
|
|
862
|
+
* Throws {@link UnknownToolError}, {@link ToolNotDirectlyCallableError},
|
|
863
|
+
* {@link ToolInputError}, or {@link UnknownSessionError} before the tool
|
|
864
|
+
* body runs; a tool body that throws is reported as an `isError` outcome
|
|
865
|
+
* with the same `{ content, isError }` envelope the model would see, not
|
|
866
|
+
* an exception.
|
|
867
|
+
*/
|
|
868
|
+
callTool(toolName_1) {
|
|
869
|
+
return __awaiter(this, arguments, void 0, function* (toolName, input = {}, options = {}) {
|
|
870
|
+
var _a;
|
|
871
|
+
const tool = this.project.agent.tools.find((t) => t.name === toolName);
|
|
872
|
+
if (tool === undefined) {
|
|
873
|
+
throw new UnknownToolError(toolName, this.project.agent.tools
|
|
874
|
+
.filter((t) => t.execution === "server")
|
|
875
|
+
.map((t) => t.name));
|
|
876
|
+
}
|
|
877
|
+
const execute = tool.definition.execute;
|
|
878
|
+
if (tool.execution !== "server" || typeof execute !== "function") {
|
|
879
|
+
throw new ToolNotDirectlyCallableError(toolName);
|
|
880
|
+
}
|
|
881
|
+
const validation = validateToolInput(tool.definition.inputSchema, input);
|
|
882
|
+
if (!validation.ok) {
|
|
883
|
+
throw new ToolInputError({ toolName, detail: validation.message });
|
|
884
|
+
}
|
|
885
|
+
const callId = newToolCallId(toolName);
|
|
886
|
+
const auth = (_a = options.auth) !== null && _a !== void 0 ? _a : null;
|
|
887
|
+
let record;
|
|
888
|
+
let session;
|
|
889
|
+
let workspaceDir;
|
|
890
|
+
let scratchDir;
|
|
891
|
+
if (options.sessionId !== undefined) {
|
|
892
|
+
record = yield this.sessions.get(options.sessionId);
|
|
893
|
+
if (record === undefined) {
|
|
894
|
+
throw new UnknownSessionError(options.sessionId);
|
|
895
|
+
}
|
|
896
|
+
// Direct calls respect the same serialization as follow-up sends: a
|
|
897
|
+
// running model turn owns the session workspace and event stream.
|
|
898
|
+
if (this.activeTurns.has(record.sessionId) ||
|
|
899
|
+
this.pendingTurns.has(record.sessionId)) {
|
|
900
|
+
throw new SessionBusyError(record.sessionId);
|
|
901
|
+
}
|
|
902
|
+
session = this.sessionInfo(record);
|
|
903
|
+
workspaceDir = this.harnessCwd(record);
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
session = {
|
|
907
|
+
id: callId,
|
|
908
|
+
channelId: DIRECT_TOOL_CALL_CHANNEL_ID,
|
|
909
|
+
mode: "task",
|
|
910
|
+
auth,
|
|
911
|
+
};
|
|
912
|
+
scratchDir = join(this.stateRoot, "tool-calls", callId);
|
|
913
|
+
workspaceDir = scratchDir;
|
|
914
|
+
}
|
|
915
|
+
yield mkdir(workspaceDir, { recursive: true });
|
|
916
|
+
// Match what a model turn would see: session workspaces get the same
|
|
917
|
+
// scaffold as executeTurn (seeds only before the first turn); scratch
|
|
918
|
+
// workspaces are materialized fresh.
|
|
919
|
+
yield materializeWorkspace({
|
|
920
|
+
agent: this.project.agent,
|
|
921
|
+
workspaceDir: record === undefined ? workspaceDir : this.scaffoldDir(record),
|
|
922
|
+
seed: record === undefined || record.turnCount === 0,
|
|
923
|
+
});
|
|
924
|
+
// A per-call turnId keeps these events visible in trajectories (which
|
|
925
|
+
// drop turn-less action events) without attributing them to a model turn.
|
|
926
|
+
if (record !== undefined) {
|
|
927
|
+
yield this.appendEvent(record.sessionId, {
|
|
928
|
+
type: "actions.requested",
|
|
929
|
+
turnId: callId,
|
|
930
|
+
data: { calls: [{ callId, toolName, args: validation.value }] },
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
const started = Date.now();
|
|
934
|
+
this.logger(`[session] tool ${toolName} start (direct ${callId})`);
|
|
935
|
+
let result;
|
|
936
|
+
let isError;
|
|
937
|
+
try {
|
|
938
|
+
result = normalizeToolResult(yield execute(validation.value, {
|
|
939
|
+
toolCallId: callId,
|
|
940
|
+
session,
|
|
941
|
+
workspaceDir,
|
|
942
|
+
host: this.host,
|
|
943
|
+
send: (channelId, message, sendOptions = {}) => this.send(channelId, message, Object.assign(Object.assign({}, sendOptions), { auth: sendOptions.auth !== undefined
|
|
944
|
+
? sendOptions.auth
|
|
945
|
+
: session.auth })),
|
|
946
|
+
getSession: (channelId, sessionId) => this.getChannelSession(channelId, sessionId),
|
|
947
|
+
}));
|
|
948
|
+
isError = isErrorEnvelope(result);
|
|
949
|
+
}
|
|
950
|
+
catch (error) {
|
|
951
|
+
this.logger(`[session] tool ${toolName} failed (direct ${callId}): ${describeError(error)}`);
|
|
952
|
+
// Same envelope the model path returns for a throwing tool body.
|
|
953
|
+
result = {
|
|
954
|
+
content: [
|
|
955
|
+
{
|
|
956
|
+
type: "text",
|
|
957
|
+
text: `Tool "${toolName}" failed: ${describeError(error)}`,
|
|
958
|
+
},
|
|
959
|
+
],
|
|
960
|
+
isError: true,
|
|
961
|
+
};
|
|
962
|
+
isError = true;
|
|
963
|
+
}
|
|
964
|
+
finally {
|
|
965
|
+
if (scratchDir !== undefined) {
|
|
966
|
+
yield rm(scratchDir, { recursive: true, force: true }).catch(() => { });
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
const durationMs = Date.now() - started;
|
|
970
|
+
this.logger(`[session] tool ${toolName} done (direct ${callId}, ${durationMs}ms)`);
|
|
971
|
+
if (record !== undefined) {
|
|
972
|
+
yield this.appendEvent(record.sessionId, {
|
|
973
|
+
type: "action.result",
|
|
974
|
+
turnId: callId,
|
|
975
|
+
data: { callId, toolName, output: result, isError },
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
const errorMessage = isError ? toolCallErrorMessage(result) : undefined;
|
|
979
|
+
return Object.assign(Object.assign({ toolName,
|
|
980
|
+
callId,
|
|
981
|
+
isError,
|
|
982
|
+
result,
|
|
983
|
+
durationMs }, (errorMessage === undefined ? {} : { errorMessage })), (record === undefined ? {} : { sessionId: record.sessionId }));
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
// ==========================================================================
|
|
987
|
+
// Events: append, dispatch to channel handlers + hooks
|
|
988
|
+
// ==========================================================================
|
|
989
|
+
appendEvent(sessionId, payload) {
|
|
990
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
991
|
+
const log = yield this.logs.get(sessionId);
|
|
992
|
+
const event = log.append(payload);
|
|
993
|
+
this.queueDispatch(sessionId, event);
|
|
994
|
+
return event;
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
queueDispatch(sessionId, event) {
|
|
998
|
+
var _a;
|
|
999
|
+
const chain = (_a = this.dispatchChains.get(sessionId)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
1000
|
+
const next = chain
|
|
1001
|
+
.then(() => this.dispatchEvent(sessionId, event))
|
|
1002
|
+
.catch((error) => {
|
|
1003
|
+
this.logger(`[agent-serve] event dispatch failed for ${sessionId}: ${describeError(error)}`);
|
|
1004
|
+
});
|
|
1005
|
+
this.dispatchChains.set(sessionId, next);
|
|
1006
|
+
}
|
|
1007
|
+
dispatchEvent(sessionId, event) {
|
|
1008
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1009
|
+
var _a;
|
|
1010
|
+
const record = yield this.sessions.get(sessionId);
|
|
1011
|
+
if (record === undefined) {
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
const sessionCtx = { session: this.sessionInfo(record) };
|
|
1015
|
+
const channelEvents = this.channelEventHandlersFor(record.channelId);
|
|
1016
|
+
if (channelEvents !== undefined) {
|
|
1017
|
+
// Handlers are declared against the narrowed event for their key; the
|
|
1018
|
+
// dispatch key always matches, so widening here is sound.
|
|
1019
|
+
const handlers = [
|
|
1020
|
+
channelEvents[event.type],
|
|
1021
|
+
channelEvents["*"],
|
|
1022
|
+
];
|
|
1023
|
+
const channelCtx = {
|
|
1024
|
+
channelId: record.channelId,
|
|
1025
|
+
state: ((_a = record.channelState) !== null && _a !== void 0 ? _a : null),
|
|
1026
|
+
continuationToken: record.continuationKey,
|
|
1027
|
+
setContinuationToken: (token) => this.setContinuationToken(sessionId, token),
|
|
1028
|
+
};
|
|
1029
|
+
for (const handler of handlers) {
|
|
1030
|
+
if (handler === undefined) {
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
try {
|
|
1034
|
+
yield handler(event, channelCtx, sessionCtx);
|
|
1035
|
+
}
|
|
1036
|
+
catch (error) {
|
|
1037
|
+
this.logger(`[agent-serve] channel "${record.channelId}" handler for ${event.type} threw: ${describeError(error)}`);
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
// Handlers mutate channel state in place; persist after each batch.
|
|
1041
|
+
yield this.updateRecord(sessionId, (current) => (Object.assign({}, current)));
|
|
1042
|
+
}
|
|
1043
|
+
for (const hook of this.project.hooks) {
|
|
1044
|
+
const handlers = [
|
|
1045
|
+
hook.definition.events[event.type],
|
|
1046
|
+
hook.definition.events["*"],
|
|
1047
|
+
];
|
|
1048
|
+
for (const handler of handlers) {
|
|
1049
|
+
if (handler === undefined) {
|
|
1050
|
+
continue;
|
|
1051
|
+
}
|
|
1052
|
+
try {
|
|
1053
|
+
yield handler(event, {
|
|
1054
|
+
agent: { name: this.project.name },
|
|
1055
|
+
channel: {
|
|
1056
|
+
id: record.channelId,
|
|
1057
|
+
continuationToken: record.continuationKey,
|
|
1058
|
+
},
|
|
1059
|
+
session: this.sessionInfo(record),
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
catch (error) {
|
|
1063
|
+
this.logger(`[agent-serve] hook "${hook.name}" handler for ${event.type} threw: ${describeError(error)}`);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (this.abCollector.enabled) {
|
|
1068
|
+
yield this.abCollector.onEvent(event, this.sessionInfo(record), this.project.name);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
}
|
|
1072
|
+
channelEventHandlersFor(channelId) {
|
|
1073
|
+
var _a;
|
|
1074
|
+
if (channelId === HTTP_CHANNEL_ID) {
|
|
1075
|
+
return this.project.httpChannel.events;
|
|
1076
|
+
}
|
|
1077
|
+
return (_a = this.channelsById.get(channelId)) === null || _a === void 0 ? void 0 : _a.events;
|
|
1078
|
+
}
|
|
1079
|
+
// ==========================================================================
|
|
1080
|
+
// Session handles
|
|
1081
|
+
// ==========================================================================
|
|
1082
|
+
waitForCompletion(sessionId) {
|
|
1083
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1084
|
+
var _a;
|
|
1085
|
+
// Drain admission first so pendingTurns → activeTurns is visible before we
|
|
1086
|
+
// decide the session is idle (reminders use this for queueBehindTurn).
|
|
1087
|
+
const chain = this.admissionChains.get(sessionId);
|
|
1088
|
+
if (chain !== undefined) {
|
|
1089
|
+
yield chain.catch(() => { });
|
|
1090
|
+
}
|
|
1091
|
+
const active = this.activeTurns.get(sessionId);
|
|
1092
|
+
if (active !== undefined) {
|
|
1093
|
+
return active;
|
|
1094
|
+
}
|
|
1095
|
+
return (_a = this.lastOutcomes.get(sessionId)) !== null && _a !== void 0 ? _a : { status: "finished" };
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Sessions visible to `caller`, newest first (`GET /v1/sessions`).
|
|
1100
|
+
* Callers only see sessions whose stored principal matches — except when
|
|
1101
|
+
* `includeAll` is set (dev mode), where the listing becomes a full
|
|
1102
|
+
* observability surface across every principal (channel-driven runs,
|
|
1103
|
+
* schedule/task sessions, other callers). The route auth chain still
|
|
1104
|
+
* gates who may call this at all.
|
|
1105
|
+
*/
|
|
1106
|
+
listSessions(caller, options) {
|
|
1107
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1108
|
+
var _a;
|
|
1109
|
+
const includeAll = (_a = options === null || options === void 0 ? void 0 : options.includeAll) !== null && _a !== void 0 ? _a : false;
|
|
1110
|
+
const records = yield this.sessions.list();
|
|
1111
|
+
return records
|
|
1112
|
+
.filter((record) => includeAll || samePrincipal(record.auth, caller))
|
|
1113
|
+
.map((record) => {
|
|
1114
|
+
const externalUrl = externalUrlForSession(record);
|
|
1115
|
+
return Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId, channelId: record.channelId, mode: record.mode }, (record.title === undefined ? {} : { title: record.title })), { turnCount: record.turnCount, running: this.activeTurns.has(record.sessionId) ||
|
|
1116
|
+
this.pendingTurns.has(record.sessionId), createdAt: record.createdAt, updatedAt: record.updatedAt, continuationToken: record.continuationKey === null
|
|
1117
|
+
? null
|
|
1118
|
+
: formatContinuationToken(record.channelId, record.continuationKey) }), (externalUrl === undefined ? {} : { externalUrl }));
|
|
1119
|
+
})
|
|
1120
|
+
.reverse();
|
|
1121
|
+
});
|
|
1122
|
+
}
|
|
1123
|
+
/**
|
|
1124
|
+
* Fold durable session streams into per-session A/B assignments and
|
|
1125
|
+
* aggregate arm metrics (`GET /v1/abs`). Collector-independent: always
|
|
1126
|
+
* reads `events.ndjson`.
|
|
1127
|
+
*/
|
|
1128
|
+
abSnapshot(caller, options) {
|
|
1129
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1130
|
+
const sessions = yield this.listSessions(caller, options);
|
|
1131
|
+
const abConfig = this.project.abConfig;
|
|
1132
|
+
const maxPlaygroundSessions = resolveABMaxPlaygroundSessions(abConfig === null || abConfig === void 0 ? void 0 : abConfig.maxPlaygroundSessions);
|
|
1133
|
+
const foldedSessions = sessions.slice(0, maxPlaygroundSessions);
|
|
1134
|
+
const snapshot = yield buildABSnapshot({
|
|
1135
|
+
experiments: this.project.abs,
|
|
1136
|
+
agentName: this.project.name,
|
|
1137
|
+
sessions: foldedSessions,
|
|
1138
|
+
loadRecord: (sessionId) => this.sessions.get(sessionId),
|
|
1139
|
+
loadEvents: (sessionId) => this.listEvents(sessionId, 0),
|
|
1140
|
+
sessionInfo: (record) => this.sessionInfo(record),
|
|
1141
|
+
logger: this.logger,
|
|
1142
|
+
config: {
|
|
1143
|
+
maxPlaygroundSessions,
|
|
1144
|
+
durableSamples: (abConfig === null || abConfig === void 0 ? void 0 : abConfig.persistSamples) !== undefined,
|
|
1145
|
+
durableSnapshots: (abConfig === null || abConfig === void 0 ? void 0 : abConfig.persistSnapshots) !== undefined,
|
|
1146
|
+
},
|
|
1147
|
+
});
|
|
1148
|
+
// Side effect on the read path: playground polls GET /v1/abs ~every 4s.
|
|
1149
|
+
// Authors who set persistSnapshots accept that write cadence.
|
|
1150
|
+
const persistSnapshots = abConfig === null || abConfig === void 0 ? void 0 : abConfig.persistSnapshots;
|
|
1151
|
+
if (persistSnapshots !== undefined) {
|
|
1152
|
+
try {
|
|
1153
|
+
yield persistSnapshots.save(snapshot, {
|
|
1154
|
+
projectRoot: this.project.rootDir,
|
|
1155
|
+
});
|
|
1156
|
+
}
|
|
1157
|
+
catch (error) {
|
|
1158
|
+
this.logger(`[agent-serve] ab persistSnapshots.save threw: ${describeError(error)}`);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return snapshot;
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
eventStream(sessionId_1) {
|
|
1165
|
+
return __awaiter(this, arguments, void 0, function* (sessionId, startIndex = 0) {
|
|
1166
|
+
const log = yield this.logs.get(sessionId);
|
|
1167
|
+
return log.stream(startIndex);
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Append an assistant message to a session transcript without a model turn
|
|
1172
|
+
* and without a user `message.received` bubble. Used by host-driven channel
|
|
1173
|
+
* tasks (e.g. webhook stages) to publish a final summary.
|
|
1174
|
+
*/
|
|
1175
|
+
emitAssistantMessage(sessionId, text) {
|
|
1176
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1177
|
+
if (this.isTurnRunning(sessionId)) {
|
|
1178
|
+
throw new SessionBusyError(sessionId);
|
|
1179
|
+
}
|
|
1180
|
+
const existing = yield this.sessions.get(sessionId);
|
|
1181
|
+
if (existing === undefined) {
|
|
1182
|
+
throw new Error(`Unknown session: ${sessionId}`);
|
|
1183
|
+
}
|
|
1184
|
+
const record = yield this.updateRecord(sessionId, (current) => (Object.assign(Object.assign({}, current), { turnCount: current.turnCount + 1 })));
|
|
1185
|
+
const turnId = `turn_${record.turnCount}`;
|
|
1186
|
+
yield this.appendEvent(sessionId, {
|
|
1187
|
+
type: "turn.started",
|
|
1188
|
+
turnId,
|
|
1189
|
+
data: {},
|
|
1190
|
+
});
|
|
1191
|
+
yield this.appendEvent(sessionId, {
|
|
1192
|
+
type: "message.completed",
|
|
1193
|
+
turnId,
|
|
1194
|
+
data: { text, finishReason: "stop" },
|
|
1195
|
+
});
|
|
1196
|
+
yield this.appendEvent(sessionId, {
|
|
1197
|
+
type: "turn.completed",
|
|
1198
|
+
turnId,
|
|
1199
|
+
data: { result: text },
|
|
1200
|
+
});
|
|
1201
|
+
yield this.appendEvent(sessionId, {
|
|
1202
|
+
type: "session.waiting",
|
|
1203
|
+
data: {},
|
|
1204
|
+
});
|
|
1205
|
+
this.lastOutcomes.set(sessionId, {
|
|
1206
|
+
status: "finished",
|
|
1207
|
+
result: text,
|
|
1208
|
+
});
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
/** Snapshot of recorded events (for download / offline trace). */
|
|
1212
|
+
listEvents(sessionId_1) {
|
|
1213
|
+
return __awaiter(this, arguments, void 0, function* (sessionId, startIndex = 0) {
|
|
1214
|
+
const log = yield this.logs.get(sessionId);
|
|
1215
|
+
return log.snapshot(startIndex);
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
setContinuationToken(sessionId, token) {
|
|
1219
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1220
|
+
yield this.updateRecord(sessionId, (current) => (Object.assign(Object.assign({}, current), { continuationKey: token })));
|
|
1221
|
+
});
|
|
1222
|
+
}
|
|
1223
|
+
isTurnRunning(sessionId) {
|
|
1224
|
+
// Include the admit window (pendingTurns) so busy policies do not treat a
|
|
1225
|
+
// turn that has been accepted but not yet marked active as idle.
|
|
1226
|
+
return this.activeTurns.has(sessionId) || this.pendingTurns.has(sessionId);
|
|
1227
|
+
}
|
|
1228
|
+
sessionInfo(record) {
|
|
1229
|
+
return Object.assign(Object.assign(Object.assign({ id: record.sessionId, channelId: record.channelId, mode: record.mode, purpose: record.purpose === "eval" ? "eval" : "live", auth: record.auth, continuationKey: record.continuationKey }, (record.abs === undefined ? {} : { abs: record.abs })), (record.title === undefined ? {} : { title: record.title })), (record.sdkAgentId === undefined
|
|
1230
|
+
? {}
|
|
1231
|
+
: { sdkAgentId: record.sdkAgentId }));
|
|
1232
|
+
}
|
|
1233
|
+
channelSession(sessionId, record, isNew) {
|
|
1234
|
+
return {
|
|
1235
|
+
id: sessionId,
|
|
1236
|
+
continuationToken: record.continuationKey,
|
|
1237
|
+
isNew,
|
|
1238
|
+
info: this.sessionInfo(record),
|
|
1239
|
+
getEventStream: (options) => { var _a; return this.eventStream(sessionId, (_a = options === null || options === void 0 ? void 0 : options.startIndex) !== null && _a !== void 0 ? _a : 0); },
|
|
1240
|
+
waitForCompletion: () => this.waitForCompletion(sessionId),
|
|
1241
|
+
setContinuationToken: (token) => this.setContinuationToken(sessionId, token),
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
resolveChannelId(channel) {
|
|
1245
|
+
if (typeof channel === "string") {
|
|
1246
|
+
if (channel !== HTTP_CHANNEL_ID &&
|
|
1247
|
+
!this.channelsById.has(channel) &&
|
|
1248
|
+
!channel.startsWith(SCHEDULE_CHANNEL_PREFIX)) {
|
|
1249
|
+
throw new UnknownChannelError(channel);
|
|
1250
|
+
}
|
|
1251
|
+
return channel;
|
|
1252
|
+
}
|
|
1253
|
+
if (channel.__agentServe === "http-channel") {
|
|
1254
|
+
return HTTP_CHANNEL_ID;
|
|
1255
|
+
}
|
|
1256
|
+
for (const [id, definition] of this.channelsById) {
|
|
1257
|
+
if (definition === channel) {
|
|
1258
|
+
return id;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
throw new UnknownChannelError("<unregistered channel definition>");
|
|
1262
|
+
}
|
|
1263
|
+
initialChannelState(channelId, override) {
|
|
1264
|
+
if (override !== undefined) {
|
|
1265
|
+
return structuredClone(override);
|
|
1266
|
+
}
|
|
1267
|
+
const definition = this.channelsById.get(channelId);
|
|
1268
|
+
if ((definition === null || definition === void 0 ? void 0 : definition.state) === undefined) {
|
|
1269
|
+
return undefined;
|
|
1270
|
+
}
|
|
1271
|
+
return structuredClone(definition.state);
|
|
1272
|
+
}
|
|
1273
|
+
/** Serialized, immutable record updates (latest-wins per session). */
|
|
1274
|
+
updateRecord(sessionId, updater) {
|
|
1275
|
+
var _a;
|
|
1276
|
+
const chain = (_a = this.updateChains.get(sessionId)) !== null && _a !== void 0 ? _a : Promise.resolve();
|
|
1277
|
+
const next = chain.then(() => __awaiter(this, void 0, void 0, function* () {
|
|
1278
|
+
const current = yield this.sessions.get(sessionId);
|
|
1279
|
+
if (current === undefined) {
|
|
1280
|
+
throw new Error(`Session ${sessionId} not found`);
|
|
1281
|
+
}
|
|
1282
|
+
const updated = Object.assign(Object.assign({}, updater(current)), { updatedAt: new Date().toISOString() });
|
|
1283
|
+
yield this.sessions.save(updated);
|
|
1284
|
+
return updated;
|
|
1285
|
+
}));
|
|
1286
|
+
this.updateChains.set(sessionId, next.catch(() => { }));
|
|
1287
|
+
return next;
|
|
1288
|
+
}
|
|
1289
|
+
// ==========================================================================
|
|
1290
|
+
// Lifecycle
|
|
1291
|
+
// ==========================================================================
|
|
1292
|
+
trackBackground(promise) {
|
|
1293
|
+
this.backgroundWork.add(promise);
|
|
1294
|
+
void promise
|
|
1295
|
+
.catch(() => { })
|
|
1296
|
+
.finally(() => {
|
|
1297
|
+
this.backgroundWork.delete(promise);
|
|
1298
|
+
});
|
|
1299
|
+
}
|
|
1300
|
+
/** Drain running turns, dispatches, and pending writes, then dispose. */
|
|
1301
|
+
close() {
|
|
1302
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1303
|
+
var _a, _b;
|
|
1304
|
+
this.approvals.cancelAll("deny");
|
|
1305
|
+
yield Promise.allSettled([...this.activeTurns.values()]);
|
|
1306
|
+
yield Promise.allSettled([...this.backgroundWork]);
|
|
1307
|
+
yield Promise.allSettled([...this.dispatchChains.values()]);
|
|
1308
|
+
yield Promise.allSettled([...this.updateChains.values()]);
|
|
1309
|
+
yield this.logs.flushAll();
|
|
1310
|
+
yield this.mcpHost.close();
|
|
1311
|
+
yield (_b = (_a = this.runner).dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
1312
|
+
});
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
function subagentPrompt(subagent) {
|
|
1316
|
+
var _a;
|
|
1317
|
+
if (subagent.instructions !== undefined && subagent.instructions !== "") {
|
|
1318
|
+
return subagent.instructions;
|
|
1319
|
+
}
|
|
1320
|
+
return (_a = subagent.description) !== null && _a !== void 0 ? _a : subagent.name;
|
|
1321
|
+
}
|
|
1322
|
+
function describeError(error) {
|
|
1323
|
+
return error instanceof Error ? error.message : String(error);
|
|
1324
|
+
}
|
|
1325
|
+
/** Detects the `{ content, isError: true }` rich-result error envelope. */
|
|
1326
|
+
function isErrorEnvelope(result) {
|
|
1327
|
+
return (typeof result === "object" &&
|
|
1328
|
+
result !== null &&
|
|
1329
|
+
!Array.isArray(result) &&
|
|
1330
|
+
Array.isArray(result.content) &&
|
|
1331
|
+
result.isError === true);
|
|
1332
|
+
}
|
|
1333
|
+
function evaluateNeedsApproval(needsApproval, input) {
|
|
1334
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1335
|
+
if (needsApproval === undefined || needsApproval === false) {
|
|
1336
|
+
return false;
|
|
1337
|
+
}
|
|
1338
|
+
if (needsApproval === true) {
|
|
1339
|
+
return true;
|
|
1340
|
+
}
|
|
1341
|
+
return Boolean(yield needsApproval(input));
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
function approvalResolverLabel(auth) {
|
|
1345
|
+
var _a;
|
|
1346
|
+
if (auth === null) {
|
|
1347
|
+
return undefined;
|
|
1348
|
+
}
|
|
1349
|
+
const slackUser = (_a = auth.attributes) === null || _a === void 0 ? void 0 : _a.user_id;
|
|
1350
|
+
if (auth.authenticator === "slack" &&
|
|
1351
|
+
slackUser !== undefined &&
|
|
1352
|
+
slackUser !== "") {
|
|
1353
|
+
return slackUser;
|
|
1354
|
+
}
|
|
1355
|
+
return auth.principalId;
|
|
1356
|
+
}
|
|
1357
|
+
/** Deep link to the external conversation (Slack thread today). */
|
|
1358
|
+
function externalUrlForSession(record) {
|
|
1359
|
+
var _a, _b, _c;
|
|
1360
|
+
var _d, _e;
|
|
1361
|
+
const auth = record.auth;
|
|
1362
|
+
if (auth === null || auth.authenticator !== "slack") {
|
|
1363
|
+
return undefined;
|
|
1364
|
+
}
|
|
1365
|
+
const channelId = (_d = (_a = auth.attributes) === null || _a === void 0 ? void 0 : _a.channel_id) !== null && _d !== void 0 ? _d : "";
|
|
1366
|
+
const threadTs = (_e = (_b = auth.attributes) === null || _b === void 0 ? void 0 : _b.thread_ts) !== null && _e !== void 0 ? _e : "";
|
|
1367
|
+
const teamId = (_c = auth.attributes) === null || _c === void 0 ? void 0 : _c.team_id;
|
|
1368
|
+
const url = slackThreadPermalink(Object.assign({ channelId,
|
|
1369
|
+
threadTs }, (teamId === undefined ? {} : { teamId })));
|
|
1370
|
+
return url !== null && url !== void 0 ? url : undefined;
|
|
1371
|
+
}
|
|
1372
|
+
/** Let the HTTP NDJSON writer flush before the next SDK update. */
|
|
1373
|
+
function yieldEventLoop() {
|
|
1374
|
+
return new Promise((resolve) => {
|
|
1375
|
+
setImmediate(resolve);
|
|
1376
|
+
});
|
|
1377
|
+
}
|