@wrongstack/core 0.313.0 → 0.316.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/dist/chronicle/index.js +84 -6391
- package/dist/chronicle/legacy-journal-import.d.ts +0 -6
- package/dist/chronicle/metrics-ingest.d.ts +14 -0
- package/dist/chronicle/metrics-schema.d.ts +10 -1
- package/dist/chronicle/metrics-store.d.ts +20 -2
- package/dist/chronicle/project-server.js +98 -4874
- package/dist/chronicle/sink.d.ts +13 -0
- package/dist/chronicle/sqlite-journal.d.ts +20 -2
- package/dist/chunk-25LSLC3G.js +334 -0
- package/dist/chunk-27QIURLN.js +1073 -0
- package/dist/chunk-2FK4WTFF.js +207 -0
- package/dist/chunk-2YU4WVBW.js +134 -0
- package/dist/chunk-3AGWVSS7.js +4608 -0
- package/dist/chunk-3BDV7RL3.js +211 -0
- package/dist/chunk-3ECENVHL.js +325 -0
- package/dist/chunk-3XPVB7BA.js +135 -0
- package/dist/chunk-45WI72ZB.js +16884 -0
- package/dist/chunk-4BMVPOHO.js +3748 -0
- package/dist/chunk-4I7HGVVA.js +363 -0
- package/dist/chunk-4LCZVJVF.js +317 -0
- package/dist/chunk-4SJNS7OB.js +217 -0
- package/dist/chunk-4VWO3ZXL.js +342 -0
- package/dist/chunk-57GEWK5X.js +26 -0
- package/dist/chunk-5BMIE7QG.js +46 -0
- package/dist/chunk-5S437PRC.js +50 -0
- package/dist/chunk-5XDUGTKN.js +511 -0
- package/dist/chunk-5YPSXQXM.js +1443 -0
- package/dist/chunk-6FHWXLPT.js +10 -0
- package/dist/chunk-6H5GEOET.js +2406 -0
- package/dist/chunk-6NWEVFEL.js +151 -0
- package/dist/chunk-AYW5YINX.js +58 -0
- package/dist/chunk-B4AMLZPB.js +9 -0
- package/dist/chunk-B7F4WVP4.js +65 -0
- package/dist/chunk-BJHVULHO.js +55 -0
- package/dist/chunk-BPBKKBUK.js +2085 -0
- package/dist/chunk-BYD345OX.js +589 -0
- package/dist/chunk-C27KDLOX.js +279 -0
- package/dist/chunk-CU546ESW.js +597 -0
- package/dist/chunk-DLHHXUAM.js +172 -0
- package/dist/chunk-DOTHQSJ2.js +429 -0
- package/dist/chunk-DYLPMV4Q.js +711 -0
- package/dist/chunk-EODDVJBM.js +82 -0
- package/dist/chunk-F3JWNST7.js +361 -0
- package/dist/chunk-FC2ZGRF3.js +140 -0
- package/dist/chunk-FCBTO76Z.js +5786 -0
- package/dist/chunk-FHZVFYSL.js +396 -0
- package/dist/chunk-FNXVT7HF.js +52 -0
- package/dist/chunk-FWNLEZYG.js +259 -0
- package/dist/chunk-FXQLIX3K.js +211 -0
- package/dist/chunk-G5S3S5ZN.js +38 -0
- package/dist/chunk-GDQ7CZRQ.js +116 -0
- package/dist/chunk-GJJGJB2R.js +1374 -0
- package/dist/chunk-GTCU6DZ3.js +1 -0
- package/dist/chunk-HA4ZYA6T.js +178 -0
- package/dist/chunk-HI3JGZR2.js +188 -0
- package/dist/chunk-HMLIGAU7.js +395 -0
- package/dist/chunk-HXKZ7OBQ.js +303 -0
- package/dist/chunk-I7DALG7J.js +292 -0
- package/dist/chunk-IRUTEHCE.js +960 -0
- package/dist/chunk-IYFBFX34.js +86 -0
- package/dist/chunk-J2WRWR37.js +1104 -0
- package/dist/chunk-JFPJ6LLQ.js +129 -0
- package/dist/chunk-JMULFFLN.js +9 -0
- package/dist/chunk-JO4MGB6Z.js +1264 -0
- package/dist/chunk-JVKB2TBL.js +48 -0
- package/dist/chunk-JX7VBZDK.js +31 -0
- package/dist/chunk-K6BXGITA.js +931 -0
- package/dist/chunk-KDQJCT2J.js +45 -0
- package/dist/chunk-KJ2ZTQSL.js +47 -0
- package/dist/chunk-KZMSZV32.js +187 -0
- package/dist/chunk-LA7CLDRW.js +129 -0
- package/dist/chunk-LHOA5DWA.js +28 -0
- package/dist/chunk-LSMMOS2Z.js +119 -0
- package/dist/chunk-LX5QWLBL.js +27 -0
- package/dist/chunk-LXWCMQNJ.js +789 -0
- package/dist/chunk-M27LIEKV.js +2829 -0
- package/dist/chunk-MJPHJRAL.js +663 -0
- package/dist/chunk-MTL4O5YR.js +228 -0
- package/dist/chunk-NDBIPFNY.js +102 -0
- package/dist/chunk-NEMIWEXC.js +1199 -0
- package/dist/chunk-NHJ75HXY.js +1 -0
- package/dist/chunk-NIOAGFPB.js +1510 -0
- package/dist/chunk-NS52ISWS.js +355 -0
- package/dist/chunk-NSOFPKGT.js +5352 -0
- package/dist/chunk-NVIZG63R.js +757 -0
- package/dist/chunk-O37BQ65S.js +21 -0
- package/dist/chunk-OOYZPOXM.js +308 -0
- package/dist/chunk-OSGLYW5J.js +28 -0
- package/dist/chunk-PFCMSSUF.js +1640 -0
- package/dist/chunk-PFZO3C6H.js +14 -0
- package/dist/chunk-PGRVXIMY.js +158 -0
- package/dist/chunk-PHH7LD45.js +113 -0
- package/dist/chunk-PISBFBJN.js +109 -0
- package/dist/chunk-POOOIWE5.js +9 -0
- package/dist/chunk-PQNS3U7Z.js +1240 -0
- package/dist/chunk-QSTSJOCK.js +402 -0
- package/dist/chunk-RFQMOKWK.js +282 -0
- package/dist/chunk-RJIZEJ42.js +30 -0
- package/dist/chunk-RTOTNK3P.js +484 -0
- package/dist/chunk-RXKSDUAU.js +1172 -0
- package/dist/chunk-S45JPKQN.js +3317 -0
- package/dist/chunk-SAFHACFA.js +152 -0
- package/dist/chunk-SQJVFEBV.js +41 -0
- package/dist/chunk-SVT4RNTR.js +49 -0
- package/dist/chunk-SWUFN7XM.js +43 -0
- package/dist/chunk-SXMVYC5H.js +174 -0
- package/dist/chunk-SZ6AYUIZ.js +23 -0
- package/dist/chunk-TBRL2RCC.js +9 -0
- package/dist/chunk-TCCHINEF.js +655 -0
- package/dist/chunk-THEGTRAG.js +316 -0
- package/dist/chunk-TR4LQE7L.js +2256 -0
- package/dist/chunk-TRT2HRWZ.js +18 -0
- package/dist/chunk-TWJMGXCI.js +900 -0
- package/dist/chunk-U45AXT5D.js +1023 -0
- package/dist/chunk-U4FWBSO3.js +509 -0
- package/dist/chunk-UBGDX6FI.js +147 -0
- package/dist/chunk-UJILKYES.js +75 -0
- package/dist/chunk-ULMAQV3T.js +234 -0
- package/dist/chunk-VVBP6KGT.js +9 -0
- package/dist/chunk-W5VITDM2.js +168 -0
- package/dist/chunk-WAHQVNWT.js +194 -0
- package/dist/chunk-WBPVKEEE.js +227 -0
- package/dist/chunk-WGGYRBXH.js +5434 -0
- package/dist/chunk-WLYKU45P.js +5770 -0
- package/dist/chunk-WP7BVANJ.js +53 -0
- package/dist/chunk-WQQON3SV.js +473 -0
- package/dist/chunk-WQTOVMGV.js +119 -0
- package/dist/chunk-XBDP4QPW.js +7 -0
- package/dist/chunk-XEBHSJLT.js +103 -0
- package/dist/chunk-XF4VUMKW.js +231 -0
- package/dist/chunk-XQ2ECNK3.js +1 -0
- package/dist/chunk-Y5YXDM7D.js +74 -0
- package/dist/chunk-YOPAUVU4.js +2588 -0
- package/dist/chunk-YPT4A46F.js +362 -0
- package/dist/chunk-YWXT3JNM.js +425 -0
- package/dist/chunk-YY6APYUM.js +49 -0
- package/dist/chunk-Z2IJUO45.js +691 -0
- package/dist/chunk-ZHZ2Y2DH.js +1643 -0
- package/dist/chunk-ZT7JNIGX.js +152 -0
- package/dist/coordination/agent-monitor.d.ts +9 -1
- package/dist/coordination/agent-subagent-runner.d.ts +1 -1
- package/dist/coordination/agents/agent-prompts.d.ts +8 -0
- package/dist/coordination/agents/index.d.ts +33 -1
- package/dist/coordination/agents/index.js +184 -6269
- package/dist/coordination/agents/phase3-wave1-platform.d.ts +1 -3
- package/dist/coordination/agents/phase3-wave2-meta.d.ts +1 -3
- package/dist/coordination/agents/phase8-wave3-products.d.ts +1 -3
- package/dist/coordination/agents/phase9-wave4-platform-meta.d.ts +1 -3
- package/dist/coordination/agents/project-agent-identity.d.ts +11 -11
- package/dist/coordination/agents/types.d.ts +13 -2
- package/dist/coordination/brain-monitor.d.ts +30 -7
- package/dist/coordination/collab-debug.d.ts +1 -1
- package/dist/coordination/collab-pause.d.ts +1 -1
- package/dist/coordination/delegate-tool.d.ts +7 -5
- package/dist/coordination/director-kanban-queue-helpers.d.ts +1 -0
- package/dist/coordination/director-options.d.ts +7 -0
- package/dist/coordination/director-spawn-model.d.ts +17 -0
- package/dist/coordination/director-tools.d.ts +2 -2
- package/dist/coordination/director.d.ts +12 -0
- package/dist/coordination/dispatcher.d.ts +8 -0
- package/dist/coordination/fleet-bus.d.ts +35 -4
- package/dist/coordination/fleet-supervisor.d.ts +10 -2
- package/dist/coordination/fleet.d.ts +16 -7
- package/dist/coordination/index.d.ts +4 -0
- package/dist/coordination/index.js +402 -34324
- package/dist/coordination/kanban-dispatch-port.d.ts +2 -2
- package/dist/coordination/mailbox-http-router.d.ts +4 -3
- package/dist/coordination/mailbox-http-validation.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +48 -3276
- package/dist/coordination/mailbox-types.d.ts +24 -6
- package/dist/coordination/model-tier-leader.d.ts +122 -0
- package/dist/coordination/model-tier.d.ts +122 -0
- package/dist/coordination/multi-agent-coordinator.d.ts +24 -0
- package/dist/coordination/origin-session.d.ts +16 -0
- package/dist/coordination/provider-status-tracker.d.ts +41 -5
- package/dist/coordination/session-note-hub.d.ts +44 -0
- package/dist/coordination/session-note-tool.d.ts +10 -0
- package/dist/core/agent.d.ts +15 -2
- package/dist/core/context.d.ts +22 -3
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +239 -12360
- package/dist/core/model-ref.js +5 -23
- package/dist/core/provider-runner.d.ts +36 -1
- package/dist/core/request-conversation-binding.d.ts +37 -0
- package/dist/core/session-notes.d.ts +25 -0
- package/dist/core/system-prompt-builder.d.ts +11 -2
- package/dist/core/system-prompt-variants.d.ts +78 -0
- package/dist/design/index.js +50 -1087
- package/dist/execution/auto-compaction-middleware.d.ts +11 -0
- package/dist/execution/autonomy-brain.d.ts +2 -1
- package/dist/execution/autonomy-prompt-contributor.d.ts +7 -2
- package/dist/execution/compaction-core.d.ts +4 -4
- package/dist/execution/eternal-autonomy.d.ts +1 -1
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +1615 -21895
- package/dist/execution/model-runtime.d.ts +0 -7
- package/dist/execution/parallel-eternal-engine.d.ts +7 -7
- package/dist/execution/prompt-enhancer.js +16 -535
- package/dist/execution/retry-policy.d.ts +31 -1
- package/dist/extension/index.js +7 -311
- package/dist/goal/index.js +33 -3435
- package/dist/hooks/index.js +14 -1138
- package/dist/hq/index.js +114 -5139
- package/dist/hq/persistence/event-log.d.ts +0 -1
- package/dist/hq/persistence.d.ts +4 -4
- package/dist/hq/protocol.js +21 -668
- package/dist/hq/publisher.d.ts +2 -0
- package/dist/index.d.ts +14 -11
- package/dist/index.js +1502 -92655
- package/dist/infrastructure/index.d.ts +4 -4
- package/dist/infrastructure/index.js +504 -5230
- package/dist/infrastructure/token-counter.d.ts +9 -0
- package/dist/kernel/events/agent-events.d.ts +77 -3
- package/dist/kernel/events/brain-events.d.ts +12 -0
- package/dist/kernel/events/provider-events.d.ts +22 -1
- package/dist/kernel/events/session-events.d.ts +14 -0
- package/dist/kernel/events/tool-events.d.ts +13 -0
- package/dist/kernel/index.js +14 -992
- package/dist/mailbox-attach.d.ts +1 -1
- package/dist/models/index.js +41 -1117
- package/dist/notifications/index.js +3 -108
- package/dist/observability/event-bridge.d.ts +25 -5
- package/dist/observability/index.d.ts +9 -8
- package/dist/observability/index.js +477 -631
- package/dist/observability/tool-usage-source.d.ts +65 -0
- package/dist/plugin/index.d.ts +5 -0
- package/dist/plugin/index.js +600 -14835
- package/dist/plugins/auto-review-plugin.d.ts +3 -2
- package/dist/plugins/review-finding-commands.d.ts +0 -8
- package/dist/plugins/review-finding-store.js +22 -0
- package/dist/plugins/review-finding-types.js +19 -0
- package/dist/plugins/review-report-integration.js +21 -0
- package/dist/plugins/review-report-store.js +20 -0
- package/dist/plugins/review-report-types.js +13 -0
- package/dist/prompts/index.js +14 -948
- package/dist/registry/index.js +9 -822
- package/dist/registry/tool-registry.d.ts +59 -9
- package/dist/registry-7N4O3E7W.js +14 -0
- package/dist/replay/index.js +6 -84
- package/dist/security/capabilities.d.ts +6 -0
- package/dist/security/file-permissions.d.ts +1 -1
- package/dist/security/index.js +161 -2235
- package/dist/security/permission-policy.d.ts +12 -1
- package/dist/security/secret-scrubber.d.ts +15 -0
- package/dist/session-catalog/index.d.ts +1 -0
- package/dist/session-catalog/index.js +41 -2103
- package/dist/session-catalog/project-server.js +59 -1435
- package/dist/session-catalog/protocol.d.ts +32 -1
- package/dist/session-catalog/registry.d.ts +17 -1
- package/dist/session-catalog/session-agents.d.ts +56 -0
- package/dist/session-catalog/store-rebuild.d.ts +14 -0
- package/dist/session-catalog/store-schema.d.ts +26 -0
- package/dist/session-catalog/store.d.ts +27 -1
- package/dist/session-note-attach.d.ts +9 -0
- package/dist/skills/index.js +35 -1578
- package/dist/statusline/index.d.ts +43 -0
- package/dist/statusline/index.js +102 -0
- package/dist/storage/board-store-port.d.ts +2 -2
- package/dist/storage/config-loader/in-project-policy.d.ts +29 -0
- package/dist/storage/config-loader.d.ts +3 -2
- package/dist/storage/file-session-writer.d.ts +4 -2
- package/dist/storage/goal-coordination.d.ts +6 -1
- package/dist/storage/goal-kanban.d.ts +4 -1
- package/dist/storage/index.d.ts +12 -4
- package/dist/storage/index.js +198 -15601
- package/dist/storage/input-history-store.d.ts +2 -4
- package/dist/storage/memory-curator.d.ts +133 -0
- package/dist/storage/session-agent-attribution.d.ts +39 -0
- package/dist/storage/session-doctor.d.ts +109 -0
- package/dist/storage/session-event-bridge.d.ts +5 -7
- package/dist/storage/session-outcome.d.ts +42 -0
- package/dist/storage/session-recovery.d.ts +55 -0
- package/dist/storage/session-store/load-session-data.d.ts +11 -0
- package/dist/storage/session-store/prune-helpers.d.ts +1 -1
- package/dist/storage/session-store/resume-session.d.ts +0 -2
- package/dist/storage/session-store/types.d.ts +8 -0
- package/dist/storage/session-store.d.ts +3 -3
- package/dist/storage/session-summary-tracker.d.ts +8 -1
- package/dist/storage/session-write-buffer.d.ts +18 -0
- package/dist/storage/session-writer-truncate.d.ts +2 -0
- package/dist/tasking/index.js +10 -676
- package/dist/tools/fallback-manage-tools.d.ts +6 -4
- package/dist/tools/index.js +52 -9590
- package/dist/tools/model-tier-set-tool.d.ts +66 -0
- package/dist/types/agent-bridge.d.ts +2 -1
- package/dist/types/config/autonomy.d.ts +5 -0
- package/dist/types/config/mcp-features.d.ts +7 -0
- package/dist/types/config/model-tiers.d.ts +148 -0
- package/dist/types/config/root.d.ts +9 -0
- package/dist/types/config/tools.d.ts +73 -0
- package/dist/types/config/ui.d.ts +1 -1
- package/dist/types/config.d.ts +1 -0
- package/dist/types/context.d.ts +2 -0
- package/dist/types/default-config.d.ts +6 -0
- package/dist/types/errors.d.ts +20 -1
- package/dist/types/index.d.ts +11 -9
- package/dist/types/index.js +131 -1342
- package/dist/types/multi-agent.d.ts +43 -0
- package/dist/types/plugin.d.ts +1 -1
- package/dist/types/provider-runner.d.ts +8 -0
- package/dist/types/provider.d.ts +15 -0
- package/dist/types/runtime-capability-manifest.d.ts +1 -1
- package/dist/types/secret-scrubber.d.ts +15 -0
- package/dist/types/session-markers.d.ts +37 -10
- package/dist/types/session-markers.js +8 -144
- package/dist/types/session-timeline.d.ts +197 -0
- package/dist/types/session-timeline.js +12 -0
- package/dist/types/session.d.ts +172 -11
- package/dist/types/system-prompt.d.ts +27 -0
- package/dist/utils/config-backup.d.ts +9 -1
- package/dist/utils/error.js +3 -4
- package/dist/utils/expect-defined.js +3 -9
- package/dist/utils/heap-watchdog.js +6 -706
- package/dist/utils/index.js +271 -5685
- package/dist/utils/message-invariants.d.ts +4 -1
- package/dist/utils/regex-guard.d.ts +1 -1
- package/dist/utils/sage-output-block.js +5 -42
- package/dist/utils/socket-path.d.ts +1 -1
- package/dist/utils/token-estimate.d.ts +6 -2
- package/dist/utils/wstack-paths.d.ts +2 -0
- package/dist/wiring/proxy-rewrite.d.ts +46 -0
- package/dist/wiring/proxy-rewrite.js +24 -121
- package/dist/worktree/index.js +6 -887
- package/instructions/agents/explore-companion.md +45 -18
- package/instructions/agents/memory-curator.md +34 -22
- package/instructions/coordination/director-preamble.md +12 -7
- package/instructions/coordination/subagent-baseline.md +26 -5
- package/instructions/leader-after-task.md +4 -1
- package/instructions/llm/chimera-review.md +30 -9
- package/instructions/llm/memory-curator.md +31 -0
- package/instructions/sections/tool/delegation-compact.md +2 -0
- package/instructions/sections/tool/delegation-full.md +5 -1
- package/instructions/sections/tool/mailbox-compact.md +5 -1
- package/instructions/sections/tool/mailbox-full.md +3 -0
- package/instructions/sections/tool/session-note-compact.md +3 -0
- package/instructions/sections/tool/session-note-full.md +15 -0
- package/instructions/system-lite.md +9 -4
- package/instructions/system-pro.md +66 -143
- package/instructions/system.md +73 -175
- package/package.json +32 -4
- package/dist/coordination/agents.d.ts +0 -8
- package/dist/coordination/transport.d.ts +0 -2
- package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +0 -28
|
@@ -0,0 +1,2588 @@
|
|
|
1
|
+
import {
|
|
2
|
+
recordPromptJournalEntry
|
|
3
|
+
} from "./chunk-RFQMOKWK.js";
|
|
4
|
+
import {
|
|
5
|
+
createHqPublisherFromEnv
|
|
6
|
+
} from "./chunk-NIOAGFPB.js";
|
|
7
|
+
import {
|
|
8
|
+
ExtensionRegistry
|
|
9
|
+
} from "./chunk-MTL4O5YR.js";
|
|
10
|
+
import {
|
|
11
|
+
JsonlReportStore
|
|
12
|
+
} from "./chunk-HXKZ7OBQ.js";
|
|
13
|
+
import {
|
|
14
|
+
getSharedProjectMailbox,
|
|
15
|
+
mailboxSessionTag,
|
|
16
|
+
resolveMailboxIdentity,
|
|
17
|
+
sessionNoteHub
|
|
18
|
+
} from "./chunk-JO4MGB6Z.js";
|
|
19
|
+
import {
|
|
20
|
+
MAILBOX_AWARENESS_INTERVAL_MS,
|
|
21
|
+
MAILBOX_HEARTBEAT_INTERVAL_MS,
|
|
22
|
+
PULSE_MIN_READ_INTERVAL_MS,
|
|
23
|
+
resolveProjectDir
|
|
24
|
+
} from "./chunk-IRUTEHCE.js";
|
|
25
|
+
import {
|
|
26
|
+
MAILBOX_TYPE_PROPERTIES,
|
|
27
|
+
acceptMailboxMessageForSession,
|
|
28
|
+
isMailboxLeader
|
|
29
|
+
} from "./chunk-FWNLEZYG.js";
|
|
30
|
+
import {
|
|
31
|
+
buildBtwBlock,
|
|
32
|
+
consumeBtwNotes,
|
|
33
|
+
setBtwNote
|
|
34
|
+
} from "./chunk-SQJVFEBV.js";
|
|
35
|
+
import {
|
|
36
|
+
runProviderWithRetry
|
|
37
|
+
} from "./chunk-U45AXT5D.js";
|
|
38
|
+
import {
|
|
39
|
+
clearPendingNextSteps,
|
|
40
|
+
consumeAutonomousContinue,
|
|
41
|
+
createAgentResponseHandler,
|
|
42
|
+
formatTodosForModel,
|
|
43
|
+
hasKanbanBoundTodos,
|
|
44
|
+
hasOpenTodos,
|
|
45
|
+
sizeSignals,
|
|
46
|
+
truncateForEvent
|
|
47
|
+
} from "./chunk-M27LIEKV.js";
|
|
48
|
+
import {
|
|
49
|
+
isRuntimeContextInput,
|
|
50
|
+
providerBoundToRequest,
|
|
51
|
+
recordToolOutputEvidence,
|
|
52
|
+
recordUserIntentEvidence,
|
|
53
|
+
resolveEventSessionId,
|
|
54
|
+
resolveOwningSessionId
|
|
55
|
+
} from "./chunk-ZHZ2Y2DH.js";
|
|
56
|
+
import {
|
|
57
|
+
estimateMessageTokens,
|
|
58
|
+
estimateRequestTokens,
|
|
59
|
+
getCalibrationState,
|
|
60
|
+
realAnchoredInputTokens,
|
|
61
|
+
recordActualUsage
|
|
62
|
+
} from "./chunk-HMLIGAU7.js";
|
|
63
|
+
import {
|
|
64
|
+
capSageLines,
|
|
65
|
+
splitSageOutputBlock
|
|
66
|
+
} from "./chunk-SVT4RNTR.js";
|
|
67
|
+
import {
|
|
68
|
+
wstackGlobalRoot
|
|
69
|
+
} from "./chunk-SAFHACFA.js";
|
|
70
|
+
import {
|
|
71
|
+
Pipeline,
|
|
72
|
+
RunController,
|
|
73
|
+
TOKENS
|
|
74
|
+
} from "./chunk-NS52ISWS.js";
|
|
75
|
+
import {
|
|
76
|
+
effectiveInputTokens
|
|
77
|
+
} from "./chunk-3ECENVHL.js";
|
|
78
|
+
import {
|
|
79
|
+
isTextBlock,
|
|
80
|
+
isToolUseBlock
|
|
81
|
+
} from "./chunk-O37BQ65S.js";
|
|
82
|
+
import {
|
|
83
|
+
AgentError,
|
|
84
|
+
toWrongStackError
|
|
85
|
+
} from "./chunk-25LSLC3G.js";
|
|
86
|
+
import {
|
|
87
|
+
toErrorMessage
|
|
88
|
+
} from "./chunk-TBRL2RCC.js";
|
|
89
|
+
|
|
90
|
+
// src/core/mailbox-loop.ts
|
|
91
|
+
function createMailboxChecker(opts) {
|
|
92
|
+
const getMailbox = typeof opts.mailbox === "function" ? opts.mailbox : () => opts.mailbox;
|
|
93
|
+
const currentId = typeof opts.agentId === "function" ? opts.agentId : () => opts.agentId;
|
|
94
|
+
const currentSessionId = typeof opts.sessionId === "function" ? opts.sessionId : () => opts.sessionId;
|
|
95
|
+
const currentRole = typeof opts.role === "function" ? opts.role : () => opts.role;
|
|
96
|
+
const injectedIds = /* @__PURE__ */ new Set();
|
|
97
|
+
const broadcastFloor = opts.broadcastFloor ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
98
|
+
return async () => {
|
|
99
|
+
try {
|
|
100
|
+
const mailbox = getMailbox();
|
|
101
|
+
const agentId = currentId();
|
|
102
|
+
const role = currentRole();
|
|
103
|
+
const sessionId = currentSessionId();
|
|
104
|
+
const targets = [
|
|
105
|
+
agentId,
|
|
106
|
+
...(opts.aliases ?? []).filter((al) => al && al !== agentId),
|
|
107
|
+
...sessionId ? [`@session:${sessionId}`] : []
|
|
108
|
+
];
|
|
109
|
+
const batches = await Promise.all(
|
|
110
|
+
targets.map(
|
|
111
|
+
(to) => mailbox.query({ to, unreadBy: agentId, readerRole: role, limit: 10 }).catch(() => [])
|
|
112
|
+
)
|
|
113
|
+
);
|
|
114
|
+
const seen = /* @__PURE__ */ new Set();
|
|
115
|
+
const messages = [];
|
|
116
|
+
for (const batch of batches) {
|
|
117
|
+
for (const m of batch) {
|
|
118
|
+
if (seen.has(m.id)) continue;
|
|
119
|
+
if (m.to === "*" && m.timestamp < broadcastFloor) continue;
|
|
120
|
+
if (opts.include && !opts.include(m)) continue;
|
|
121
|
+
seen.add(m.id);
|
|
122
|
+
messages.push(m);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
const fresh = messages.filter(
|
|
126
|
+
(m) => !injectedIds.has(m.id) && !m.completed
|
|
127
|
+
);
|
|
128
|
+
for (const m of fresh) {
|
|
129
|
+
injectedIds.add(m.id);
|
|
130
|
+
}
|
|
131
|
+
if (fresh.length > 0 && opts.ack !== false) {
|
|
132
|
+
void mailbox.ackMany({
|
|
133
|
+
acks: fresh.map((m) => ({
|
|
134
|
+
messageId: m.id,
|
|
135
|
+
readerId: agentId,
|
|
136
|
+
read: true
|
|
137
|
+
}))
|
|
138
|
+
}).catch(() => {
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (injectedIds.size > 1e3) {
|
|
142
|
+
const recent = new Set([...injectedIds].slice(-500));
|
|
143
|
+
injectedIds.clear();
|
|
144
|
+
for (const id of recent) injectedIds.add(id);
|
|
145
|
+
}
|
|
146
|
+
return fresh;
|
|
147
|
+
} catch {
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
var TYPE_LABEL = {
|
|
153
|
+
steer: "\u{1F504} STEER",
|
|
154
|
+
btw: "\u{1F4AC} BTW",
|
|
155
|
+
ask: "\u2753 ASK",
|
|
156
|
+
assign: "\u{1F4CB} ASSIGN",
|
|
157
|
+
result: "\u2705 RESULT",
|
|
158
|
+
review: "\u{1F50D} REVIEW",
|
|
159
|
+
status: "\u{1F4E8} STATUS",
|
|
160
|
+
note: "\u{1F4E8} NOTE",
|
|
161
|
+
broadcast: "\u{1F4E2} BROADCAST",
|
|
162
|
+
control: "\u2699 CONTROL"
|
|
163
|
+
};
|
|
164
|
+
var TYPE_GUIDANCE = {
|
|
165
|
+
steer: "After your current operation reaches a stopping point, adjust your approach per the instruction above.",
|
|
166
|
+
ask: "This agent is waiting for your answer. Reply directly or use mailbox action=send to respond.",
|
|
167
|
+
assign: "This is a task assignment. Act on it when your current operation allows.",
|
|
168
|
+
result: "A subagent has completed its work. Factor this result into your next decision.",
|
|
169
|
+
btw: "FYI only \u2014 absorb the information and stay on your current task; no reply needed.",
|
|
170
|
+
status: "Peer status update \u2014 use it to avoid duplicate or conflicting work; no reply needed.",
|
|
171
|
+
review: "This is a review request. Inspect the referenced code/doc/PR when convenient; an immediate reply is not required.",
|
|
172
|
+
note: "Read for context; no reply needed.",
|
|
173
|
+
broadcast: "Broadcast message. Read for context; no reply needed.",
|
|
174
|
+
control: ""
|
|
175
|
+
// control never reaches this rendering path — see injectPendingMailboxMessages
|
|
176
|
+
};
|
|
177
|
+
var ACTION_FOOTER_TYPES = /* @__PURE__ */ new Set([
|
|
178
|
+
"ask",
|
|
179
|
+
"assign",
|
|
180
|
+
"result",
|
|
181
|
+
"review"
|
|
182
|
+
]);
|
|
183
|
+
var BACKGROUND_DELIVERY_TYPES = /* @__PURE__ */ new Set([
|
|
184
|
+
"steer",
|
|
185
|
+
"ask",
|
|
186
|
+
"assign",
|
|
187
|
+
"result",
|
|
188
|
+
"review"
|
|
189
|
+
]);
|
|
190
|
+
var OUT_OF_BAND_TYPES = /* @__PURE__ */ new Set(["control"]);
|
|
191
|
+
function buildMailboxBtwAwarenessBlock(messages) {
|
|
192
|
+
if (messages.length === 0) throw new Error("buildMailboxBtwAwarenessBlock called with empty messages");
|
|
193
|
+
const parts = [];
|
|
194
|
+
parts.push("[MAILBOX BTW] Mailbox awareness update:");
|
|
195
|
+
parts.push("");
|
|
196
|
+
parts.push(
|
|
197
|
+
"Disclaimer: an agent just sent mail to everyone or to you. Do not stop your current work to look at it; this is only for awareness. This notification came from the WrongStack mailbox system."
|
|
198
|
+
);
|
|
199
|
+
parts.push("");
|
|
200
|
+
for (const m of messages) {
|
|
201
|
+
const typeLabel = TYPE_LABEL[m.type] ?? `\u{1F4E8} ${m.type.toUpperCase()}`;
|
|
202
|
+
const scope = m.to === "*" ? "broadcast to everyone" : `addressed to ${m.to}`;
|
|
203
|
+
parts.push(`--- ${typeLabel} from ${m.from} (${scope}) ---`);
|
|
204
|
+
parts.push(`Subject: ${m.subject}`);
|
|
205
|
+
parts.push("");
|
|
206
|
+
parts.push(m.body);
|
|
207
|
+
parts.push("");
|
|
208
|
+
}
|
|
209
|
+
parts.push("This raw awareness block is request-scoped; absorb it once without quoting or restating it.");
|
|
210
|
+
parts.push("");
|
|
211
|
+
parts.push("[END MAILBOX BTW]");
|
|
212
|
+
return { type: "text", text: parts.join("\n") };
|
|
213
|
+
}
|
|
214
|
+
function buildMailboxBlock(messages) {
|
|
215
|
+
if (messages.length === 0) throw new Error("buildMailboxBlock called with empty messages");
|
|
216
|
+
const parts = [];
|
|
217
|
+
parts.push("[MAILBOX] New message(s) from other agents:");
|
|
218
|
+
parts.push("");
|
|
219
|
+
const hasActionable = messages.some((m) => ACTION_FOOTER_TYPES.has(m.type));
|
|
220
|
+
const steers = [];
|
|
221
|
+
const rest = [];
|
|
222
|
+
for (const m of messages) {
|
|
223
|
+
if (m.type === "steer") steers.push(m);
|
|
224
|
+
else rest.push(m);
|
|
225
|
+
}
|
|
226
|
+
const ordered = [...steers, ...rest];
|
|
227
|
+
for (const m of ordered) {
|
|
228
|
+
const typeLabel = TYPE_LABEL[m.type] ?? `\u{1F4E8} ${m.type.toUpperCase()}`;
|
|
229
|
+
parts.push(`--- ${typeLabel} from ${m.from} ---`);
|
|
230
|
+
parts.push(`Subject: ${m.subject}`);
|
|
231
|
+
parts.push("");
|
|
232
|
+
parts.push(m.body);
|
|
233
|
+
parts.push("");
|
|
234
|
+
const guidance = TYPE_GUIDANCE[m.type];
|
|
235
|
+
if (guidance) {
|
|
236
|
+
parts.push(`${guidance}`);
|
|
237
|
+
parts.push("");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (hasActionable) {
|
|
241
|
+
parts.push("Action required: address the items above. When done, use `mailbox action=ack messageId=<id> completed=true` to mark them complete.");
|
|
242
|
+
parts.push("");
|
|
243
|
+
}
|
|
244
|
+
parts.push(
|
|
245
|
+
"Context policy: this raw mailbox block is request-scoped and will be removed after you evaluate it. Do not quote or restate the mail. If it has a durable consequence, retain only one concise conclusion/action in your response, task state, or tool output; otherwise acknowledge it internally and continue."
|
|
246
|
+
);
|
|
247
|
+
parts.push("");
|
|
248
|
+
parts.push("[END MAILBOX]");
|
|
249
|
+
return { type: "text", text: parts.join("\n") };
|
|
250
|
+
}
|
|
251
|
+
function removeInjectedMailboxBlocks(messages, injectedBlocks) {
|
|
252
|
+
if (injectedBlocks.length === 0) {
|
|
253
|
+
return { messages, changed: false };
|
|
254
|
+
}
|
|
255
|
+
const blockRefs = new Set(injectedBlocks);
|
|
256
|
+
const blockTexts = new Set(injectedBlocks.map((block) => block.text));
|
|
257
|
+
const next = [];
|
|
258
|
+
let changed = false;
|
|
259
|
+
for (const message of messages) {
|
|
260
|
+
if (!Array.isArray(message.content)) {
|
|
261
|
+
next.push(message);
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const content = message.content.filter((block) => {
|
|
265
|
+
if (block.type !== "text") return true;
|
|
266
|
+
const isInjected = blockRefs.has(block) || blockTexts.has(block.text);
|
|
267
|
+
if (isInjected) changed = true;
|
|
268
|
+
return !isInjected;
|
|
269
|
+
});
|
|
270
|
+
if (content.length === message.content.length) {
|
|
271
|
+
next.push(message);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (content.length === 0) continue;
|
|
275
|
+
const { _estTokens: _staleEstimate, ...rest } = message;
|
|
276
|
+
next.push({ ...rest, content });
|
|
277
|
+
}
|
|
278
|
+
return changed ? { messages: next, changed: true } : { messages, changed: false };
|
|
279
|
+
}
|
|
280
|
+
async function injectPendingMailboxMessages(checkMailbox, foldFn, a, deliveryMode = "inline") {
|
|
281
|
+
let messages;
|
|
282
|
+
try {
|
|
283
|
+
messages = await checkMailbox();
|
|
284
|
+
} catch {
|
|
285
|
+
return { interrupt: false };
|
|
286
|
+
}
|
|
287
|
+
for (const m of messages) {
|
|
288
|
+
a.events.emit("mailbox.received", {
|
|
289
|
+
messageId: m.id,
|
|
290
|
+
from: m.from,
|
|
291
|
+
to: m.to,
|
|
292
|
+
type: m.type,
|
|
293
|
+
audience: m.audience ?? "all",
|
|
294
|
+
subject: m.subject
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
if (messages.length === 0) return { interrupt: false };
|
|
298
|
+
const outOfBand = messages.filter((m) => OUT_OF_BAND_TYPES.has(m.type));
|
|
299
|
+
const content = messages.filter((m) => !OUT_OF_BAND_TYPES.has(m.type));
|
|
300
|
+
const deliveredContent = deliveryMode === "background" ? content.filter((message) => BACKGROUND_DELIVERY_TYPES.has(message.type)) : content;
|
|
301
|
+
if (deliveredContent.length > 0) {
|
|
302
|
+
try {
|
|
303
|
+
foldFn(buildMailboxBlock(deliveredContent));
|
|
304
|
+
} catch (err) {
|
|
305
|
+
(a.logger.debug ?? console.debug)?.(
|
|
306
|
+
`mailbox: failed to fold messages: ${toErrorMessage(err)}`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const interruptMsg = outOfBand.find(
|
|
311
|
+
(m) => /\b(interrupt|stop|halt|abort|cancel)\b/i.test(`${m.subject} ${m.body}`)
|
|
312
|
+
);
|
|
313
|
+
return interruptMsg ? { interrupt: true, interruptReason: interruptMsg.body || interruptMsg.subject || "operator interrupt" } : { interrupt: false };
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// src/core/fleet-pulse.ts
|
|
317
|
+
var DEFAULT_MAX_AGENTS = 15;
|
|
318
|
+
var DEFAULT_MAX_CHARS = 900;
|
|
319
|
+
var TASK_SNIPPET_CHARS = 60;
|
|
320
|
+
function fleetPulseSignature(statuses) {
|
|
321
|
+
return statuses.map((s) => `${s.agentId}|${s.status}|${s.currentTask ?? ""}`).sort().join("\n");
|
|
322
|
+
}
|
|
323
|
+
function visibleLineKey(s) {
|
|
324
|
+
const role = s.role && s.role !== s.name ? s.role : "";
|
|
325
|
+
const task = s.currentTask && s.currentTask.length > TASK_SNIPPET_CHARS ? `${s.currentTask.slice(0, TASK_SNIPPET_CHARS)}\u2026` : s.currentTask ?? "";
|
|
326
|
+
const tool = s.currentTool || "";
|
|
327
|
+
const toolCalls = s.toolCalls > 0 ? String(s.toolCalls) : "";
|
|
328
|
+
return [s.name, role, s.status, task, tool, toolCalls].join("\0");
|
|
329
|
+
}
|
|
330
|
+
function peerLine(s, count = 1) {
|
|
331
|
+
const role = s.role && s.role !== s.name ? ` (${s.role})` : "";
|
|
332
|
+
const grouped = count > 1 ? ` \xD7${count}` : "";
|
|
333
|
+
const parts = [`\u2022 ${s.name}${role}${grouped} \u2014 ${s.status}`];
|
|
334
|
+
if (s.currentTask) {
|
|
335
|
+
const task = s.currentTask.length > TASK_SNIPPET_CHARS ? `${s.currentTask.slice(0, TASK_SNIPPET_CHARS)}\u2026` : s.currentTask;
|
|
336
|
+
parts.push(`"${task}"`);
|
|
337
|
+
}
|
|
338
|
+
if (s.currentTool) parts.push(`tool: ${s.currentTool}`);
|
|
339
|
+
if (s.toolCalls > 0) parts.push(`${s.toolCalls} tools`);
|
|
340
|
+
return parts.join(" \u2014 ");
|
|
341
|
+
}
|
|
342
|
+
function buildFleetPulseBlock(statuses, opts) {
|
|
343
|
+
const maxAgents = opts.maxAgents ?? DEFAULT_MAX_AGENTS;
|
|
344
|
+
const maxChars = opts.maxChars ?? DEFAULT_MAX_CHARS;
|
|
345
|
+
const peers = statuses.filter((s) => s.online && s.agentId !== opts.selfId);
|
|
346
|
+
if (peers.length === 0) return null;
|
|
347
|
+
const order = { running: 0, streaming: 0, waiting_user: 1, idle: 2, error: 3, offline: 4 };
|
|
348
|
+
const sorted = [...peers].sort(
|
|
349
|
+
(x, y) => (order[x.status] ?? 5) - (order[y.status] ?? 5) || visibleLineKey(x).localeCompare(visibleLineKey(y))
|
|
350
|
+
);
|
|
351
|
+
const shown = sorted.slice(0, maxAgents);
|
|
352
|
+
const hidden = sorted.length - shown.length;
|
|
353
|
+
const parts = [];
|
|
354
|
+
parts.push(`[FLEET PULSE] ${peers.length} peer${peers.length === 1 ? "" : "s"} online:`);
|
|
355
|
+
for (let i = 0; i < shown.length; ) {
|
|
356
|
+
let run = 1;
|
|
357
|
+
while (i + run < shown.length && visibleLineKey(shown[i]) === visibleLineKey(shown[i + run])) {
|
|
358
|
+
run++;
|
|
359
|
+
}
|
|
360
|
+
parts.push(peerLine(shown[i], run));
|
|
361
|
+
i += run;
|
|
362
|
+
}
|
|
363
|
+
if (hidden > 0) parts.push(`\u2026 +${hidden} more`);
|
|
364
|
+
parts.push(
|
|
365
|
+
"[END FLEET PULSE] (FYI \u2014 coordinate via mail_send; avoid duplicating peers' work)"
|
|
366
|
+
);
|
|
367
|
+
let text = parts.join("\n");
|
|
368
|
+
if (text.length > maxChars) {
|
|
369
|
+
while (parts.length > 3 && parts.join("\n").length > maxChars) {
|
|
370
|
+
parts.splice(parts.length - 2, 1);
|
|
371
|
+
}
|
|
372
|
+
text = parts.join("\n");
|
|
373
|
+
if (text.length > maxChars) text = `${text.slice(0, maxChars - 1)}\u2026`;
|
|
374
|
+
}
|
|
375
|
+
return { type: "text", text };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/mailbox-attach.ts
|
|
379
|
+
function attachMailboxChecker(a, source) {
|
|
380
|
+
if (!a.ctx.projectRoot) {
|
|
381
|
+
return async () => [];
|
|
382
|
+
}
|
|
383
|
+
try {
|
|
384
|
+
return attachMailboxCheckerInner(a, source);
|
|
385
|
+
} catch {
|
|
386
|
+
return async () => [];
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function attachMailboxCheckerInner(a, source) {
|
|
390
|
+
const getMailbox = () => {
|
|
391
|
+
const projectDir = resolveProjectDir(a.ctx.projectRoot, wstackGlobalRoot());
|
|
392
|
+
return getSharedProjectMailbox(projectDir, a.events, hqPublisher);
|
|
393
|
+
};
|
|
394
|
+
const hqPublisher = createHqPublisherFromEnv({
|
|
395
|
+
clientKind: source ?? "cli",
|
|
396
|
+
projectRoot: a.ctx.projectRoot,
|
|
397
|
+
logger: a.logger,
|
|
398
|
+
capabilities: ["telemetry.publish", "mailbox.summary"]
|
|
399
|
+
});
|
|
400
|
+
hqPublisher?.connect();
|
|
401
|
+
if (hqPublisher) {
|
|
402
|
+
a.ctx.registerAgentHook(() => hqPublisher.close());
|
|
403
|
+
}
|
|
404
|
+
const surface = source ?? a.ctx.meta["source"] ?? "cli";
|
|
405
|
+
if (!a.ctx.meta["source"]) a.ctx.meta["source"] = surface;
|
|
406
|
+
const baseIdOf = () => {
|
|
407
|
+
const fieldId = a.ctx.agentId && a.ctx.agentId !== "unknown" ? a.ctx.agentId : void 0;
|
|
408
|
+
return a.ctx.meta["agentId"] ?? fieldId ?? "leader";
|
|
409
|
+
};
|
|
410
|
+
const identitySessionId = () => {
|
|
411
|
+
const owning = a.ctx.meta["sessionId"];
|
|
412
|
+
return typeof owning === "string" && owning.length > 0 ? owning : a.ctx.session.id;
|
|
413
|
+
};
|
|
414
|
+
let registeredAs = "";
|
|
415
|
+
const ensureRegistered = () => {
|
|
416
|
+
const derived = `${baseIdOf()}@${mailboxSessionTag(identitySessionId())}`;
|
|
417
|
+
if (a.ctx.meta["globalAgentId"] !== derived) {
|
|
418
|
+
a.ctx.meta["globalAgentId"] = derived;
|
|
419
|
+
}
|
|
420
|
+
if (registeredAs !== derived) {
|
|
421
|
+
registeredAs = derived;
|
|
422
|
+
const identity = resolveMailboxIdentity(a.ctx);
|
|
423
|
+
getMailbox().registerAgent({
|
|
424
|
+
agentId: derived,
|
|
425
|
+
name: `${identity.name} [${surface}]`,
|
|
426
|
+
role: identity.role,
|
|
427
|
+
sessionId: identitySessionId(),
|
|
428
|
+
pid: process.pid,
|
|
429
|
+
source: surface
|
|
430
|
+
}).catch((err) => {
|
|
431
|
+
a.logger.debug(`Failed to register agent ${derived}`, {
|
|
432
|
+
agentId: derived,
|
|
433
|
+
err: toErrorMessage(err)
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return derived;
|
|
438
|
+
};
|
|
439
|
+
ensureRegistered();
|
|
440
|
+
const HEARTBEAT_INTERVAL_MS = MAILBOX_HEARTBEAT_INTERVAL_MS;
|
|
441
|
+
const heartbeatTimer = setInterval(() => {
|
|
442
|
+
const id = ensureRegistered();
|
|
443
|
+
const identity = resolveMailboxIdentity(a.ctx);
|
|
444
|
+
getMailbox().heartbeat({
|
|
445
|
+
agentId: id,
|
|
446
|
+
sessionId: identitySessionId(),
|
|
447
|
+
name: `${identity.name} [${surface}]`,
|
|
448
|
+
role: identity.role,
|
|
449
|
+
pid: process.pid,
|
|
450
|
+
source: surface
|
|
451
|
+
}).catch(() => {
|
|
452
|
+
});
|
|
453
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
454
|
+
heartbeatTimer.unref?.();
|
|
455
|
+
a.ctx.registerAgentHook(() => {
|
|
456
|
+
clearInterval(heartbeatTimer);
|
|
457
|
+
});
|
|
458
|
+
const mailboxCheckerOptions = {
|
|
459
|
+
mailbox: getMailbox,
|
|
460
|
+
agentId: () => ensureRegistered(),
|
|
461
|
+
role: () => resolveMailboxIdentity(a.ctx).role,
|
|
462
|
+
aliases: [baseIdOf()],
|
|
463
|
+
sessionId: () => identitySessionId(),
|
|
464
|
+
// ACK is deferred to the session-affinity wrapper so messages dropped
|
|
465
|
+
// by the filter are NOT marked as read by this agent.
|
|
466
|
+
ack: false
|
|
467
|
+
};
|
|
468
|
+
const checkMailbox = createMailboxChecker(mailboxCheckerOptions);
|
|
469
|
+
const reportStore = new JsonlReportStore(
|
|
470
|
+
resolveProjectDir(a.ctx.projectRoot, wstackGlobalRoot())
|
|
471
|
+
);
|
|
472
|
+
const sessionAffinityCtx = {
|
|
473
|
+
resolveChimeraReportSessionId: async (reportId) => (await reportStore.get(reportId))?.sessionId
|
|
474
|
+
};
|
|
475
|
+
const applySessionAffinityFilter = (checker, ack) => {
|
|
476
|
+
return async () => {
|
|
477
|
+
const currentSessionId = identitySessionId();
|
|
478
|
+
const agentId = ack ? ensureRegistered() : null;
|
|
479
|
+
const messages = await checker();
|
|
480
|
+
const filtered = [];
|
|
481
|
+
for (const m of messages) {
|
|
482
|
+
if (await acceptMailboxMessageForSession(m, currentSessionId, sessionAffinityCtx)) {
|
|
483
|
+
filtered.push(m);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (ack && filtered.length > 0 && agentId) {
|
|
487
|
+
void getMailbox().ackMany({
|
|
488
|
+
acks: filtered.map((m) => ({
|
|
489
|
+
messageId: m.id,
|
|
490
|
+
readerId: agentId,
|
|
491
|
+
read: true
|
|
492
|
+
}))
|
|
493
|
+
}).catch(() => {
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
return filtered;
|
|
497
|
+
};
|
|
498
|
+
};
|
|
499
|
+
const sessionScopedCheckMailbox = applySessionAffinityFilter(checkMailbox, true);
|
|
500
|
+
const checkMailboxAwareness = createMailboxChecker({
|
|
501
|
+
...mailboxCheckerOptions,
|
|
502
|
+
// Exclude out-of-band types (control) from awareness polling.
|
|
503
|
+
// Uses the canonical MAILBOX_TYPE_PROPERTIES outOfBand flag instead
|
|
504
|
+
// of a hardcoded type string, so a newly-added out-of-band type is
|
|
505
|
+
// automatically excluded.
|
|
506
|
+
include: (m) => !MAILBOX_TYPE_PROPERTIES[m.type]?.outOfBand,
|
|
507
|
+
ack: false
|
|
508
|
+
});
|
|
509
|
+
const sessionScopedCheckMailboxAwareness = applySessionAffinityFilter(
|
|
510
|
+
checkMailboxAwareness,
|
|
511
|
+
false
|
|
512
|
+
);
|
|
513
|
+
const AWARENESS_FALLBACK_INTERVAL_MS = MAILBOX_AWARENESS_INTERVAL_MS;
|
|
514
|
+
const AWARENESS_PUSH_DEBOUNCE_MS = 500;
|
|
515
|
+
let pollInFlight = false;
|
|
516
|
+
let awarenessDisposed = false;
|
|
517
|
+
let pushDebounceTimer = null;
|
|
518
|
+
const pollMailboxAwareness = async () => {
|
|
519
|
+
if (awarenessDisposed || pollInFlight) return;
|
|
520
|
+
pollInFlight = true;
|
|
521
|
+
try {
|
|
522
|
+
const messages = await sessionScopedCheckMailboxAwareness();
|
|
523
|
+
if (!awarenessDisposed && messages.length > 0 && a.ctx.meta["coordinationContextMode"] !== "background") {
|
|
524
|
+
setBtwNote(a.ctx, buildMailboxBtwAwarenessBlock(messages).text);
|
|
525
|
+
}
|
|
526
|
+
} catch {
|
|
527
|
+
} finally {
|
|
528
|
+
pollInFlight = false;
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
const triggerPushPoll = () => {
|
|
532
|
+
if (awarenessDisposed) return;
|
|
533
|
+
if (pushDebounceTimer !== null) clearTimeout(pushDebounceTimer);
|
|
534
|
+
pushDebounceTimer = setTimeout(() => {
|
|
535
|
+
pushDebounceTimer = null;
|
|
536
|
+
void pollMailboxAwareness();
|
|
537
|
+
}, AWARENESS_PUSH_DEBOUNCE_MS);
|
|
538
|
+
};
|
|
539
|
+
let pushUnsub = null;
|
|
540
|
+
if (a.events && typeof a.events.onPattern === "function") {
|
|
541
|
+
pushUnsub = a.events.onPattern("mailbox.message_sent", () => {
|
|
542
|
+
triggerPushPoll();
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
const awarenessTimer = setInterval(() => {
|
|
546
|
+
void pollMailboxAwareness();
|
|
547
|
+
}, AWARENESS_FALLBACK_INTERVAL_MS);
|
|
548
|
+
awarenessTimer.unref?.();
|
|
549
|
+
a.ctx.registerAgentHook(() => {
|
|
550
|
+
awarenessDisposed = true;
|
|
551
|
+
clearInterval(awarenessTimer);
|
|
552
|
+
if (pushDebounceTimer !== null) clearTimeout(pushDebounceTimer);
|
|
553
|
+
pushUnsub?.();
|
|
554
|
+
});
|
|
555
|
+
return sessionScopedCheckMailbox;
|
|
556
|
+
}
|
|
557
|
+
function attachFleetPulse(a, cfg) {
|
|
558
|
+
if (!a.ctx.projectRoot || cfg?.enabled === false) {
|
|
559
|
+
return async () => null;
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
const projectDir = resolveProjectDir(a.ctx.projectRoot, wstackGlobalRoot());
|
|
563
|
+
const mailbox = getSharedProjectMailbox(projectDir);
|
|
564
|
+
let lastReadAt = 0;
|
|
565
|
+
let lastSignature = "";
|
|
566
|
+
return async () => {
|
|
567
|
+
try {
|
|
568
|
+
const now = Date.now();
|
|
569
|
+
if (now - lastReadAt < PULSE_MIN_READ_INTERVAL_MS) return null;
|
|
570
|
+
lastReadAt = now;
|
|
571
|
+
const statuses = await mailbox.getAgentStatuses();
|
|
572
|
+
const selfId = a.ctx.meta["globalAgentId"] ?? `${a.ctx.agentId ?? "leader"}@${mailboxSessionTag(a.ctx.session.id)}`;
|
|
573
|
+
const online = statuses.filter((s) => s.online && s.agentId !== selfId);
|
|
574
|
+
const signature = fleetPulseSignature(online);
|
|
575
|
+
if (online.length === 0 || signature === lastSignature) return null;
|
|
576
|
+
const block = buildFleetPulseBlock(statuses, {
|
|
577
|
+
selfId,
|
|
578
|
+
maxAgents: cfg?.maxAgents,
|
|
579
|
+
maxChars: cfg?.maxChars
|
|
580
|
+
});
|
|
581
|
+
if (block) lastSignature = signature;
|
|
582
|
+
return block;
|
|
583
|
+
} catch {
|
|
584
|
+
return null;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
} catch {
|
|
588
|
+
return async () => null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// src/core/session-notes.ts
|
|
593
|
+
var META_KEY = "_sessionNotes";
|
|
594
|
+
var MAX_PENDING = 20;
|
|
595
|
+
var MAX_BODY = 2e3;
|
|
596
|
+
function readQueue(ctx) {
|
|
597
|
+
const raw = ctx.meta[META_KEY];
|
|
598
|
+
return Array.isArray(raw) ? raw : [];
|
|
599
|
+
}
|
|
600
|
+
function clipBody(body) {
|
|
601
|
+
const trimmed = body.trim();
|
|
602
|
+
if (trimmed.length <= MAX_BODY) return trimmed;
|
|
603
|
+
return `${trimmed.slice(0, MAX_BODY)}\u2026`;
|
|
604
|
+
}
|
|
605
|
+
function enqueueSessionNote(ctx, note) {
|
|
606
|
+
const body = clipBody(note.body);
|
|
607
|
+
if (!body) return readQueue(ctx).length;
|
|
608
|
+
const next = [
|
|
609
|
+
...readQueue(ctx),
|
|
610
|
+
{
|
|
611
|
+
from: note.from.trim() || "unknown",
|
|
612
|
+
kind: note.kind,
|
|
613
|
+
body,
|
|
614
|
+
...note.subject?.trim() ? { subject: note.subject.trim() } : {}
|
|
615
|
+
}
|
|
616
|
+
].slice(-MAX_PENDING);
|
|
617
|
+
ctx.meta[META_KEY] = next;
|
|
618
|
+
return next.length;
|
|
619
|
+
}
|
|
620
|
+
function pendingSessionNoteCount(ctx) {
|
|
621
|
+
return readQueue(ctx).length;
|
|
622
|
+
}
|
|
623
|
+
function consumeSessionNotes(ctx) {
|
|
624
|
+
const notes = readQueue(ctx);
|
|
625
|
+
if (notes.length > 0) delete ctx.meta[META_KEY];
|
|
626
|
+
return notes;
|
|
627
|
+
}
|
|
628
|
+
function buildSessionNoteBlock(notes) {
|
|
629
|
+
const items = notes.map((n) => {
|
|
630
|
+
const header = n.subject ? `[SESSION ${n.kind.toUpperCase()} from ${n.from} \u2014 ${n.subject}]` : `[SESSION ${n.kind.toUpperCase()} from ${n.from}]`;
|
|
631
|
+
return `${header}
|
|
632
|
+
${n.body}`;
|
|
633
|
+
});
|
|
634
|
+
return [
|
|
635
|
+
"[Same-session notes \u2014 in-process, not mailbox. Fold into the current task;",
|
|
636
|
+
"do not restart unless a steer contradicts the goal.]",
|
|
637
|
+
"",
|
|
638
|
+
...items
|
|
639
|
+
].join("\n");
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
// src/session-note-attach.ts
|
|
643
|
+
function attachSessionNotes(a) {
|
|
644
|
+
const agentId = typeof a.ctx.agentId === "string" && a.ctx.agentId ? a.ctx.agentId : "unknown";
|
|
645
|
+
const aliases = [];
|
|
646
|
+
if (agentId === "leader" || isMailboxLeader(agentId)) aliases.push("leader");
|
|
647
|
+
const off = sessionNoteHub.register({
|
|
648
|
+
sessionId: () => {
|
|
649
|
+
try {
|
|
650
|
+
return resolveOwningSessionId(a.ctx);
|
|
651
|
+
} catch {
|
|
652
|
+
return void 0;
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
agentId,
|
|
656
|
+
aliases,
|
|
657
|
+
deliver: (note) => {
|
|
658
|
+
enqueueSessionNote(a.ctx, note);
|
|
659
|
+
},
|
|
660
|
+
events: a.events
|
|
661
|
+
});
|
|
662
|
+
if (typeof a.ctx.registerAgentHook !== "function") {
|
|
663
|
+
off();
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
a.ctx.registerAgentHook(() => {
|
|
667
|
+
off();
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/core/queued-messages.ts
|
|
672
|
+
var META_KEY2 = "_queuedMessagesAwareness";
|
|
673
|
+
var MAX_ITEMS = 20;
|
|
674
|
+
var MAX_PREVIEW_CHARS = 500;
|
|
675
|
+
function read(ctx) {
|
|
676
|
+
const raw = ctx.meta[META_KEY2];
|
|
677
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
678
|
+
const s = raw;
|
|
679
|
+
return Array.isArray(s.items) && Array.isArray(s.seen) ? s : void 0;
|
|
680
|
+
}
|
|
681
|
+
function sameList(a, b) {
|
|
682
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
683
|
+
}
|
|
684
|
+
function toPreview(text) {
|
|
685
|
+
const trimmed = text.trim();
|
|
686
|
+
if (trimmed.length <= MAX_PREVIEW_CHARS) return trimmed;
|
|
687
|
+
return `${trimmed.slice(0, MAX_PREVIEW_CHARS - 1)}\u2026`;
|
|
688
|
+
}
|
|
689
|
+
function setQueuedMessagesSnapshot(ctx, texts) {
|
|
690
|
+
const items = texts.map(toPreview).filter((t) => t.length > 0).slice(0, MAX_ITEMS);
|
|
691
|
+
const prev = read(ctx);
|
|
692
|
+
if (items.length === 0 && (!prev || prev.seen.length === 0)) {
|
|
693
|
+
if (prev) delete ctx.meta[META_KEY2];
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
ctx.meta[META_KEY2] = { items, seen: prev?.seen ?? [] };
|
|
697
|
+
}
|
|
698
|
+
function peekQueuedMessages(ctx) {
|
|
699
|
+
return read(ctx)?.items ?? [];
|
|
700
|
+
}
|
|
701
|
+
function consumeQueuedMessagesUpdate(ctx) {
|
|
702
|
+
const s = read(ctx);
|
|
703
|
+
if (!s) return null;
|
|
704
|
+
if (sameList(s.items, s.seen)) return null;
|
|
705
|
+
if (s.items.length === 0) {
|
|
706
|
+
delete ctx.meta[META_KEY2];
|
|
707
|
+
return null;
|
|
708
|
+
}
|
|
709
|
+
ctx.meta[META_KEY2] = { items: s.items, seen: s.items };
|
|
710
|
+
return s.items;
|
|
711
|
+
}
|
|
712
|
+
function buildQueuedMessagesBlock(items) {
|
|
713
|
+
const body = items.map((t, i) => `${i + 1}. ${t}`).join("\n");
|
|
714
|
+
return [
|
|
715
|
+
"[QUEUED MESSAGES \u2014 the user typed these while you were working. They are",
|
|
716
|
+
"waiting in the input queue and each will be delivered as its own turn",
|
|
717
|
+
"after you finish, so do NOT answer or act on them as new tasks now. Read",
|
|
718
|
+
"them as a heads-up: if one changes what you should be doing right now",
|
|
719
|
+
"(new constraint, invalidated approach), adapt your current work; if one",
|
|
720
|
+
"is just an FYI, fold it in. The queue may still change before delivery.",
|
|
721
|
+
"This notice replaces any earlier one \u2014 the queue now holds exactly:",
|
|
722
|
+
"",
|
|
723
|
+
body,
|
|
724
|
+
"]"
|
|
725
|
+
].join("\n");
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/core/agent-types.ts
|
|
729
|
+
var DEFAULT_MAX_ITERATIONS = 100;
|
|
730
|
+
function resolveLoopDetection(cfg) {
|
|
731
|
+
const steerThreshold = Math.max(2, cfg?.steerThreshold ?? 3);
|
|
732
|
+
return {
|
|
733
|
+
mode: cfg?.mode ?? "steer-then-cut",
|
|
734
|
+
steerThreshold,
|
|
735
|
+
cutThreshold: Math.max(steerThreshold + 1, cfg?.cutThreshold ?? steerThreshold + 2),
|
|
736
|
+
windowSize: Math.max(4, cfg?.windowSize ?? 12),
|
|
737
|
+
callRepeatThreshold: Math.max(2, cfg?.callRepeatThreshold ?? 4)
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
function normalizeInput(input) {
|
|
741
|
+
if (typeof input === "string") {
|
|
742
|
+
return { blocks: [{ type: "text", text: input }], text: input };
|
|
743
|
+
}
|
|
744
|
+
const text = input.filter(isTextBlock).map((b) => b.text).join("");
|
|
745
|
+
return { blocks: input, text };
|
|
746
|
+
}
|
|
747
|
+
function createDefaultPipelines() {
|
|
748
|
+
return {
|
|
749
|
+
request: new Pipeline(),
|
|
750
|
+
response: new Pipeline(),
|
|
751
|
+
toolCall: new Pipeline(),
|
|
752
|
+
userInput: new Pipeline(),
|
|
753
|
+
assistantOutput: new Pipeline(),
|
|
754
|
+
contextWindow: new Pipeline()
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// src/core/agent.ts
|
|
759
|
+
import { createHash } from "node:crypto";
|
|
760
|
+
|
|
761
|
+
// src/core/agent-loop-context.ts
|
|
762
|
+
function createAgentLoopContextManager(a, handlers) {
|
|
763
|
+
let _lastEmittedMsgCount = -1;
|
|
764
|
+
let _lastEmittedToolCount = -1;
|
|
765
|
+
let _lastEmittedRevision = -1;
|
|
766
|
+
let _lastEmittedMaxContext = -1;
|
|
767
|
+
let _lastPreFlightMsgCount = -1;
|
|
768
|
+
let _lastPreFlightToolCount = -1;
|
|
769
|
+
let _lastPreFlightRevision = -1;
|
|
770
|
+
let _lastCompactionMsgCount = -1;
|
|
771
|
+
let _lastCompactionRevision = -1;
|
|
772
|
+
let _lastCompactionToolsRef = null;
|
|
773
|
+
let _lastCompactionSystemRef = null;
|
|
774
|
+
let _lastCompactionRequestTokens;
|
|
775
|
+
let _lastCompactionMaxContext = -1;
|
|
776
|
+
let _lastCompactionWasNoop = false;
|
|
777
|
+
let _cachedSysRef = null;
|
|
778
|
+
let _cachedToolsRef = null;
|
|
779
|
+
let _cachedOverheadTokens = 0;
|
|
780
|
+
const calibrationKey = (model = a.ctx.model) => `${a.ctx.provider?.id ?? "unknown"}/${model}`;
|
|
781
|
+
function currentMaxContext() {
|
|
782
|
+
const metaLimit = a.ctx.meta?.["effectiveMaxContext"];
|
|
783
|
+
const providerMax = a.ctx.provider.capabilities.maxContext;
|
|
784
|
+
return typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : 2e5;
|
|
785
|
+
}
|
|
786
|
+
async function refreshProviderContextLimit(provider, model, opts = {}) {
|
|
787
|
+
a.ctx.meta ??= {};
|
|
788
|
+
const routeKey = `${provider.id}/${model}`;
|
|
789
|
+
const previousRouteKey = a.ctx.meta["contextLimitRouteKey"];
|
|
790
|
+
const routeChanged = previousRouteKey !== void 0 && previousRouteKey !== routeKey;
|
|
791
|
+
const previousMaxContext = currentMaxContext();
|
|
792
|
+
if (routeChanged) {
|
|
793
|
+
delete a.ctx.meta["providerOverflowMaxContext"];
|
|
794
|
+
delete a.ctx.meta["contextLimitBaseline"];
|
|
795
|
+
delete a.ctx.meta["providerLimitEffectiveMaxContext"];
|
|
796
|
+
delete a.ctx.meta["effectiveMaxContext"];
|
|
797
|
+
delete a.ctx.meta["effectiveMaxContextSource"];
|
|
798
|
+
delete a.ctx.meta["contextLimitLastEmittedMaxContext"];
|
|
799
|
+
delete a.ctx.meta["contextLimitLastEmittedSource"];
|
|
800
|
+
}
|
|
801
|
+
a.ctx.meta["contextLimitRouteKey"] = routeKey;
|
|
802
|
+
const routeMeta = a.ctx.meta["effectiveMaxContext"];
|
|
803
|
+
const routeProviderCap = provider.capabilities.maxContext;
|
|
804
|
+
const currentEffective = typeof routeMeta === "number" && routeMeta > 0 ? routeMeta : typeof routeProviderCap === "number" && routeProviderCap > 0 ? routeProviderCap : 2e5;
|
|
805
|
+
const emitEffectiveLimit = (effective2, source) => {
|
|
806
|
+
const lastEmitted = a.ctx.meta["contextLimitLastEmittedMaxContext"];
|
|
807
|
+
const lastEmittedSource = a.ctx.meta["contextLimitLastEmittedSource"];
|
|
808
|
+
if (lastEmitted === effective2 && lastEmittedSource === source) return;
|
|
809
|
+
const eventPrevious = typeof lastEmitted === "number" && Number.isFinite(lastEmitted) && lastEmitted > 0 ? Math.floor(lastEmitted) : previousMaxContext;
|
|
810
|
+
a.ctx.meta["contextLimitLastEmittedMaxContext"] = effective2;
|
|
811
|
+
a.ctx.meta["contextLimitLastEmittedSource"] = source;
|
|
812
|
+
a.events.emit("ctx.max_context", {
|
|
813
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
814
|
+
providerId: provider.id,
|
|
815
|
+
modelId: model,
|
|
816
|
+
maxContext: effective2,
|
|
817
|
+
...eventPrevious > 0 ? { previousMaxContext: eventPrevious } : {},
|
|
818
|
+
source,
|
|
819
|
+
decreased: eventPrevious > 0 && effective2 < eventPrevious
|
|
820
|
+
});
|
|
821
|
+
};
|
|
822
|
+
const emitNativeAfterRouteChange = () => {
|
|
823
|
+
if (routeChanged && currentEffective > 0) {
|
|
824
|
+
a.ctx.meta["effectiveMaxContext"] = currentEffective;
|
|
825
|
+
a.ctx.meta["effectiveMaxContextSource"] = "configured";
|
|
826
|
+
emitEffectiveLimit(currentEffective, "configured");
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
const refresh = provider.refreshContextLimit;
|
|
830
|
+
if (opts.probe === false || !refresh) {
|
|
831
|
+
emitNativeAfterRouteChange();
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
let discovered;
|
|
835
|
+
try {
|
|
836
|
+
discovered = await refresh.call(provider, model, { signal: a.ctx.signal });
|
|
837
|
+
} catch {
|
|
838
|
+
emitNativeAfterRouteChange();
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
const providerLimit = discovered?.maxContext;
|
|
842
|
+
if (typeof providerLimit !== "number" || !Number.isFinite(providerLimit) || providerLimit <= 0) {
|
|
843
|
+
emitNativeAfterRouteChange();
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
const providerLimitFloored = Math.floor(providerLimit);
|
|
847
|
+
const existingBaseline = a.ctx.meta["contextLimitBaseline"];
|
|
848
|
+
const lastApplied = a.ctx.meta["providerLimitEffectiveMaxContext"];
|
|
849
|
+
const existingOverflow = a.ctx.meta["providerOverflowMaxContext"];
|
|
850
|
+
const effectiveChangedOutsideProbe = typeof lastApplied === "number" && Number.isFinite(lastApplied) && currentEffective !== Math.floor(lastApplied) && currentEffective !== existingOverflow;
|
|
851
|
+
const baseline = effectiveChangedOutsideProbe || typeof existingBaseline !== "number" || !Number.isFinite(existingBaseline) || existingBaseline <= 0 ? currentEffective : Math.floor(existingBaseline);
|
|
852
|
+
a.ctx.meta["contextLimitBaseline"] = baseline;
|
|
853
|
+
const overflowLimit = a.ctx.meta["providerOverflowMaxContext"];
|
|
854
|
+
const overflowFloored = typeof overflowLimit === "number" && Number.isFinite(overflowLimit) && overflowLimit > 0 ? Math.floor(overflowLimit) : void 0;
|
|
855
|
+
const ceilings = [baseline, providerLimitFloored];
|
|
856
|
+
if (overflowFloored !== void 0) ceilings.push(overflowFloored);
|
|
857
|
+
const effective = Math.min(...ceilings.filter((limit) => limit > 0));
|
|
858
|
+
let effectiveSource;
|
|
859
|
+
if (overflowFloored !== void 0 && effective === overflowFloored) {
|
|
860
|
+
effectiveSource = "provider_overflow";
|
|
861
|
+
} else if (effective === providerLimitFloored) {
|
|
862
|
+
effectiveSource = "provider";
|
|
863
|
+
} else {
|
|
864
|
+
effectiveSource = "configured";
|
|
865
|
+
}
|
|
866
|
+
a.ctx.meta["effectiveMaxContext"] = effective;
|
|
867
|
+
a.ctx.meta["providerLimitEffectiveMaxContext"] = effective;
|
|
868
|
+
a.ctx.meta["effectiveMaxContextSource"] = effectiveSource;
|
|
869
|
+
emitEffectiveLimit(effective, effectiveSource);
|
|
870
|
+
}
|
|
871
|
+
function systemAndToolsOverhead() {
|
|
872
|
+
const sysRef = a.ctx.systemPrompt;
|
|
873
|
+
const toolsRef = a.ctx.tools;
|
|
874
|
+
if (sysRef === _cachedSysRef && toolsRef === _cachedToolsRef && _cachedOverheadTokens > 0) {
|
|
875
|
+
return _cachedOverheadTokens;
|
|
876
|
+
}
|
|
877
|
+
const breakdown = estimateRequestTokens([], sysRef, toolsRef ?? [], calibrationKey());
|
|
878
|
+
_cachedSysRef = sysRef;
|
|
879
|
+
_cachedToolsRef = toolsRef;
|
|
880
|
+
_cachedOverheadTokens = breakdown.systemPrompt + breakdown.tools;
|
|
881
|
+
return _cachedOverheadTokens;
|
|
882
|
+
}
|
|
883
|
+
function stashRequestTokens(req) {
|
|
884
|
+
const preFlight = estimateRequestTokens(
|
|
885
|
+
req.messages,
|
|
886
|
+
req.system,
|
|
887
|
+
req.tools ?? [],
|
|
888
|
+
calibrationKey(req.model)
|
|
889
|
+
);
|
|
890
|
+
a.ctx.lastRequestTokens = preFlight.total;
|
|
891
|
+
_lastPreFlightMsgCount = req.messages.length;
|
|
892
|
+
_lastPreFlightToolCount = (req.tools ?? []).length;
|
|
893
|
+
_lastPreFlightRevision = a.ctx.state.revision;
|
|
894
|
+
_cachedSysRef = req.system;
|
|
895
|
+
_cachedToolsRef = req.tools ?? [];
|
|
896
|
+
_cachedOverheadTokens = preFlight.systemPrompt + preFlight.tools;
|
|
897
|
+
a.ctx.meta["lastRequestTokensAt"] = {
|
|
898
|
+
msgCount: req.messages.length,
|
|
899
|
+
toolCount: (req.tools ?? []).length,
|
|
900
|
+
revision: a.ctx.state.revision
|
|
901
|
+
};
|
|
902
|
+
return preFlight;
|
|
903
|
+
}
|
|
904
|
+
function refreshContextRequestTokenStash(opts = {}) {
|
|
905
|
+
const msgCount = a.ctx.messages.length;
|
|
906
|
+
const toolCount = (a.ctx.tools ?? []).length;
|
|
907
|
+
const revision = a.ctx.state.revision;
|
|
908
|
+
const stashed = a.ctx.lastRequestTokens;
|
|
909
|
+
const stashedAt = a.ctx.meta?.["lastRequestTokensAt"];
|
|
910
|
+
if (!opts.force && typeof stashed === "number" && stashed > 0 && typeof stashedAt === "object" && stashedAt !== null) {
|
|
911
|
+
const meta = stashedAt;
|
|
912
|
+
if (meta.msgCount === msgCount && meta.toolCount === toolCount && meta.revision === revision && _lastPreFlightRevision === revision) {
|
|
913
|
+
return stashed;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
const refreshed = estimateMessageTokens(a.ctx.messages) + systemAndToolsOverhead();
|
|
917
|
+
a.ctx.lastRequestTokens = refreshed;
|
|
918
|
+
_lastPreFlightMsgCount = msgCount;
|
|
919
|
+
_lastPreFlightToolCount = toolCount;
|
|
920
|
+
_lastPreFlightRevision = revision;
|
|
921
|
+
a.ctx.meta["lastRequestTokensAt"] = { msgCount, toolCount, revision };
|
|
922
|
+
return refreshed;
|
|
923
|
+
}
|
|
924
|
+
async function compactContextIfNeeded() {
|
|
925
|
+
const msgCount = a.ctx.messages.length;
|
|
926
|
+
const maxContext = currentMaxContext();
|
|
927
|
+
const revision = a.ctx.state.revision;
|
|
928
|
+
const toolsRef = a.ctx.tools;
|
|
929
|
+
const systemRef = a.ctx.systemPrompt;
|
|
930
|
+
const requestTokens = a.ctx.lastRequestTokens;
|
|
931
|
+
if (_lastCompactionMsgCount === msgCount && _lastCompactionRevision === revision && _lastCompactionToolsRef === toolsRef && _lastCompactionSystemRef === systemRef && _lastCompactionRequestTokens === requestTokens && _lastCompactionWasNoop && _lastCompactionMaxContext === maxContext && maxContext > 0) {
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
const beforeMessages = a.ctx.messages;
|
|
935
|
+
const beforeMsgCount = a.ctx.messages.length;
|
|
936
|
+
const beforeRevision = a.ctx.state.revision;
|
|
937
|
+
await a.pipelines.contextWindow.run(a.ctx);
|
|
938
|
+
_lastCompactionMsgCount = a.ctx.messages.length;
|
|
939
|
+
_lastCompactionRevision = a.ctx.state.revision;
|
|
940
|
+
_lastCompactionToolsRef = a.ctx.tools;
|
|
941
|
+
_lastCompactionSystemRef = a.ctx.systemPrompt;
|
|
942
|
+
_lastCompactionMaxContext = maxContext;
|
|
943
|
+
const changed = a.ctx.state.revision !== beforeRevision || a.ctx.messages !== beforeMessages || a.ctx.messages.length !== beforeMsgCount;
|
|
944
|
+
const tokens = refreshContextRequestTokenStash({ force: changed });
|
|
945
|
+
_lastCompactionRequestTokens = tokens;
|
|
946
|
+
const load = maxContext > 0 ? tokens / maxContext : 0;
|
|
947
|
+
_lastCompactionWasNoop = tokens > 0 && load < 0.5;
|
|
948
|
+
if (changed) {
|
|
949
|
+
_lastEmittedMsgCount = -1;
|
|
950
|
+
_lastEmittedToolCount = -1;
|
|
951
|
+
_lastEmittedMaxContext = -1;
|
|
952
|
+
}
|
|
953
|
+
return changed;
|
|
954
|
+
}
|
|
955
|
+
async function buildRequestWithPreflightCompaction(opts) {
|
|
956
|
+
let prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
957
|
+
let req = prepared.request;
|
|
958
|
+
let preFlight = stashRequestTokens(req);
|
|
959
|
+
let probedRouteKey = `${prepared.provider.id}/${req.model}`;
|
|
960
|
+
await refreshProviderContextLimit(prepared.provider, req.model);
|
|
961
|
+
if (await compactContextIfNeeded()) {
|
|
962
|
+
prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
963
|
+
req = prepared.request;
|
|
964
|
+
preFlight = stashRequestTokens(req);
|
|
965
|
+
const rebuiltRouteKey = `${prepared.provider.id}/${req.model}`;
|
|
966
|
+
if (rebuiltRouteKey !== probedRouteKey) {
|
|
967
|
+
probedRouteKey = rebuiltRouteKey;
|
|
968
|
+
await refreshProviderContextLimit(prepared.provider, req.model);
|
|
969
|
+
if (await compactContextIfNeeded()) {
|
|
970
|
+
prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
971
|
+
req = prepared.request;
|
|
972
|
+
preFlight = stashRequestTokens(req);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return { req, provider: prepared.provider, preFlight };
|
|
977
|
+
}
|
|
978
|
+
function emitContextPct() {
|
|
979
|
+
const msgCount = a.ctx.messages.length;
|
|
980
|
+
const toolCount = (a.ctx.tools ?? []).length;
|
|
981
|
+
const maxContext = currentMaxContext();
|
|
982
|
+
const revision = a.ctx.state.revision;
|
|
983
|
+
if (msgCount === _lastEmittedMsgCount && toolCount === _lastEmittedToolCount && revision === _lastEmittedRevision && maxContext === _lastEmittedMaxContext && maxContext > 0) {
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
_lastEmittedMsgCount = msgCount;
|
|
987
|
+
_lastEmittedToolCount = toolCount;
|
|
988
|
+
_lastEmittedRevision = revision;
|
|
989
|
+
_lastEmittedMaxContext = maxContext;
|
|
990
|
+
if (msgCount !== _lastPreFlightMsgCount || toolCount !== _lastPreFlightToolCount || revision !== _lastPreFlightRevision) {
|
|
991
|
+
refreshContextRequestTokenStash({ force: true });
|
|
992
|
+
}
|
|
993
|
+
let total;
|
|
994
|
+
const anchored = realAnchoredInputTokens(
|
|
995
|
+
a.ctx.messages,
|
|
996
|
+
a.ctx.lastRealInputTokens,
|
|
997
|
+
typeof a.ctx.meta?.["realAnchorMsgCount"] === "number" ? a.ctx.meta["realAnchorMsgCount"] : void 0
|
|
998
|
+
);
|
|
999
|
+
const stashed = a.ctx.lastRequestTokens;
|
|
1000
|
+
if (anchored !== null) {
|
|
1001
|
+
total = anchored;
|
|
1002
|
+
} else if (typeof stashed === "number" && stashed > 0) {
|
|
1003
|
+
const cal = getCalibrationState(calibrationKey());
|
|
1004
|
+
total = cal.calibrated ? Math.round(stashed * Math.min(1.5, Math.max(0.5, cal.ratio))) : stashed;
|
|
1005
|
+
} else {
|
|
1006
|
+
const raw = refreshContextRequestTokenStash({ force: true });
|
|
1007
|
+
const cal = getCalibrationState(calibrationKey());
|
|
1008
|
+
total = cal.calibrated ? Math.round(raw * Math.min(1.5, Math.max(0.5, cal.ratio))) : raw;
|
|
1009
|
+
}
|
|
1010
|
+
const rawLoad = maxContext > 0 ? total / maxContext : 0;
|
|
1011
|
+
const load = Math.max(0, Math.min(1, rawLoad));
|
|
1012
|
+
a.events.emit("ctx.pct", {
|
|
1013
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1014
|
+
load,
|
|
1015
|
+
rawLoad,
|
|
1016
|
+
tokens: total,
|
|
1017
|
+
maxContext
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
return {
|
|
1021
|
+
refreshProviderContextLimit,
|
|
1022
|
+
compactContextIfNeeded,
|
|
1023
|
+
stashRequestTokens,
|
|
1024
|
+
refreshContextRequestTokenStash,
|
|
1025
|
+
buildRequestWithPreflightCompaction,
|
|
1026
|
+
emitContextPct,
|
|
1027
|
+
currentMaxContext,
|
|
1028
|
+
calibrationKey,
|
|
1029
|
+
get lastPreFlightMsgCount() {
|
|
1030
|
+
return _lastPreFlightMsgCount;
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/core/agent-loop-detector.ts
|
|
1036
|
+
function iterationFingerprint(blocks) {
|
|
1037
|
+
const toolUses = blocks.filter(isToolUseBlock);
|
|
1038
|
+
const texts = blocks.filter(isTextBlock);
|
|
1039
|
+
const toolNameSet = Array.from(new Set(toolUses.map((u) => u.name))).sort();
|
|
1040
|
+
const firstInputHash = toolUses[0] ? hashSmall(stableStringify(toolUses[0].input ?? {})) : "";
|
|
1041
|
+
const textBlob = texts.map((t) => t.text).join("").slice(0, 512);
|
|
1042
|
+
const hasContent = toolNameSet.length > 0 || textBlob.length > 0;
|
|
1043
|
+
if (!hasContent) return "__empty__";
|
|
1044
|
+
return [
|
|
1045
|
+
`tools=${toolNameSet.join("+") || "-"}`,
|
|
1046
|
+
`in0=${firstInputHash}`,
|
|
1047
|
+
`txt=${textBlob}`
|
|
1048
|
+
].join("\n");
|
|
1049
|
+
}
|
|
1050
|
+
function stableStringify(value) {
|
|
1051
|
+
return JSON.stringify(canonicalize(value));
|
|
1052
|
+
}
|
|
1053
|
+
function canonicalize(value) {
|
|
1054
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
1055
|
+
if (value && typeof value === "object") {
|
|
1056
|
+
const src = value;
|
|
1057
|
+
const out = {};
|
|
1058
|
+
for (const k of Object.keys(src).sort()) out[k] = canonicalize(src[k]);
|
|
1059
|
+
return out;
|
|
1060
|
+
}
|
|
1061
|
+
return value;
|
|
1062
|
+
}
|
|
1063
|
+
function hashSmall(s) {
|
|
1064
|
+
let h = 2166136261;
|
|
1065
|
+
for (let i = 0; i < s.length; i++) {
|
|
1066
|
+
h ^= s.charCodeAt(i);
|
|
1067
|
+
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
1068
|
+
}
|
|
1069
|
+
return h.toString(36);
|
|
1070
|
+
}
|
|
1071
|
+
var AgentLoopDetector = class {
|
|
1072
|
+
constructor(a) {
|
|
1073
|
+
this.a = a;
|
|
1074
|
+
}
|
|
1075
|
+
a;
|
|
1076
|
+
lastToolSignature = "";
|
|
1077
|
+
toolLoopCount = 0;
|
|
1078
|
+
iterationSteerDone = false;
|
|
1079
|
+
recentCallKeys = [];
|
|
1080
|
+
steeredCallKeys = /* @__PURE__ */ new Set();
|
|
1081
|
+
checkIteration(iterationIndex, content, toolUses, queueSteer) {
|
|
1082
|
+
const loopCfg = this.a.loopDetection;
|
|
1083
|
+
if (loopCfg.mode === "off") return { cut: false };
|
|
1084
|
+
const sig = iterationFingerprint(content);
|
|
1085
|
+
if (sig !== "__empty__") {
|
|
1086
|
+
if (sig === this.lastToolSignature) {
|
|
1087
|
+
this.toolLoopCount++;
|
|
1088
|
+
} else {
|
|
1089
|
+
this.lastToolSignature = sig;
|
|
1090
|
+
this.toolLoopCount = 1;
|
|
1091
|
+
this.iterationSteerDone = false;
|
|
1092
|
+
}
|
|
1093
|
+
const names = toolUses.map((t) => t.name).join(", ");
|
|
1094
|
+
const hasText = content.some(isTextBlock);
|
|
1095
|
+
const kind = toolUses.length > 0 && hasText ? "mixed" : toolUses.length > 0 ? "tool" : "message";
|
|
1096
|
+
const observationRepeat = loopCfg.mode === "steer-then-cut" && toolUses.length > 0 && toolUses.every((use) => {
|
|
1097
|
+
const tool = this.a.tools.get(use.name);
|
|
1098
|
+
return tool?.mutating === false && (tool.riskTier === "safe" || (tool.capabilities?.length ?? 0) > 0);
|
|
1099
|
+
});
|
|
1100
|
+
const repeatMultiplier = observationRepeat ? 2 : 1;
|
|
1101
|
+
const detail = kind === "tool" ? `"${names}" called with effectively identical inputs ${this.toolLoopCount} times in a row` : kind === "mixed" ? `"${names}" + same text repeated ${this.toolLoopCount} times in a row` : `same assistant text repeated ${this.toolLoopCount} times in a row`;
|
|
1102
|
+
const cutAt = (loopCfg.mode === "cut" ? loopCfg.steerThreshold : loopCfg.cutThreshold) * repeatMultiplier;
|
|
1103
|
+
if (this.toolLoopCount >= cutAt) {
|
|
1104
|
+
this.a.logger.warn(`Loop detected: ${detail} \u2014 stopping to prevent infinite loop.`);
|
|
1105
|
+
this.a.events.emit("tool.loop_detected", {
|
|
1106
|
+
sessionId: resolveEventSessionId(this.a.ctx),
|
|
1107
|
+
ctx: this.a.ctx,
|
|
1108
|
+
tools: names,
|
|
1109
|
+
repeatCount: this.toolLoopCount,
|
|
1110
|
+
iteration: iterationIndex,
|
|
1111
|
+
kind,
|
|
1112
|
+
action: "cut",
|
|
1113
|
+
scope: "iteration"
|
|
1114
|
+
});
|
|
1115
|
+
const summary = kind === "message" ? `[Loop detected: same assistant message repeated ${this.toolLoopCount}\xD7 \u2014 stopping to prevent infinite repetition.]` : `[Loop detected: ${detail} \u2014 stopping to prevent infinite repetition.]`;
|
|
1116
|
+
return { cut: true, cutSummary: summary };
|
|
1117
|
+
}
|
|
1118
|
+
if (loopCfg.mode === "steer-then-cut" && this.toolLoopCount >= loopCfg.steerThreshold * repeatMultiplier && !this.iterationSteerDone) {
|
|
1119
|
+
this.iterationSteerDone = true;
|
|
1120
|
+
this.a.logger.warn(`Loop detected: ${detail} \u2014 steering the model to change approach.`);
|
|
1121
|
+
this.a.events.emit("tool.loop_detected", {
|
|
1122
|
+
sessionId: resolveEventSessionId(this.a.ctx),
|
|
1123
|
+
ctx: this.a.ctx,
|
|
1124
|
+
tools: names,
|
|
1125
|
+
repeatCount: this.toolLoopCount,
|
|
1126
|
+
iteration: iterationIndex,
|
|
1127
|
+
kind,
|
|
1128
|
+
action: "steer",
|
|
1129
|
+
scope: "iteration"
|
|
1130
|
+
});
|
|
1131
|
+
queueSteer(
|
|
1132
|
+
`[loop-detector] Your last ${this.toolLoopCount} responses were effectively identical (${detail}). This approach is not working. Change strategy: use a different tool, different arguments, or a different plan \u2014 or explain what is blocking you and stop. Repeating the same response ${cutAt - this.toolLoopCount} more time(s) will terminate the turn.`
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
} else {
|
|
1136
|
+
this.lastToolSignature = "";
|
|
1137
|
+
this.toolLoopCount = 0;
|
|
1138
|
+
this.iterationSteerDone = false;
|
|
1139
|
+
}
|
|
1140
|
+
if (loopCfg.mode === "steer-then-cut") {
|
|
1141
|
+
for (const u of toolUses) {
|
|
1142
|
+
const key = `${u.name}:${hashSmall(stableStringify(u.input ?? {}))}`;
|
|
1143
|
+
this.recentCallKeys.push(key);
|
|
1144
|
+
if (this.recentCallKeys.length > loopCfg.windowSize) this.recentCallKeys.shift();
|
|
1145
|
+
if (this.steeredCallKeys.has(key)) continue;
|
|
1146
|
+
let count = 0;
|
|
1147
|
+
for (const k of this.recentCallKeys) if (k === key) count++;
|
|
1148
|
+
const tool = this.a.tools.get(u.name);
|
|
1149
|
+
const observationThreshold = tool?.mutating === false && (tool.riskTier === "safe" || (tool.capabilities?.length ?? 0) > 0) ? loopCfg.callRepeatThreshold * 2 : loopCfg.callRepeatThreshold;
|
|
1150
|
+
if (count < observationThreshold) continue;
|
|
1151
|
+
this.steeredCallKeys.add(key);
|
|
1152
|
+
const preview = JSON.stringify(u.input ?? {}).slice(0, 160);
|
|
1153
|
+
this.a.logger.warn(
|
|
1154
|
+
`Loop detected: "${u.name}" called with identical arguments ${count}\xD7 within the last ${loopCfg.windowSize} tool calls \u2014 steering the model to change approach.`
|
|
1155
|
+
);
|
|
1156
|
+
this.a.events.emit("tool.loop_detected", {
|
|
1157
|
+
sessionId: resolveEventSessionId(this.a.ctx),
|
|
1158
|
+
ctx: this.a.ctx,
|
|
1159
|
+
tools: u.name,
|
|
1160
|
+
repeatCount: count,
|
|
1161
|
+
iteration: iterationIndex,
|
|
1162
|
+
kind: "tool",
|
|
1163
|
+
action: "steer",
|
|
1164
|
+
scope: "call"
|
|
1165
|
+
});
|
|
1166
|
+
queueSteer(
|
|
1167
|
+
`[loop-detector] You have called ${u.name}(${preview}) ${count} times with identical arguments within the last ${loopCfg.windowSize} tool calls. The result will not change. Do not repeat this call \u2014 use what you already know, try a different approach, or explain the blocker.`
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return { cut: false };
|
|
1172
|
+
}
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
// src/core/iteration-limit.ts
|
|
1176
|
+
function requestLimitExtension(opts) {
|
|
1177
|
+
const { events, sessionId, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
|
|
1178
|
+
return new Promise((resolve) => {
|
|
1179
|
+
let resolved = false;
|
|
1180
|
+
const timerFired = () => {
|
|
1181
|
+
if (!resolved) {
|
|
1182
|
+
resolved = true;
|
|
1183
|
+
resolve(0);
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
const timer = setTimeout(timerFired, timeoutMs);
|
|
1187
|
+
timer.unref();
|
|
1188
|
+
const deny = () => {
|
|
1189
|
+
if (!resolved) {
|
|
1190
|
+
resolved = true;
|
|
1191
|
+
clearTimeout(timer);
|
|
1192
|
+
resolve(0);
|
|
1193
|
+
}
|
|
1194
|
+
};
|
|
1195
|
+
const grant = (extra) => {
|
|
1196
|
+
if (!resolved) {
|
|
1197
|
+
resolved = true;
|
|
1198
|
+
clearTimeout(timer);
|
|
1199
|
+
resolve(Math.max(0, extra));
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
events.emit("iteration.limit_reached", {
|
|
1203
|
+
sessionId,
|
|
1204
|
+
currentIterations,
|
|
1205
|
+
currentLimit,
|
|
1206
|
+
grant,
|
|
1207
|
+
deny
|
|
1208
|
+
});
|
|
1209
|
+
if (autoExtend) {
|
|
1210
|
+
setImmediate(() => {
|
|
1211
|
+
if (!resolved) {
|
|
1212
|
+
resolved = true;
|
|
1213
|
+
clearTimeout(timer);
|
|
1214
|
+
resolve(100);
|
|
1215
|
+
}
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// src/core/agent-loop.ts
|
|
1222
|
+
function toError(err) {
|
|
1223
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
1224
|
+
}
|
|
1225
|
+
function recordSelfHealingRetry(a, err, reason) {
|
|
1226
|
+
const projectRoot = a.ctx.projectRoot;
|
|
1227
|
+
if (!projectRoot) return;
|
|
1228
|
+
void recordPromptJournalEntry({
|
|
1229
|
+
projectRoot,
|
|
1230
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1231
|
+
category: "self_healing_retry",
|
|
1232
|
+
content: toErrorMessage(err),
|
|
1233
|
+
decisionReason: reason,
|
|
1234
|
+
model: a.ctx.model,
|
|
1235
|
+
provider: a.ctx.provider?.id,
|
|
1236
|
+
activeTools: a.ctx.tools?.map((tool) => tool.name) ?? []
|
|
1237
|
+
}).catch(() => {
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
function recordAutonomousContinue(a, text) {
|
|
1241
|
+
const projectRoot = a.ctx.projectRoot;
|
|
1242
|
+
if (!projectRoot) return;
|
|
1243
|
+
void recordPromptJournalEntry({
|
|
1244
|
+
projectRoot,
|
|
1245
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1246
|
+
category: "autonomous_next_step",
|
|
1247
|
+
content: text,
|
|
1248
|
+
decisionReason: "text-marker autonomous continue",
|
|
1249
|
+
model: a.ctx.model,
|
|
1250
|
+
provider: a.ctx.provider?.id,
|
|
1251
|
+
activeTools: a.ctx.tools?.map((tool) => tool.name) ?? []
|
|
1252
|
+
}).catch(() => {
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
function signalAbortReason(signal) {
|
|
1256
|
+
const r = signal.reason;
|
|
1257
|
+
if (r instanceof Error) return r.message || r.name;
|
|
1258
|
+
if (typeof r === "string" && r.length > 0) return r;
|
|
1259
|
+
return "aborted";
|
|
1260
|
+
}
|
|
1261
|
+
function createAgentLoopHandler(a, handlers) {
|
|
1262
|
+
const checkMailbox = attachMailboxChecker(a);
|
|
1263
|
+
attachSessionNotes(a);
|
|
1264
|
+
const fleetPulseCfg = (() => {
|
|
1265
|
+
try {
|
|
1266
|
+
return typeof a.container?.has === "function" && a.container.has(TOKENS.ConfigStore) ? a.container.resolve(TOKENS.ConfigStore).get().fleet?.pulse : void 0;
|
|
1267
|
+
} catch {
|
|
1268
|
+
return void 0;
|
|
1269
|
+
}
|
|
1270
|
+
})();
|
|
1271
|
+
const getFleetPulse = attachFleetPulse(a, fleetPulseCfg);
|
|
1272
|
+
const pulseEveryN = Math.max(1, fleetPulseCfg?.everyNIterations ?? 5);
|
|
1273
|
+
const backgroundCoordination = () => a.ctx.meta["coordinationContextMode"] === "background";
|
|
1274
|
+
const loopContext = createAgentLoopContextManager(a, handlers);
|
|
1275
|
+
function foldBlockIntoConversation(block) {
|
|
1276
|
+
if (!a.ctx.state.appendBlockToLastUserMessage(block)) {
|
|
1277
|
+
a.ctx.state.appendMessage({ role: "user", content: [block], origin: "runtime" });
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
function injectPendingBtwNotes(onMailboxBlock) {
|
|
1281
|
+
const notes = consumeBtwNotes(a.ctx);
|
|
1282
|
+
if (notes.length === 0) return;
|
|
1283
|
+
const mailboxNotes = notes.filter((note) => note.startsWith("[MAILBOX BTW]"));
|
|
1284
|
+
const regularNotes = notes.filter((note) => !note.startsWith("[MAILBOX BTW]"));
|
|
1285
|
+
if (regularNotes.length > 0) {
|
|
1286
|
+
foldBlockIntoConversation({ type: "text", text: buildBtwBlock(regularNotes) });
|
|
1287
|
+
}
|
|
1288
|
+
if (mailboxNotes.length > 0) {
|
|
1289
|
+
const block = { type: "text", text: buildBtwBlock(mailboxNotes) };
|
|
1290
|
+
foldBlockIntoConversation(block);
|
|
1291
|
+
onMailboxBlock?.(block);
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
function injectPendingSessionNotes() {
|
|
1295
|
+
const notes = consumeSessionNotes(a.ctx);
|
|
1296
|
+
if (notes.length === 0) return;
|
|
1297
|
+
foldBlockIntoConversation({ type: "text", text: buildSessionNoteBlock(notes) });
|
|
1298
|
+
}
|
|
1299
|
+
function injectQueueAwareness() {
|
|
1300
|
+
const items = consumeQueuedMessagesUpdate(a.ctx);
|
|
1301
|
+
if (!items) return;
|
|
1302
|
+
foldBlockIntoConversation({ type: "text", text: buildQueuedMessagesBlock(items) });
|
|
1303
|
+
}
|
|
1304
|
+
async function checkIterationLimit(iterationIndex, limit, hasHardLimit, currentIterations, delegateSummaries) {
|
|
1305
|
+
if (hasHardLimit && iterationIndex >= limit) {
|
|
1306
|
+
const extendBy = await requestLimitExtension({
|
|
1307
|
+
events: a.events,
|
|
1308
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1309
|
+
currentIterations,
|
|
1310
|
+
currentLimit: limit,
|
|
1311
|
+
autoExtend: a.autoExtendLimit
|
|
1312
|
+
});
|
|
1313
|
+
if (extendBy > 0) {
|
|
1314
|
+
const newLimit = limit + extendBy;
|
|
1315
|
+
a.logger.info(`Iteration limit extended by ${extendBy} (new limit: ${newLimit})`);
|
|
1316
|
+
return { limit: newLimit };
|
|
1317
|
+
}
|
|
1318
|
+
return {
|
|
1319
|
+
limit,
|
|
1320
|
+
exit: { status: "max_iterations", iterations: currentIterations, delegateSummaries }
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
return { limit };
|
|
1324
|
+
}
|
|
1325
|
+
async function runInner(inputPayload, opts, controller, autonomousContinue) {
|
|
1326
|
+
await a.pipelines.userInput.run(inputPayload);
|
|
1327
|
+
recordUserIntentEvidence(a.ctx, inputPayload.text);
|
|
1328
|
+
await a.ctx.session.append({
|
|
1329
|
+
type: "user_input",
|
|
1330
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1331
|
+
content: inputPayload.content
|
|
1332
|
+
});
|
|
1333
|
+
const inputOrigin = isRuntimeContextInput(inputPayload.text) ? "runtime" : "user_input";
|
|
1334
|
+
a.ctx.state.appendMessage({
|
|
1335
|
+
role: "user",
|
|
1336
|
+
content: inputPayload.content,
|
|
1337
|
+
origin: inputOrigin
|
|
1338
|
+
});
|
|
1339
|
+
const promptIndex = a.ctx.messages.filter((m) => m.role === "user").length - 1;
|
|
1340
|
+
const preview = inputPayload.text.slice(0, 80) + (inputPayload.text.length > 80 ? "\u2026" : "");
|
|
1341
|
+
await a.ctx.session.writeCheckpoint(promptIndex, preview);
|
|
1342
|
+
try {
|
|
1343
|
+
await a.ctx.flushConversationJournal();
|
|
1344
|
+
await a.ctx.session.flush();
|
|
1345
|
+
} catch (err) {
|
|
1346
|
+
(a.logger.debug ?? a.logger.warn)?.(`session boundary flush failed: ${toErrorMessage(err)}`);
|
|
1347
|
+
}
|
|
1348
|
+
clearPendingNextSteps(a.ctx);
|
|
1349
|
+
let finalText = "";
|
|
1350
|
+
let iterations = 0;
|
|
1351
|
+
const delegateSummaries = [];
|
|
1352
|
+
let effectiveLimit = opts.maxIterations ?? a.maxIterations;
|
|
1353
|
+
const hasHardLimit = effectiveLimit > 0 && Number.isFinite(effectiveLimit);
|
|
1354
|
+
let recoveryRetries = 0;
|
|
1355
|
+
const pendingMailboxBlocks = [];
|
|
1356
|
+
function clearEvaluatedMailboxBlocks() {
|
|
1357
|
+
if (pendingMailboxBlocks.length === 0) return;
|
|
1358
|
+
const cleaned = removeInjectedMailboxBlocks(a.ctx.messages, pendingMailboxBlocks);
|
|
1359
|
+
pendingMailboxBlocks.length = 0;
|
|
1360
|
+
if (cleaned.changed) {
|
|
1361
|
+
a.ctx.state.replaceMessages(cleaned.messages);
|
|
1362
|
+
a.ctx.lastRealInputTokens = void 0;
|
|
1363
|
+
delete a.ctx.meta["realAnchorMsgCount"];
|
|
1364
|
+
loopContext.refreshContextRequestTokenStash({ force: true });
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
const loopDetector = new AgentLoopDetector(a);
|
|
1368
|
+
let pendingLoopSteer = null;
|
|
1369
|
+
let todoReconcileSteers = 0;
|
|
1370
|
+
function queueLoopSteer(text) {
|
|
1371
|
+
pendingLoopSteer = pendingLoopSteer ? `${pendingLoopSteer}
|
|
1372
|
+
${text}` : text;
|
|
1373
|
+
}
|
|
1374
|
+
const onSubagentDone = ({ summary, ok }) => {
|
|
1375
|
+
delegateSummaries.push({ summary, ok });
|
|
1376
|
+
};
|
|
1377
|
+
const offSubagentDone = a.events.on("subagent.done", onSubagentDone);
|
|
1378
|
+
const diRunner = a.container.has(TOKENS.ProviderRunner) ? a.container.resolve(TOKENS.ProviderRunner) : null;
|
|
1379
|
+
const statusTracker = a.container.safeResolve(TOKENS.ProviderModelStatusTracker);
|
|
1380
|
+
const baseRunner = diRunner ? (ctx, req) => diRunner.run({
|
|
1381
|
+
provider: providerBoundToRequest(req) ?? ctx.provider,
|
|
1382
|
+
request: req,
|
|
1383
|
+
signal: controller.signal,
|
|
1384
|
+
ctx,
|
|
1385
|
+
events: a.events,
|
|
1386
|
+
retry: a.retry,
|
|
1387
|
+
logger: a.logger,
|
|
1388
|
+
tracer: a.tracer,
|
|
1389
|
+
...statusTracker ? { statusTracker } : {}
|
|
1390
|
+
}) : async (ctx, req) => runProviderWithRetry({
|
|
1391
|
+
provider: providerBoundToRequest(req) ?? ctx.provider,
|
|
1392
|
+
request: req,
|
|
1393
|
+
signal: controller.signal,
|
|
1394
|
+
ctx,
|
|
1395
|
+
events: a.events,
|
|
1396
|
+
retry: a.retry,
|
|
1397
|
+
logger: a.logger,
|
|
1398
|
+
tracer: a.tracer,
|
|
1399
|
+
...statusTracker ? { statusTracker } : {}
|
|
1400
|
+
});
|
|
1401
|
+
const customRunner = a.extensions.wrapProviderRunner(baseRunner);
|
|
1402
|
+
try {
|
|
1403
|
+
for (let i = 0; ; i++) {
|
|
1404
|
+
iterations = i + 1;
|
|
1405
|
+
if (controller.signal.aborted) {
|
|
1406
|
+
return {
|
|
1407
|
+
status: "aborted",
|
|
1408
|
+
iterations,
|
|
1409
|
+
abortReason: signalAbortReason(controller.signal)
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
try {
|
|
1413
|
+
await a.ctx.session.writeInFlightMarker(`iteration ${i} / max ${a.maxIterations}`);
|
|
1414
|
+
} catch (err) {
|
|
1415
|
+
(a.logger.debug ?? a.logger.warn)?.(
|
|
1416
|
+
`in-flight marker write failed: ${toErrorMessage(err)}`
|
|
1417
|
+
);
|
|
1418
|
+
}
|
|
1419
|
+
if (autonomousContinue) {
|
|
1420
|
+
consumeAutonomousContinue(a.ctx);
|
|
1421
|
+
}
|
|
1422
|
+
const limitCheck = await checkIterationLimit(
|
|
1423
|
+
i,
|
|
1424
|
+
effectiveLimit,
|
|
1425
|
+
hasHardLimit,
|
|
1426
|
+
iterations,
|
|
1427
|
+
delegateSummaries
|
|
1428
|
+
);
|
|
1429
|
+
effectiveLimit = limitCheck.limit;
|
|
1430
|
+
if (limitCheck.exit) {
|
|
1431
|
+
return { ...limitCheck.exit, finalText };
|
|
1432
|
+
}
|
|
1433
|
+
await a.extensions.runBeforeIteration(a.ctx, i);
|
|
1434
|
+
a.events.emit("iteration.started", {
|
|
1435
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1436
|
+
ctx: a.ctx,
|
|
1437
|
+
index: i
|
|
1438
|
+
});
|
|
1439
|
+
injectPendingBtwNotes((block) => pendingMailboxBlocks.push(block));
|
|
1440
|
+
injectPendingSessionNotes();
|
|
1441
|
+
injectQueueAwareness();
|
|
1442
|
+
if (pendingLoopSteer) {
|
|
1443
|
+
foldBlockIntoConversation({ type: "text", text: pendingLoopSteer });
|
|
1444
|
+
pendingLoopSteer = null;
|
|
1445
|
+
}
|
|
1446
|
+
if (!backgroundCoordination() && (i % pulseEveryN === 1 || pulseEveryN === 1)) {
|
|
1447
|
+
try {
|
|
1448
|
+
const pulse = await getFleetPulse();
|
|
1449
|
+
if (pulse) foldBlockIntoConversation(pulse);
|
|
1450
|
+
} catch {
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
const mailboxResult = await injectPendingMailboxMessages(
|
|
1454
|
+
checkMailbox,
|
|
1455
|
+
(block) => {
|
|
1456
|
+
foldBlockIntoConversation(block);
|
|
1457
|
+
pendingMailboxBlocks.push(block);
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
events: {
|
|
1461
|
+
emit: (type, payload) => {
|
|
1462
|
+
a.events.emit(type, payload);
|
|
1463
|
+
}
|
|
1464
|
+
},
|
|
1465
|
+
logger: a.logger
|
|
1466
|
+
},
|
|
1467
|
+
backgroundCoordination() ? "background" : "inline"
|
|
1468
|
+
);
|
|
1469
|
+
if (mailboxResult.interrupt) {
|
|
1470
|
+
const reason = `interrupted: ${mailboxResult.interruptReason ?? "operator request"}`;
|
|
1471
|
+
return { status: "aborted", iterations, abortReason: reason, finalText };
|
|
1472
|
+
}
|
|
1473
|
+
const {
|
|
1474
|
+
req,
|
|
1475
|
+
provider: requestProvider,
|
|
1476
|
+
preFlight
|
|
1477
|
+
} = await loopContext.buildRequestWithPreflightCompaction(opts);
|
|
1478
|
+
await a.ctx.session.append({
|
|
1479
|
+
type: "llm_request",
|
|
1480
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1481
|
+
model: req.model,
|
|
1482
|
+
messageCount: req.messages.length,
|
|
1483
|
+
estimatedInputTokens: preFlight.total,
|
|
1484
|
+
toolCount: (req.tools ?? []).length
|
|
1485
|
+
}).catch(() => {
|
|
1486
|
+
});
|
|
1487
|
+
let res;
|
|
1488
|
+
try {
|
|
1489
|
+
res = await customRunner(a.ctx, req);
|
|
1490
|
+
const key = loopContext.calibrationKey(req.model);
|
|
1491
|
+
const cal = getCalibrationState(key);
|
|
1492
|
+
const calibratedTotal = cal.calibrated ? Math.round(preFlight.total * Math.min(1.5, Math.max(0.5, cal.ratio))) : preFlight.total;
|
|
1493
|
+
const realInputTokens = effectiveInputTokens(res.usage);
|
|
1494
|
+
recordActualUsage(realInputTokens, calibratedTotal, key);
|
|
1495
|
+
const previousRealInput = a.ctx.lastRealInputTokens;
|
|
1496
|
+
const previousAnchorMsgCount = typeof a.ctx.meta?.["realAnchorMsgCount"] === "number" ? a.ctx.meta["realAnchorMsgCount"] : void 0;
|
|
1497
|
+
const anchorIsPlausible = realInputTokens >= calibratedTotal * 0.5;
|
|
1498
|
+
const anchorAdvanced = previousRealInput === void 0 || realInputTokens > previousRealInput || previousAnchorMsgCount !== void 0 && loopContext.lastPreFlightMsgCount < previousAnchorMsgCount;
|
|
1499
|
+
if (realInputTokens > 0 && anchorIsPlausible && anchorAdvanced) {
|
|
1500
|
+
a.ctx.lastRealInputTokens = realInputTokens;
|
|
1501
|
+
a.ctx.meta["realAnchorMsgCount"] = loopContext.lastPreFlightMsgCount;
|
|
1502
|
+
}
|
|
1503
|
+
recoveryRetries = 0;
|
|
1504
|
+
} catch (err) {
|
|
1505
|
+
if (controller.signal.aborted) {
|
|
1506
|
+
a.events.emit("error", {
|
|
1507
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1508
|
+
err: toError(err),
|
|
1509
|
+
phase: "provider"
|
|
1510
|
+
});
|
|
1511
|
+
return {
|
|
1512
|
+
status: "aborted",
|
|
1513
|
+
iterations,
|
|
1514
|
+
error: toWrongStackError(err, "AGENT_ABORTED"),
|
|
1515
|
+
abortReason: signalAbortReason(controller.signal)
|
|
1516
|
+
};
|
|
1517
|
+
}
|
|
1518
|
+
const extDecision = await a.extensions.runOnError(a.ctx, err, "provider", i);
|
|
1519
|
+
if (extDecision) {
|
|
1520
|
+
if (extDecision.action === "fail") {
|
|
1521
|
+
a.events.emit("error", {
|
|
1522
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1523
|
+
err: toError(err),
|
|
1524
|
+
phase: "provider"
|
|
1525
|
+
});
|
|
1526
|
+
return {
|
|
1527
|
+
status: "failed",
|
|
1528
|
+
iterations,
|
|
1529
|
+
error: toWrongStackError(err),
|
|
1530
|
+
delegateSummaries
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
if (extDecision.action === "continue") {
|
|
1534
|
+
await a.extensions.runAfterIteration(a.ctx, i);
|
|
1535
|
+
continue;
|
|
1536
|
+
}
|
|
1537
|
+
if (extDecision.action === "retry") {
|
|
1538
|
+
recoveryRetries++;
|
|
1539
|
+
if (recoveryRetries > 2) {
|
|
1540
|
+
a.events.emit("error", {
|
|
1541
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1542
|
+
err: toError(err),
|
|
1543
|
+
phase: "provider"
|
|
1544
|
+
});
|
|
1545
|
+
return {
|
|
1546
|
+
status: "failed",
|
|
1547
|
+
iterations,
|
|
1548
|
+
error: toWrongStackError(err),
|
|
1549
|
+
delegateSummaries
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
if (extDecision.model) a.ctx.model = extDecision.model;
|
|
1553
|
+
a.logger.info("Extension requested retry; retrying turn");
|
|
1554
|
+
recordSelfHealingRetry(a, err, "extension-requested provider retry");
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
const recovered = await a.errorHandler.recover(err, a.ctx);
|
|
1559
|
+
if (!recovered || recovered.action === "fail") {
|
|
1560
|
+
a.events.emit("error", {
|
|
1561
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1562
|
+
err: toError(err),
|
|
1563
|
+
phase: "provider"
|
|
1564
|
+
});
|
|
1565
|
+
return {
|
|
1566
|
+
status: "failed",
|
|
1567
|
+
iterations,
|
|
1568
|
+
error: toWrongStackError(recovered?.error ?? err),
|
|
1569
|
+
delegateSummaries
|
|
1570
|
+
};
|
|
1571
|
+
}
|
|
1572
|
+
if (recovered.action === "retry") {
|
|
1573
|
+
recoveryRetries++;
|
|
1574
|
+
if (recoveryRetries > 2) {
|
|
1575
|
+
a.events.emit("error", {
|
|
1576
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1577
|
+
err: toError(err),
|
|
1578
|
+
phase: "provider"
|
|
1579
|
+
});
|
|
1580
|
+
return { status: "failed", iterations, error: toWrongStackError(err) };
|
|
1581
|
+
}
|
|
1582
|
+
if (recovered.model) a.ctx.model = recovered.model;
|
|
1583
|
+
a.logger.info(`Recovered provider error via ${recovered.reason}; retrying turn`);
|
|
1584
|
+
recordSelfHealingRetry(a, err, recovered.reason);
|
|
1585
|
+
continue;
|
|
1586
|
+
}
|
|
1587
|
+
recoveryRetries = 0;
|
|
1588
|
+
res = recovered.response;
|
|
1589
|
+
}
|
|
1590
|
+
clearEvaluatedMailboxBlocks();
|
|
1591
|
+
const responseProvider = providerBoundToRequest(req) ?? requestProvider;
|
|
1592
|
+
const responseResult = await handlers.response.processResponse(res, req, responseProvider);
|
|
1593
|
+
await loopContext.refreshProviderContextLimit(responseProvider, req.model, {
|
|
1594
|
+
probe: false
|
|
1595
|
+
});
|
|
1596
|
+
if (responseResult.finalText) {
|
|
1597
|
+
a.ctx.meta["lastAgentOutput"] = responseResult.finalText;
|
|
1598
|
+
}
|
|
1599
|
+
if (responseResult.aborted) {
|
|
1600
|
+
return {
|
|
1601
|
+
status: "aborted",
|
|
1602
|
+
iterations,
|
|
1603
|
+
finalText: responseResult.finalText,
|
|
1604
|
+
delegateSummaries,
|
|
1605
|
+
abortReason: signalAbortReason(controller.signal)
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
if (responseResult.done) {
|
|
1609
|
+
return {
|
|
1610
|
+
status: "done",
|
|
1611
|
+
iterations,
|
|
1612
|
+
finalText: responseResult.finalText,
|
|
1613
|
+
delegateSummaries
|
|
1614
|
+
};
|
|
1615
|
+
}
|
|
1616
|
+
finalText = responseResult.finalText;
|
|
1617
|
+
const toolUses = res.content.filter(isToolUseBlock);
|
|
1618
|
+
const loopCheck = loopDetector.checkIteration(i, res.content, toolUses, queueLoopSteer);
|
|
1619
|
+
if (loopCheck.cut) {
|
|
1620
|
+
return {
|
|
1621
|
+
status: "max_iterations",
|
|
1622
|
+
iterations,
|
|
1623
|
+
finalText: finalText || loopCheck.cutSummary || "",
|
|
1624
|
+
delegateSummaries
|
|
1625
|
+
};
|
|
1626
|
+
}
|
|
1627
|
+
if (toolUses.length === 0) {
|
|
1628
|
+
await loopContext.compactContextIfNeeded();
|
|
1629
|
+
loopContext.emitContextPct();
|
|
1630
|
+
a.events.emit("iteration.completed", {
|
|
1631
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1632
|
+
ctx: a.ctx,
|
|
1633
|
+
index: i
|
|
1634
|
+
});
|
|
1635
|
+
if (a.ctx.agentId === "leader" && a.tools.get("todo") !== void 0 && hasOpenTodos(a.ctx.todos) && todoReconcileSteers < 2) {
|
|
1636
|
+
todoReconcileSteers++;
|
|
1637
|
+
queueLoopSteer(
|
|
1638
|
+
"[todo-reconciliation] The live todo/Kanban list still has open work, but you tried to end the turn without reconciling it. Call the `todo` tool now with the complete current list. Mark work you actually finished as completed, put the one item you are actively working on in_progress, and leave the rest pending. If the current item is genuinely unfinished, continue doing the work before answering; do not merely repeat the previous final response or emit <nextsteps>.\nCanonical live list:\n" + formatTodosForModel(a.ctx.todos) + (hasKanbanBoundTodos(a.ctx.todos) ? "\nEach <kanban board/task> binding must be resent verbatim as `kanbanBoardId`/`kanbanTaskId`; a row without it is not applied to its card." : "")
|
|
1639
|
+
);
|
|
1640
|
+
await a.extensions.runAfterIteration(a.ctx, i);
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
if (autonomousContinue && responseResult.directive === "continue") {
|
|
1644
|
+
recordAutonomousContinue(a, finalText);
|
|
1645
|
+
await a.extensions.runAfterIteration(a.ctx, i);
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
if (autonomousContinue && responseResult.directive === "stop") {
|
|
1649
|
+
return { status: "done", iterations, finalText, delegateSummaries };
|
|
1650
|
+
}
|
|
1651
|
+
return { status: "done", iterations, finalText, delegateSummaries };
|
|
1652
|
+
}
|
|
1653
|
+
try {
|
|
1654
|
+
await handlers.tools.executeTools(toolUses);
|
|
1655
|
+
} catch (toolErr) {
|
|
1656
|
+
if (controller.signal.aborted) {
|
|
1657
|
+
return {
|
|
1658
|
+
status: "aborted",
|
|
1659
|
+
iterations,
|
|
1660
|
+
finalText,
|
|
1661
|
+
delegateSummaries,
|
|
1662
|
+
abortReason: signalAbortReason(controller.signal)
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
throw toolErr;
|
|
1666
|
+
}
|
|
1667
|
+
if (autonomousContinue && consumeAutonomousContinue(a.ctx)) {
|
|
1668
|
+
await loopContext.compactContextIfNeeded();
|
|
1669
|
+
loopContext.emitContextPct();
|
|
1670
|
+
a.events.emit("iteration.completed", {
|
|
1671
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1672
|
+
ctx: a.ctx,
|
|
1673
|
+
index: i
|
|
1674
|
+
});
|
|
1675
|
+
await a.extensions.runAfterIteration(a.ctx, i);
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
await loopContext.compactContextIfNeeded();
|
|
1679
|
+
loopContext.emitContextPct();
|
|
1680
|
+
a.events.emit("iteration.completed", {
|
|
1681
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1682
|
+
ctx: a.ctx,
|
|
1683
|
+
index: i
|
|
1684
|
+
});
|
|
1685
|
+
await a.extensions.runAfterIteration(a.ctx, i);
|
|
1686
|
+
if (autonomousContinue && responseResult.directive === "continue") {
|
|
1687
|
+
continue;
|
|
1688
|
+
}
|
|
1689
|
+
if (autonomousContinue && responseResult.directive === "stop") {
|
|
1690
|
+
return { status: "done", iterations, finalText, delegateSummaries };
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
} finally {
|
|
1694
|
+
clearEvaluatedMailboxBlocks();
|
|
1695
|
+
offSubagentDone();
|
|
1696
|
+
const reason = controller.signal.aborted ? "aborted" : "clean";
|
|
1697
|
+
try {
|
|
1698
|
+
await a.ctx.session.clearInFlightMarker(reason);
|
|
1699
|
+
await a.ctx.session.flush();
|
|
1700
|
+
} catch (err) {
|
|
1701
|
+
(a.logger.debug ?? a.logger.warn)?.(
|
|
1702
|
+
`in-flight marker clear failed: ${toErrorMessage(err)}`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return { runInner };
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
// src/core/agent-tools.ts
|
|
1711
|
+
var DIFF_TOOL_NAMES = /* @__PURE__ */ new Set(["edit", "write", "replace", "patch", "diff"]);
|
|
1712
|
+
var DIFF_TOOL_EVENT_PREVIEW_MAX = 16e3;
|
|
1713
|
+
var SAGE_EVENT_MAX = 2e3;
|
|
1714
|
+
function createAgentToolHandler(a) {
|
|
1715
|
+
async function executeSingleWithDecision(tool, use, preToolContext) {
|
|
1716
|
+
const start = Date.now();
|
|
1717
|
+
try {
|
|
1718
|
+
const result = await a.toolExecutor.executeTool(
|
|
1719
|
+
tool,
|
|
1720
|
+
{
|
|
1721
|
+
type: "tool_use",
|
|
1722
|
+
id: use.id,
|
|
1723
|
+
name: use.name,
|
|
1724
|
+
input: use.input
|
|
1725
|
+
},
|
|
1726
|
+
a.ctx,
|
|
1727
|
+
a.perIterationOutputCapBytes,
|
|
1728
|
+
preToolContext
|
|
1729
|
+
);
|
|
1730
|
+
return { result: result.block, durationMs: Date.now() - start };
|
|
1731
|
+
} catch (err) {
|
|
1732
|
+
const msg = toErrorMessage(err);
|
|
1733
|
+
return {
|
|
1734
|
+
result: {
|
|
1735
|
+
type: "tool_result",
|
|
1736
|
+
tool_use_id: use.id,
|
|
1737
|
+
content: `Tool "${tool.name}" threw: ${msg}`,
|
|
1738
|
+
is_error: true
|
|
1739
|
+
},
|
|
1740
|
+
durationMs: Date.now() - start
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
function waitForConfirm(info) {
|
|
1745
|
+
if (a.events.listenerCount("tool.confirm_needed") === 0) {
|
|
1746
|
+
console.log(
|
|
1747
|
+
JSON.stringify({
|
|
1748
|
+
level: "warn",
|
|
1749
|
+
event: "confirm.headless_fallback",
|
|
1750
|
+
tool: info.tool.name,
|
|
1751
|
+
toolUseId: info.toolUseId,
|
|
1752
|
+
message: `No tool.confirm_needed listener \u2014 auto-denying "${info.tool.name}" to avoid headless deadlock.`
|
|
1753
|
+
})
|
|
1754
|
+
);
|
|
1755
|
+
return Promise.resolve("deny");
|
|
1756
|
+
}
|
|
1757
|
+
return new Promise((resolve) => {
|
|
1758
|
+
const signal = a.ctx.signal;
|
|
1759
|
+
const onAbort = () => resolve("abort");
|
|
1760
|
+
if (signal.aborted) {
|
|
1761
|
+
resolve("abort");
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1765
|
+
a.events.emit("tool.confirm_needed", {
|
|
1766
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1767
|
+
tool: info.tool,
|
|
1768
|
+
input: info.input,
|
|
1769
|
+
toolUseId: info.toolUseId,
|
|
1770
|
+
suggestedPattern: info.suggestedPattern,
|
|
1771
|
+
decisionSource: info.decisionSource,
|
|
1772
|
+
riskTier: info.riskTier,
|
|
1773
|
+
boundaryReason: info.boundaryReason,
|
|
1774
|
+
resolve: (choice) => {
|
|
1775
|
+
signal.removeEventListener("abort", onAbort);
|
|
1776
|
+
resolve(choice);
|
|
1777
|
+
}
|
|
1778
|
+
});
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
function emitToolExecuted(toolUseId, toolName, durationMs, ok, mutating, input, content) {
|
|
1782
|
+
const sig = sizeSignals(toolName, content);
|
|
1783
|
+
const { body, sageLines } = splitSageOutputBlock(content);
|
|
1784
|
+
const sage = capSageLines(sageLines, SAGE_EVENT_MAX);
|
|
1785
|
+
const metadata = recordToolOutputEvidence(a.ctx, {
|
|
1786
|
+
toolUseId,
|
|
1787
|
+
toolName,
|
|
1788
|
+
input,
|
|
1789
|
+
content,
|
|
1790
|
+
ok,
|
|
1791
|
+
outputBytes: sig.outputBytes,
|
|
1792
|
+
outputTokens: sig.outputTokens,
|
|
1793
|
+
outputLines: sig.outputLines
|
|
1794
|
+
});
|
|
1795
|
+
a.events.emit("tool.executed", {
|
|
1796
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1797
|
+
...a.ctx.traceId ? { traceId: a.ctx.traceId } : {},
|
|
1798
|
+
...a.ctx.activeLogicalRequestId ? { logicalRequestId: a.ctx.activeLogicalRequestId } : {},
|
|
1799
|
+
...a.ctx.activePromptManifestId ? { promptManifestId: a.ctx.activePromptManifestId } : {},
|
|
1800
|
+
agentId: a.ctx.agentId,
|
|
1801
|
+
agentName: a.ctx.agentName,
|
|
1802
|
+
id: toolUseId,
|
|
1803
|
+
name: toolName,
|
|
1804
|
+
durationMs,
|
|
1805
|
+
ok,
|
|
1806
|
+
mutating,
|
|
1807
|
+
input,
|
|
1808
|
+
output: truncateForEvent(
|
|
1809
|
+
body,
|
|
1810
|
+
DIFF_TOOL_NAMES.has(toolName) ? DIFF_TOOL_EVENT_PREVIEW_MAX : void 0
|
|
1811
|
+
),
|
|
1812
|
+
...sage.length > 0 ? { sage } : {},
|
|
1813
|
+
outputBytes: sig.outputBytes,
|
|
1814
|
+
outputTokens: sig.outputTokens,
|
|
1815
|
+
outputLines: sig.outputLines,
|
|
1816
|
+
metadata,
|
|
1817
|
+
taskId: a.ctx.currentKanbanTaskId,
|
|
1818
|
+
boardId: a.ctx.currentKanbanBoardId,
|
|
1819
|
+
...typeof a.ctx.provider === "object" ? { provider: a.ctx.provider.id } : {},
|
|
1820
|
+
...a.ctx.model ? { model: a.ctx.model } : {}
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
async function executeTools(toolUses) {
|
|
1824
|
+
const selectedToolUses = await a.extensions.runBeforeToolExecution(a.ctx, toolUses);
|
|
1825
|
+
const { outputs } = await a.toolExecutor.executeBatch(
|
|
1826
|
+
selectedToolUses,
|
|
1827
|
+
a.ctx,
|
|
1828
|
+
a.executionStrategy
|
|
1829
|
+
);
|
|
1830
|
+
const useById = new Map(selectedToolUses.map((u) => [u.id, u]));
|
|
1831
|
+
const resultsForMessage = [];
|
|
1832
|
+
const sessionEvents = [];
|
|
1833
|
+
for (const { result, tool, durationMs } of outputs) {
|
|
1834
|
+
if (result.type === "tool_confirm_pending" && tool) {
|
|
1835
|
+
const decision = await waitForConfirm({
|
|
1836
|
+
tool,
|
|
1837
|
+
input: result.input,
|
|
1838
|
+
toolUseId: result.toolUseId,
|
|
1839
|
+
suggestedPattern: result.suggestedPattern,
|
|
1840
|
+
decisionSource: result.decisionSource,
|
|
1841
|
+
riskTier: result.riskTier,
|
|
1842
|
+
boundaryReason: result.boundaryReason
|
|
1843
|
+
});
|
|
1844
|
+
if (decision === "always") {
|
|
1845
|
+
try {
|
|
1846
|
+
await a.permission.trust({ tool: tool.name, pattern: result.suggestedPattern });
|
|
1847
|
+
a.events.emit("trust.persisted", {
|
|
1848
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1849
|
+
tool: tool.name,
|
|
1850
|
+
pattern: result.suggestedPattern,
|
|
1851
|
+
decision
|
|
1852
|
+
});
|
|
1853
|
+
} catch {
|
|
1854
|
+
}
|
|
1855
|
+
} else if (decision === "deny") {
|
|
1856
|
+
try {
|
|
1857
|
+
await a.permission.deny({ tool: tool.name, pattern: result.suggestedPattern });
|
|
1858
|
+
a.events.emit("trust.persisted", {
|
|
1859
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
1860
|
+
tool: tool.name,
|
|
1861
|
+
pattern: result.suggestedPattern,
|
|
1862
|
+
decision
|
|
1863
|
+
});
|
|
1864
|
+
} catch {
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
if (decision === "no") {
|
|
1868
|
+
const p = a.permission;
|
|
1869
|
+
p.denyOnce?.({ tool: tool.name, pattern: result.suggestedPattern });
|
|
1870
|
+
}
|
|
1871
|
+
const reRunResult = decision === "yes" || decision === "always" ? await executeSingleWithDecision(
|
|
1872
|
+
tool,
|
|
1873
|
+
{
|
|
1874
|
+
id: result.toolUseId,
|
|
1875
|
+
name: tool.name,
|
|
1876
|
+
input: result.input
|
|
1877
|
+
},
|
|
1878
|
+
result.preToolContext
|
|
1879
|
+
) : {
|
|
1880
|
+
result: {
|
|
1881
|
+
type: "tool_result",
|
|
1882
|
+
tool_use_id: result.toolUseId,
|
|
1883
|
+
content: decision === "abort" ? `Tool "${tool.name}" was not executed \u2014 the run was aborted while awaiting confirmation.` : decision === "deny" ? `Tool "${tool.name}" denied and blocked for this pattern.` : `Tool "${tool.name}" denied by user.`,
|
|
1884
|
+
is_error: true
|
|
1885
|
+
},
|
|
1886
|
+
durationMs: 0
|
|
1887
|
+
};
|
|
1888
|
+
const use2 = useById.get(reRunResult.result.tool_use_id);
|
|
1889
|
+
if (use2) {
|
|
1890
|
+
await a.pipelines.toolCall.run({
|
|
1891
|
+
toolUse: use2,
|
|
1892
|
+
result: reRunResult.result,
|
|
1893
|
+
ctx: a.ctx,
|
|
1894
|
+
tool
|
|
1895
|
+
});
|
|
1896
|
+
sessionEvents.push({
|
|
1897
|
+
type: "tool_result",
|
|
1898
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1899
|
+
id: reRunResult.result.tool_use_id,
|
|
1900
|
+
content: reRunResult.result.content,
|
|
1901
|
+
isError: !!reRunResult.result.is_error
|
|
1902
|
+
});
|
|
1903
|
+
emitToolExecuted(
|
|
1904
|
+
reRunResult.result.tool_use_id,
|
|
1905
|
+
tool.name,
|
|
1906
|
+
reRunResult.durationMs,
|
|
1907
|
+
!reRunResult.result.is_error,
|
|
1908
|
+
tool.mutating,
|
|
1909
|
+
result.input,
|
|
1910
|
+
reRunResult.result.content
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
resultsForMessage.push(reRunResult.result);
|
|
1914
|
+
continue;
|
|
1915
|
+
}
|
|
1916
|
+
if (result.type !== "tool_result") continue;
|
|
1917
|
+
resultsForMessage.push(result);
|
|
1918
|
+
const use = useById.get(result.tool_use_id);
|
|
1919
|
+
if (!use) continue;
|
|
1920
|
+
await a.pipelines.toolCall.run({ toolUse: use, result, ctx: a.ctx, tool: tool ?? void 0 });
|
|
1921
|
+
sessionEvents.push({
|
|
1922
|
+
type: "tool_result",
|
|
1923
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1924
|
+
id: result.tool_use_id,
|
|
1925
|
+
content: result.content,
|
|
1926
|
+
isError: !!result.is_error
|
|
1927
|
+
});
|
|
1928
|
+
emitToolExecuted(
|
|
1929
|
+
result.tool_use_id,
|
|
1930
|
+
use.name,
|
|
1931
|
+
durationMs,
|
|
1932
|
+
!result.is_error,
|
|
1933
|
+
tool?.mutating ?? false,
|
|
1934
|
+
use.input,
|
|
1935
|
+
result.content
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
if (sessionEvents.length > 0) {
|
|
1939
|
+
await (a.ctx.activeRunSessionWriter ?? a.ctx.session).appendBatch(sessionEvents);
|
|
1940
|
+
}
|
|
1941
|
+
const pendingContext = a.ctx.pendingPostToolContext;
|
|
1942
|
+
a.ctx.pendingPostToolContext = void 0;
|
|
1943
|
+
const contentBlocks = pendingContext ? [{ type: "text", text: pendingContext }, ...resultsForMessage] : resultsForMessage;
|
|
1944
|
+
a.ctx.state.appendMessage({ role: "user", content: contentBlocks });
|
|
1945
|
+
if (contentBlocks.length > 0) {
|
|
1946
|
+
a.ctx.toolAdjacencyDirty = true;
|
|
1947
|
+
}
|
|
1948
|
+
try {
|
|
1949
|
+
await a.ctx.flushConversationJournal();
|
|
1950
|
+
await (a.ctx.activeRunSessionWriter ?? a.ctx.session).flush();
|
|
1951
|
+
} catch (err) {
|
|
1952
|
+
(a.logger.debug ?? a.logger.warn)?.(
|
|
1953
|
+
`tool result boundary flush failed: ${toErrorMessage(err)}`
|
|
1954
|
+
);
|
|
1955
|
+
}
|
|
1956
|
+
await a.extensions.runAfterToolExecution(a.ctx, outputs);
|
|
1957
|
+
return resultsForMessage;
|
|
1958
|
+
}
|
|
1959
|
+
return { executeTools, executeSingleWithDecision };
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// src/core/agent.ts
|
|
1963
|
+
var Agent = class _Agent {
|
|
1964
|
+
container;
|
|
1965
|
+
tools;
|
|
1966
|
+
providers;
|
|
1967
|
+
events;
|
|
1968
|
+
pipelines;
|
|
1969
|
+
ctx;
|
|
1970
|
+
/** Max agent-loop iterations per run. Mutable so the TUI `/settings` picker
|
|
1971
|
+
* can apply a new value to the live session (takes effect next run). */
|
|
1972
|
+
maxIterations;
|
|
1973
|
+
executionStrategy;
|
|
1974
|
+
perIterationOutputCapBytes;
|
|
1975
|
+
plugins = [];
|
|
1976
|
+
toolExecutor;
|
|
1977
|
+
autoExtendLimit;
|
|
1978
|
+
/** Resolved loop-detector settings (see `tools.loopDetection`). */
|
|
1979
|
+
loopDetection;
|
|
1980
|
+
autonomousContinue;
|
|
1981
|
+
refreshSystemPrompt;
|
|
1982
|
+
tracer;
|
|
1983
|
+
extensions;
|
|
1984
|
+
_toolHandler;
|
|
1985
|
+
_responseHandler;
|
|
1986
|
+
_loopHandler;
|
|
1987
|
+
_logger;
|
|
1988
|
+
/**
|
|
1989
|
+
* Guards against concurrent `run()` calls on the same Agent instance.
|
|
1990
|
+
* `run()` mutates shared state (`ctx.signal`, `ctx.messages`, token
|
|
1991
|
+
* bookkeeping, compaction state) and a second concurrent call would
|
|
1992
|
+
* interleave those mutations with the first, producing an invalid
|
|
1993
|
+
* conversation or racing abort signals.
|
|
1994
|
+
*/
|
|
1995
|
+
_runInProgress = false;
|
|
1996
|
+
/**
|
|
1997
|
+
* Dedup window for byte-identical consecutive inputs. Only accidental
|
|
1998
|
+
* back-to-back duplicates fall inside it — terminal \r\n re-entrancy,
|
|
1999
|
+
* stuck-key bursts, client auto-resubmit loops. A deliberate re-send later
|
|
2000
|
+
* (the classic "continue" nudge after a model switch, or retyping the same
|
|
2001
|
+
* instruction after a failed run) must always execute.
|
|
2002
|
+
*/
|
|
2003
|
+
static INPUT_DEDUP_WINDOW_MS = 1500;
|
|
2004
|
+
/**
|
|
2005
|
+
* SHA-256 + submission time of the last committed input content. The pair
|
|
2006
|
+
* powers burst-only dedup: identical text skips a run solely when it lands
|
|
2007
|
+
* within {@link Agent.INPUT_DEDUP_WINDOW_MS} of the previous submission.
|
|
2008
|
+
*/
|
|
2009
|
+
_lastInputHash;
|
|
2010
|
+
_lastInputHashAt;
|
|
2011
|
+
constructor(init) {
|
|
2012
|
+
this.container = init.container;
|
|
2013
|
+
this.tools = init.tools;
|
|
2014
|
+
this.providers = init.providers;
|
|
2015
|
+
this.events = init.events;
|
|
2016
|
+
this.pipelines = init.pipelines;
|
|
2017
|
+
this.ctx = init.context;
|
|
2018
|
+
this.maxIterations = init.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
2019
|
+
this.executionStrategy = init.executionStrategy ?? "smart";
|
|
2020
|
+
this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
|
|
2021
|
+
this.autoExtendLimit = init.autoExtendLimit ?? false;
|
|
2022
|
+
this.loopDetection = resolveLoopDetection(init.loopDetection);
|
|
2023
|
+
this.autonomousContinue = init.autonomousContinue ?? false;
|
|
2024
|
+
this.refreshSystemPrompt = init.refreshSystemPrompt ?? false;
|
|
2025
|
+
this.tracer = init.tracer;
|
|
2026
|
+
this.extensions = init.extensions ?? new ExtensionRegistry();
|
|
2027
|
+
this._logger = this.container.resolve(TOKENS.Logger).child({ sessionId: this.ctx.session.id });
|
|
2028
|
+
this.extensions.setLogger(this._logger);
|
|
2029
|
+
this.toolExecutor = init.toolExecutor;
|
|
2030
|
+
this._toolHandler = createAgentToolHandler(this);
|
|
2031
|
+
this._responseHandler = createAgentResponseHandler(this);
|
|
2032
|
+
this._loopHandler = createAgentLoopHandler(this, {
|
|
2033
|
+
tools: this._toolHandler,
|
|
2034
|
+
response: this._responseHandler
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
get logger() {
|
|
2038
|
+
return this._logger;
|
|
2039
|
+
}
|
|
2040
|
+
get retry() {
|
|
2041
|
+
return this.container.resolve(TOKENS.RetryPolicy);
|
|
2042
|
+
}
|
|
2043
|
+
get errorHandler() {
|
|
2044
|
+
return this.container.resolve(TOKENS.ErrorHandler);
|
|
2045
|
+
}
|
|
2046
|
+
get permission() {
|
|
2047
|
+
return this.container.resolve(TOKENS.PermissionPolicy);
|
|
2048
|
+
}
|
|
2049
|
+
get renderer() {
|
|
2050
|
+
return this.container.safeResolve(TOKENS.Renderer);
|
|
2051
|
+
}
|
|
2052
|
+
disableInteractiveConfirmation() {
|
|
2053
|
+
this.toolExecutor.clearConfirmAwaiter();
|
|
2054
|
+
if (typeof this.permission.setPromptDelegate === "function") {
|
|
2055
|
+
this.permission.setPromptDelegate(void 0);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
register(tool) {
|
|
2059
|
+
this.tools.register(tool);
|
|
2060
|
+
}
|
|
2061
|
+
async use(plugin, api) {
|
|
2062
|
+
await plugin.setup(api);
|
|
2063
|
+
this.plugins.push({ plugin, api });
|
|
2064
|
+
}
|
|
2065
|
+
async teardown() {
|
|
2066
|
+
const errors = [];
|
|
2067
|
+
for (const { plugin, api } of this.plugins.toReversed()) {
|
|
2068
|
+
if (typeof plugin.teardown !== "function") continue;
|
|
2069
|
+
try {
|
|
2070
|
+
await plugin.teardown(api);
|
|
2071
|
+
} catch (err) {
|
|
2072
|
+
errors.push(err);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
this.plugins.length = 0;
|
|
2076
|
+
try {
|
|
2077
|
+
await this.ctx.drainAgentHooks();
|
|
2078
|
+
} catch {
|
|
2079
|
+
}
|
|
2080
|
+
if (errors.length > 0) {
|
|
2081
|
+
throw new AgentError({
|
|
2082
|
+
message: `Agent teardown failed: ${errors.map(String).join("; ")}`,
|
|
2083
|
+
code: "AGENT_RUN_FAILED",
|
|
2084
|
+
context: { failures: errors.length, phase: "plugin-teardown" },
|
|
2085
|
+
// Preserve the FIRST underlying failure as the cause so consumers can
|
|
2086
|
+
// pull type/code off it via `instanceof` checks. The full list is
|
|
2087
|
+
// flattened into the message; we don't keep the array on the error
|
|
2088
|
+
// because WrongStackError.context is Record<string, unknown> and
|
|
2089
|
+
// arrays of unknown Errors would lose their structured fields.
|
|
2090
|
+
cause: errors[0]
|
|
2091
|
+
});
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
async run(userInput, opts = {}) {
|
|
2095
|
+
if (this._runInProgress) {
|
|
2096
|
+
throw new AgentError({
|
|
2097
|
+
message: "Agent.run() is already in progress on this instance. Concurrent runs are not supported.",
|
|
2098
|
+
code: "AGENT_RUN_FAILED",
|
|
2099
|
+
context: { phase: "concurrency-guard" }
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
let newInputHash;
|
|
2103
|
+
const inputText = typeof userInput === "string" ? userInput : userInput?.prompt ?? "";
|
|
2104
|
+
if (inputText.length > 0) {
|
|
2105
|
+
const hash = createHash("sha256").update(inputText).digest("hex");
|
|
2106
|
+
const elapsed = Date.now() - (this._lastInputHashAt ?? Number.NaN);
|
|
2107
|
+
const burstDuplicate = hash === this._lastInputHash && elapsed >= 0 && elapsed < _Agent.INPUT_DEDUP_WINDOW_MS;
|
|
2108
|
+
if (burstDuplicate) {
|
|
2109
|
+
this._logger.debug(
|
|
2110
|
+
"Duplicate input suppressed: identical text resubmitted inside the dedup burst window."
|
|
2111
|
+
);
|
|
2112
|
+
return {
|
|
2113
|
+
status: "done",
|
|
2114
|
+
iterations: 0
|
|
2115
|
+
};
|
|
2116
|
+
}
|
|
2117
|
+
newInputHash = hash;
|
|
2118
|
+
}
|
|
2119
|
+
this._runInProgress = true;
|
|
2120
|
+
const controller = new RunController({ parentSignal: opts.signal });
|
|
2121
|
+
const signal = controller.signal;
|
|
2122
|
+
this.ctx.signal = signal;
|
|
2123
|
+
const sessionWriter = this.ctx.session;
|
|
2124
|
+
const sessionId = sessionWriter.id;
|
|
2125
|
+
this.ctx.activeRunSessionWriter = sessionWriter;
|
|
2126
|
+
this.ctx.activeRunSessionId = sessionId;
|
|
2127
|
+
this.ctx.activeLogicalRequestId = void 0;
|
|
2128
|
+
this.ctx.activePromptManifestId = void 0;
|
|
2129
|
+
controller.onAbort(() => this.ctx.drainAbortHooks());
|
|
2130
|
+
controller.onAbort(async () => {
|
|
2131
|
+
await sessionWriter.flush().catch(() => {
|
|
2132
|
+
});
|
|
2133
|
+
});
|
|
2134
|
+
this.ctx.tools = this.tools.listForProvider();
|
|
2135
|
+
this.ctx.catalogTools = this.tools.list();
|
|
2136
|
+
const runStartedAt = Date.now();
|
|
2137
|
+
const runStartedIso = new Date(runStartedAt).toISOString();
|
|
2138
|
+
let span;
|
|
2139
|
+
try {
|
|
2140
|
+
if (this.refreshSystemPrompt) {
|
|
2141
|
+
const builder = this.container.safeResolve(TOKENS.SystemPromptBuilder);
|
|
2142
|
+
if (builder) {
|
|
2143
|
+
const onlineAgents = Array.isArray(this.ctx.meta["promptOnlineAgents"]) ? this.ctx.meta["promptOnlineAgents"] : void 0;
|
|
2144
|
+
const systemVariant = this.ctx.meta["systemPromptVariant"];
|
|
2145
|
+
const autonomy = this.ctx.meta["autonomy"];
|
|
2146
|
+
this.ctx.systemPrompt = await builder.build({
|
|
2147
|
+
cwd: this.ctx.cwd,
|
|
2148
|
+
projectRoot: this.ctx.projectRoot,
|
|
2149
|
+
tools: this.ctx.tools,
|
|
2150
|
+
catalogTools: this.ctx.catalogTools,
|
|
2151
|
+
provider: this.ctx.provider.id,
|
|
2152
|
+
model: opts.model ?? this.ctx.model,
|
|
2153
|
+
onlineAgents,
|
|
2154
|
+
...systemVariant === "lite" || systemVariant === "pro" || systemVariant === "default" ? { systemVariant } : {},
|
|
2155
|
+
...typeof autonomy === "string" ? { autonomy } : {}
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
span = this.tracer?.startSpan("agent.run", {
|
|
2160
|
+
"agent.model": opts.model ?? this.ctx.model,
|
|
2161
|
+
"agent.executionStrategy": opts.executionStrategy ?? this.executionStrategy
|
|
2162
|
+
});
|
|
2163
|
+
const { blocks, text } = normalizeInput(userInput);
|
|
2164
|
+
const inputPayload = { content: blocks, text, ctx: this.ctx };
|
|
2165
|
+
await this.extensions.runBeforeRun(this.ctx, inputPayload);
|
|
2166
|
+
this._lastInputHash = newInputHash;
|
|
2167
|
+
this._lastInputHashAt = Date.now();
|
|
2168
|
+
this.events.emit("agent.run.started", {
|
|
2169
|
+
sessionId,
|
|
2170
|
+
ctx: this.ctx,
|
|
2171
|
+
model: opts.model ?? this.ctx.model,
|
|
2172
|
+
at: runStartedIso,
|
|
2173
|
+
inputText: text
|
|
2174
|
+
});
|
|
2175
|
+
const autonomousContinue = opts.autonomousContinue ?? this.autonomousContinue;
|
|
2176
|
+
const result = await this._loopHandler.runInner(
|
|
2177
|
+
inputPayload,
|
|
2178
|
+
opts,
|
|
2179
|
+
controller,
|
|
2180
|
+
autonomousContinue
|
|
2181
|
+
);
|
|
2182
|
+
if (result.status === "failed" || result.status === "aborted") {
|
|
2183
|
+
this._lastInputHash = void 0;
|
|
2184
|
+
this._lastInputHashAt = void 0;
|
|
2185
|
+
}
|
|
2186
|
+
span?.setAttribute("agent.status", result.status);
|
|
2187
|
+
span?.setAttribute("agent.iterations", result.iterations);
|
|
2188
|
+
await this.extensions.runAfterRun(this.ctx, result);
|
|
2189
|
+
this.events.emit("agent.run.completed", {
|
|
2190
|
+
sessionId,
|
|
2191
|
+
ctx: this.ctx,
|
|
2192
|
+
status: result.status,
|
|
2193
|
+
iterations: result.iterations,
|
|
2194
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2195
|
+
durationMs: Date.now() - runStartedAt
|
|
2196
|
+
});
|
|
2197
|
+
return result;
|
|
2198
|
+
} catch (err) {
|
|
2199
|
+
this._lastInputHash = void 0;
|
|
2200
|
+
this._lastInputHashAt = void 0;
|
|
2201
|
+
const wse = err instanceof AgentError ? err : toWrongStackError(err);
|
|
2202
|
+
const safeError = err instanceof Error ? new Error(err.message) : new Error(String(err));
|
|
2203
|
+
this.events.emit("error", {
|
|
2204
|
+
sessionId,
|
|
2205
|
+
err: safeError,
|
|
2206
|
+
phase: "agent",
|
|
2207
|
+
_original: err instanceof Error ? err : void 0
|
|
2208
|
+
});
|
|
2209
|
+
if (err instanceof Error) span?.recordError(err);
|
|
2210
|
+
span?.setAttribute("agent.status", "failed");
|
|
2211
|
+
const result = {
|
|
2212
|
+
status: signal.aborted ? "aborted" : "failed",
|
|
2213
|
+
iterations: 0,
|
|
2214
|
+
error: wse,
|
|
2215
|
+
abortReason: signal.aborted ? signalAbortReason(signal) : void 0
|
|
2216
|
+
};
|
|
2217
|
+
await this.extensions.runAfterRun(this.ctx, result);
|
|
2218
|
+
if (result.status === "failed") {
|
|
2219
|
+
this.events.emit("agent.run.error", {
|
|
2220
|
+
sessionId,
|
|
2221
|
+
ctx: this.ctx,
|
|
2222
|
+
err: safeError,
|
|
2223
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2224
|
+
durationMs: Date.now() - runStartedAt
|
|
2225
|
+
});
|
|
2226
|
+
}
|
|
2227
|
+
this.events.emit("agent.run.completed", {
|
|
2228
|
+
sessionId,
|
|
2229
|
+
ctx: this.ctx,
|
|
2230
|
+
status: result.status,
|
|
2231
|
+
iterations: result.iterations,
|
|
2232
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2233
|
+
durationMs: Date.now() - runStartedAt
|
|
2234
|
+
});
|
|
2235
|
+
return result;
|
|
2236
|
+
} finally {
|
|
2237
|
+
span?.end();
|
|
2238
|
+
try {
|
|
2239
|
+
await controller.dispose();
|
|
2240
|
+
} finally {
|
|
2241
|
+
this.ctx.activeRunSessionId = void 0;
|
|
2242
|
+
this.ctx.activeRunSessionWriter = void 0;
|
|
2243
|
+
this.ctx.activeLogicalRequestId = void 0;
|
|
2244
|
+
this.ctx.activePromptManifestId = void 0;
|
|
2245
|
+
this._runInProgress = false;
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
// ── Tool + response execution handled by AgentToolHandler / AgentResponseHandler ──
|
|
2250
|
+
};
|
|
2251
|
+
|
|
2252
|
+
// src/core/continue-intent.ts
|
|
2253
|
+
var CONTINUE_PHRASES = /* @__PURE__ */ new Set([
|
|
2254
|
+
// ── English ──
|
|
2255
|
+
"continue",
|
|
2256
|
+
"cont",
|
|
2257
|
+
"continue continue",
|
|
2258
|
+
"continue working",
|
|
2259
|
+
"keep going",
|
|
2260
|
+
"keep it going",
|
|
2261
|
+
"keep going on",
|
|
2262
|
+
"keep working",
|
|
2263
|
+
"keep on",
|
|
2264
|
+
"carry on",
|
|
2265
|
+
"go on",
|
|
2266
|
+
"goon",
|
|
2267
|
+
"go ahead",
|
|
2268
|
+
"proceed",
|
|
2269
|
+
"proceed further",
|
|
2270
|
+
"onward",
|
|
2271
|
+
"onwards",
|
|
2272
|
+
"next",
|
|
2273
|
+
"next step",
|
|
2274
|
+
"more",
|
|
2275
|
+
"resume",
|
|
2276
|
+
"and continue",
|
|
2277
|
+
"yes continue",
|
|
2278
|
+
"ok continue",
|
|
2279
|
+
// ── Turkish ──
|
|
2280
|
+
"devam",
|
|
2281
|
+
"devam et",
|
|
2282
|
+
"devam et bakalim",
|
|
2283
|
+
"devam edelim",
|
|
2284
|
+
"devam edin",
|
|
2285
|
+
"devam etsene",
|
|
2286
|
+
"devam etsenize",
|
|
2287
|
+
"devam ediyoruz",
|
|
2288
|
+
"devam et lutfen",
|
|
2289
|
+
"surdur",
|
|
2290
|
+
"devam etsen",
|
|
2291
|
+
"devam et hadi",
|
|
2292
|
+
"hadi devam",
|
|
2293
|
+
"bir tur daha",
|
|
2294
|
+
"bir tur daha at",
|
|
2295
|
+
"bir tur daha bakalim",
|
|
2296
|
+
"bir tur daha atalim",
|
|
2297
|
+
"bir tur daha at bakalim",
|
|
2298
|
+
// ── Other Latin-script (light coverage) ──
|
|
2299
|
+
"weiter",
|
|
2300
|
+
// de
|
|
2301
|
+
"mach weiter",
|
|
2302
|
+
// de
|
|
2303
|
+
"continuar",
|
|
2304
|
+
// es/pt
|
|
2305
|
+
"continua",
|
|
2306
|
+
// es/it/pt
|
|
2307
|
+
"continuer",
|
|
2308
|
+
// fr
|
|
2309
|
+
"continue por favor"
|
|
2310
|
+
// pt/es
|
|
2311
|
+
]);
|
|
2312
|
+
var POLITENESS_SUFFIXES = [
|
|
2313
|
+
"please",
|
|
2314
|
+
"pls",
|
|
2315
|
+
"plz",
|
|
2316
|
+
"lutfen",
|
|
2317
|
+
"thanks",
|
|
2318
|
+
"thank you",
|
|
2319
|
+
"thx",
|
|
2320
|
+
"ty"
|
|
2321
|
+
];
|
|
2322
|
+
function foldDiacritics(s) {
|
|
2323
|
+
return s.replace(/ü/g, "u").replace(/ö/g, "o").replace(/ç/g, "c").replace(/ş/g, "s").replace(/ğ/g, "g").replace(/ı/g, "i").replace(/İ/g, "i").replace(/â/g, "a").replace(/î/g, "i").replace(/û/g, "u");
|
|
2324
|
+
}
|
|
2325
|
+
function normalizeContinueInput(raw) {
|
|
2326
|
+
let s = raw.trim().toLowerCase();
|
|
2327
|
+
s = foldDiacritics(s);
|
|
2328
|
+
s = s.replace(/^["'`]+/, "").replace(/["'`]+$/, "");
|
|
2329
|
+
s = s.replace(/^[\s.!?,;:…]+/, "").replace(/[\s.!?,;:…]+$/, "");
|
|
2330
|
+
s = s.replace(/\s+/g, " ").trim();
|
|
2331
|
+
for (const suffix of POLITENESS_SUFFIXES) {
|
|
2332
|
+
if (s === suffix) break;
|
|
2333
|
+
if (s.endsWith(` ${suffix}`)) {
|
|
2334
|
+
s = s.slice(0, s.length - suffix.length - 1).trim();
|
|
2335
|
+
break;
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
return s;
|
|
2339
|
+
}
|
|
2340
|
+
var MAX_CONTINUE_WORDS = 4;
|
|
2341
|
+
function detectContinueIntent(raw) {
|
|
2342
|
+
if (!raw) return false;
|
|
2343
|
+
if (raw.length > 40) return false;
|
|
2344
|
+
const normalized = normalizeContinueInput(raw);
|
|
2345
|
+
if (!normalized) return false;
|
|
2346
|
+
if (normalized.split(" ").length > MAX_CONTINUE_WORDS) return false;
|
|
2347
|
+
return CONTINUE_PHRASES.has(normalized);
|
|
2348
|
+
}
|
|
2349
|
+
function ellipsize(s, max = 72) {
|
|
2350
|
+
const flat = s.replace(/\s+/g, " ").trim();
|
|
2351
|
+
return flat.length <= max ? flat : `${flat.slice(0, max - 1)}\u2026`;
|
|
2352
|
+
}
|
|
2353
|
+
function resolveContinuation(input) {
|
|
2354
|
+
const todos = Array.isArray(input.todos) ? input.todos : [];
|
|
2355
|
+
const suggestions = (Array.isArray(input.suggestions) ? input.suggestions : []).filter(
|
|
2356
|
+
(s) => typeof s === "string" && s.trim().length > 0
|
|
2357
|
+
);
|
|
2358
|
+
if (hasOpenTodos(todos)) {
|
|
2359
|
+
const inProgress = todos.find((t) => t.status === "in_progress");
|
|
2360
|
+
const next = inProgress ?? todos.find((t) => t.status === "pending");
|
|
2361
|
+
if (next) {
|
|
2362
|
+
const total = todos.length;
|
|
2363
|
+
const done = todos.filter((t) => t.status === "completed").length;
|
|
2364
|
+
const item = next.status === "in_progress" && next.activeForm ? next.activeForm : next.content;
|
|
2365
|
+
const BOARD_MAX_ROWS = 20;
|
|
2366
|
+
const board = todos.slice(0, BOARD_MAX_ROWS).map((t) => {
|
|
2367
|
+
const mark = t.status === "completed" ? "[x]" : t.status === "in_progress" ? "[~]" : "[ ]";
|
|
2368
|
+
const flatContent = t.content.replace(/\n|\r/g, " ");
|
|
2369
|
+
return ` ${mark} ${flatContent}`;
|
|
2370
|
+
});
|
|
2371
|
+
if (todos.length > BOARD_MAX_ROWS) {
|
|
2372
|
+
board.push(` \u2026 ${todos.length - BOARD_MAX_ROWS} more`);
|
|
2373
|
+
}
|
|
2374
|
+
const text2 = [
|
|
2375
|
+
"Continue with the plan. Resume work on the next open todo:",
|
|
2376
|
+
"",
|
|
2377
|
+
` ${item}`,
|
|
2378
|
+
"",
|
|
2379
|
+
`Current todo board (${done}/${total} todos complete.):`,
|
|
2380
|
+
...board,
|
|
2381
|
+
"",
|
|
2382
|
+
"If this item is already done, mark it complete and move to the next open todo. Keep the board honest as you work \u2014 mark finished items completed and split items that need more than one turn. When every todo is complete, stop and give a short summary \u2014 do not invent new work."
|
|
2383
|
+
].join("\n");
|
|
2384
|
+
return {
|
|
2385
|
+
source: "todo",
|
|
2386
|
+
todoId: next.id,
|
|
2387
|
+
text: text2,
|
|
2388
|
+
label: `\u25B6 Continue \u2192 todo: ${ellipsize(item)}`
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
const top = suggestions[0];
|
|
2393
|
+
if (top) {
|
|
2394
|
+
const text2 = [
|
|
2395
|
+
"Continue. Proceed with this next step:",
|
|
2396
|
+
"",
|
|
2397
|
+
` ${top}`,
|
|
2398
|
+
"",
|
|
2399
|
+
"Carry it out now. When it is done, briefly report the result."
|
|
2400
|
+
].join("\n");
|
|
2401
|
+
return { source: "suggestion", text: text2, label: `\u25B6 Continue \u2192 ${ellipsize(top)}` };
|
|
2402
|
+
}
|
|
2403
|
+
const text = [
|
|
2404
|
+
"Continue working.",
|
|
2405
|
+
"",
|
|
2406
|
+
"There is no explicit pending task queued, so decide the single most valuable next step yourself based on our conversation so far and the current state of the project, then carry it out.",
|
|
2407
|
+
"",
|
|
2408
|
+
"Prefer finishing, verifying, or hardening work already in progress over starting something unrelated. If the work is genuinely complete and nothing sensible remains, say so plainly in one or two sentences and propose a direction \u2014 do not fabricate busywork."
|
|
2409
|
+
].join("\n");
|
|
2410
|
+
return { source: "open", text, label: "\u25B6 Continue \u2192 (no pending task \u2014 choosing next step)" };
|
|
2411
|
+
}
|
|
2412
|
+
|
|
2413
|
+
// src/core/input-builder.ts
|
|
2414
|
+
var InputBuilder = class {
|
|
2415
|
+
store;
|
|
2416
|
+
pasteLineThreshold;
|
|
2417
|
+
pasteCharThreshold;
|
|
2418
|
+
display = "";
|
|
2419
|
+
refs = [];
|
|
2420
|
+
constructor(opts) {
|
|
2421
|
+
this.store = opts.store;
|
|
2422
|
+
this.pasteLineThreshold = opts.pasteLineThreshold ?? 8;
|
|
2423
|
+
this.pasteCharThreshold = opts.pasteCharThreshold ?? 2e3;
|
|
2424
|
+
}
|
|
2425
|
+
get text() {
|
|
2426
|
+
return this.display;
|
|
2427
|
+
}
|
|
2428
|
+
get attachments() {
|
|
2429
|
+
return [...this.refs];
|
|
2430
|
+
}
|
|
2431
|
+
get isEmpty() {
|
|
2432
|
+
return this.display.trim().length === 0;
|
|
2433
|
+
}
|
|
2434
|
+
appendText(text) {
|
|
2435
|
+
this.display += text;
|
|
2436
|
+
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Decide whether a chunk of pasted text is "big" enough to collapse.
|
|
2439
|
+
* If yes, store it and append a placeholder. If no, inline it.
|
|
2440
|
+
* Returns the placeholder string actually appended (or `null` if inlined).
|
|
2441
|
+
*/
|
|
2442
|
+
async appendPaste(text) {
|
|
2443
|
+
if (this.shouldCollapse(text)) {
|
|
2444
|
+
const ref = await this.store.add({
|
|
2445
|
+
kind: "text",
|
|
2446
|
+
data: text,
|
|
2447
|
+
meta: { label: paragraphLabel(text) }
|
|
2448
|
+
});
|
|
2449
|
+
const placeholder = `[pasted #${ref.seq}]`;
|
|
2450
|
+
this.display += placeholder;
|
|
2451
|
+
this.refs.push(ref);
|
|
2452
|
+
return placeholder;
|
|
2453
|
+
}
|
|
2454
|
+
this.display += text;
|
|
2455
|
+
return null;
|
|
2456
|
+
}
|
|
2457
|
+
/**
|
|
2458
|
+
* Always collapses to `[image #N]` — images are never inlined.
|
|
2459
|
+
* `dataBase64` is the raw base64 payload (no data: prefix).
|
|
2460
|
+
*/
|
|
2461
|
+
async appendImage(dataBase64, mediaType) {
|
|
2462
|
+
const ref = await this.store.add({
|
|
2463
|
+
kind: "image",
|
|
2464
|
+
data: dataBase64,
|
|
2465
|
+
meta: { mediaType, label: `${mediaType.split("/")[1]?.toUpperCase() ?? "IMG"}` }
|
|
2466
|
+
});
|
|
2467
|
+
const placeholder = `[image #${ref.seq}]`;
|
|
2468
|
+
this.display += placeholder;
|
|
2469
|
+
this.refs.push(ref);
|
|
2470
|
+
return placeholder;
|
|
2471
|
+
}
|
|
2472
|
+
async appendFile(input) {
|
|
2473
|
+
const ref = await this.store.add({ ...input, kind: "file" });
|
|
2474
|
+
const placeholder = `[file #${ref.seq}]`;
|
|
2475
|
+
this.display += placeholder;
|
|
2476
|
+
this.refs.push(ref);
|
|
2477
|
+
return placeholder;
|
|
2478
|
+
}
|
|
2479
|
+
/**
|
|
2480
|
+
* Register-only variant of `appendPaste`. Always stores the paste and
|
|
2481
|
+
* returns the inline `[pasted #N, L lines]` token WITHOUT mutating
|
|
2482
|
+
* `display` — the caller (TUI) owns its own editable buffer as the single
|
|
2483
|
+
* source of truth, inserts the token there, and expands the buffer at
|
|
2484
|
+
* submit. The collapse decision is the caller's (it gates this call); use
|
|
2485
|
+
* `wouldCollapse()` for that.
|
|
2486
|
+
*/
|
|
2487
|
+
async registerPaste(text) {
|
|
2488
|
+
const ref = await this.store.add({
|
|
2489
|
+
kind: "text",
|
|
2490
|
+
data: text,
|
|
2491
|
+
meta: { label: paragraphLabel(text) }
|
|
2492
|
+
});
|
|
2493
|
+
this.refs.push(ref);
|
|
2494
|
+
const lines = text.split(/\r?\n|\r/).length;
|
|
2495
|
+
return `[pasted #${ref.seq}, ${lines} lines]`;
|
|
2496
|
+
}
|
|
2497
|
+
/**
|
|
2498
|
+
* Register-only variant of `appendImage` — see `registerPaste`. Returns a
|
|
2499
|
+
* seq-keyed `[image #N, LABEL]` token; does not touch `display`.
|
|
2500
|
+
*/
|
|
2501
|
+
async registerImage(dataBase64, mediaType) {
|
|
2502
|
+
const label = `${mediaType.split("/")[1]?.toUpperCase() ?? "IMG"}`;
|
|
2503
|
+
const ref = await this.store.add({
|
|
2504
|
+
kind: "image",
|
|
2505
|
+
data: dataBase64,
|
|
2506
|
+
meta: { mediaType, label }
|
|
2507
|
+
});
|
|
2508
|
+
this.refs.push(ref);
|
|
2509
|
+
return `[image #${ref.seq}, ${label}]`;
|
|
2510
|
+
}
|
|
2511
|
+
/**
|
|
2512
|
+
* Register-only variant of `appendFile` — see `registerPaste`. Returns a
|
|
2513
|
+
* path-keyed `[file:<path>]` token (resolved by path at expand time); does
|
|
2514
|
+
* not touch `display`. The path is read from `meta.filename` (falling back
|
|
2515
|
+
* to `meta.label`).
|
|
2516
|
+
*/
|
|
2517
|
+
async registerFile(input) {
|
|
2518
|
+
const ref = await this.store.add({ ...input, kind: "file" });
|
|
2519
|
+
this.refs.push(ref);
|
|
2520
|
+
const path = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
|
|
2521
|
+
return `[file:${path}]`;
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Whether `appendPaste(text)` would collapse the text to a placeholder
|
|
2525
|
+
* (rather than inlining it). Lets a frontend decide where to route a paste
|
|
2526
|
+
* — e.g. collapsed pastes become a pill, while inlined ones can be shown
|
|
2527
|
+
* in the editable input row — without calling `appendPaste` first (which
|
|
2528
|
+
* mutates the display buffer).
|
|
2529
|
+
*/
|
|
2530
|
+
wouldCollapse(text) {
|
|
2531
|
+
return this.shouldCollapse(text);
|
|
2532
|
+
}
|
|
2533
|
+
/** Reset display and ref list. Does NOT clear the store itself. */
|
|
2534
|
+
reset() {
|
|
2535
|
+
this.display = "";
|
|
2536
|
+
this.refs.length = 0;
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2539
|
+
* Resolve the current display string into ContentBlock[]. Empty
|
|
2540
|
+
* input returns an empty array — caller decides what to do.
|
|
2541
|
+
*/
|
|
2542
|
+
async submit() {
|
|
2543
|
+
const text = this.display;
|
|
2544
|
+
this.reset();
|
|
2545
|
+
return text ? this.store.expand(text) : [];
|
|
2546
|
+
}
|
|
2547
|
+
shouldCollapse(text) {
|
|
2548
|
+
if (text.length >= this.pasteCharThreshold) return true;
|
|
2549
|
+
let lines = 1;
|
|
2550
|
+
for (let i = 0; i < text.length; i++) {
|
|
2551
|
+
const c = text.charCodeAt(i);
|
|
2552
|
+
if (c === 10) lines++;
|
|
2553
|
+
else if (c === 13 && text.charCodeAt(i + 1) !== 10) lines++;
|
|
2554
|
+
if (lines >= this.pasteLineThreshold) return true;
|
|
2555
|
+
}
|
|
2556
|
+
return false;
|
|
2557
|
+
}
|
|
2558
|
+
};
|
|
2559
|
+
function paragraphLabel(text) {
|
|
2560
|
+
const lines = text.split(/\r?\n|\r/).length;
|
|
2561
|
+
const bytes = Buffer.byteLength(text, "utf8");
|
|
2562
|
+
if (bytes < 1024) return `${lines} lines, ${bytes} B`;
|
|
2563
|
+
return `${lines} lines, ${(bytes / 1024).toFixed(1)} KB`;
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
export {
|
|
2567
|
+
createMailboxChecker,
|
|
2568
|
+
buildMailboxBtwAwarenessBlock,
|
|
2569
|
+
buildMailboxBlock,
|
|
2570
|
+
injectPendingMailboxMessages,
|
|
2571
|
+
attachMailboxChecker,
|
|
2572
|
+
enqueueSessionNote,
|
|
2573
|
+
pendingSessionNoteCount,
|
|
2574
|
+
consumeSessionNotes,
|
|
2575
|
+
buildSessionNoteBlock,
|
|
2576
|
+
attachSessionNotes,
|
|
2577
|
+
setQueuedMessagesSnapshot,
|
|
2578
|
+
peekQueuedMessages,
|
|
2579
|
+
consumeQueuedMessagesUpdate,
|
|
2580
|
+
buildQueuedMessagesBlock,
|
|
2581
|
+
DEFAULT_MAX_ITERATIONS,
|
|
2582
|
+
createDefaultPipelines,
|
|
2583
|
+
Agent,
|
|
2584
|
+
detectContinueIntent,
|
|
2585
|
+
resolveContinuation,
|
|
2586
|
+
InputBuilder
|
|
2587
|
+
};
|
|
2588
|
+
//# sourceMappingURL=chunk-YOPAUVU4.js.map
|