@jungjaehoon/mama-core 2.4.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -239
- package/db/migrations/030-case-first-memory-substrate.sql +13 -98
- package/db/migrations/034-add-connector-event-scope-columns.sql +9 -13
- package/db/migrations/039-add-connector-event-operator-ingest-seq.sql +9 -95
- package/db/migrations/062-refresh-connector-event-sequences.sql +9 -113
- package/db/migrations/063-refresh-legacy-connector-event-updates.sql +9 -32
- package/db/migrations/067-add-connector-event-source-entity-id.sql +9 -13
- package/db/migrations/069-create-registry-nodes.sql +52 -0
- package/db/migrations/070-record-identity.sql +35 -0
- package/db/migrations/071-service-operation-origins.sql +187 -0
- package/db/migrations/072-observation-versions.sql +29 -0
- package/db/migrations/073-registry-corrections.sql +45 -0
- package/db/migrations/074-work-graph-ref-kinds.sql +39 -0
- package/db/migrations/075-agent-judgments.sql +58 -0
- package/db/migrations/076-scoped-checkpoint-bindings.sql +15 -0
- package/db/migrations/077-legacy-record-kind.sql +6 -0
- package/db/migrations/078-source-ingest-commands.sql +18 -0
- package/db/migrations/079-twin-edge-relations.sql +43 -0
- package/db/migrations/080-restore-duration-days.sql +7 -0
- package/db/migrations/081-mailbox.sql +61 -0
- package/db/migrations/082-commitments-row-id.sql +12 -0
- package/db/migrations/083-observation-is-the-evidence.sql +49 -0
- package/db/migrations/084-scope-kind-is-not-the-cores-to-list.sql +34 -0
- package/db/migrations/085-a-migration-has-a-source.sql +37 -0
- package/db/migrations/086-the-mailbox-is-generic.sql +110 -0
- package/db/migrations/087-mailbox-payload.sql +5 -0
- package/db/migrations/088-native-input-journal.sql +14 -0
- package/db/migrations/089-native-turn-receipt-lookup.sql +3 -0
- package/db/migrations/090-model-run-native-inputs.sql +29 -0
- package/db/migrations/091-native-turn-results.sql +11 -0
- package/db/migrations/092-restore-decision-fts.sql +34 -0
- package/db/migrations/093-native-turn-primary-kind.sql +14 -0
- package/db/migrations/094-commitment-run-provenance.sql +9 -0
- package/db/migrations/095-model-run-native-input-identity.sql +39 -0
- package/db/migrations/096-drop-connector-event-index-fts.sql +9 -0
- package/db/migrations/097-native-input-json-validity.sql +37 -0
- package/db/migrations/098-workflow-memory-kind.sql +105 -0
- package/db/migrations/099-commitment-revision-graph.sql +2 -0
- package/dist/action-contracts.d.ts +212 -0
- package/dist/action-contracts.js +10 -0
- package/dist/api/catalog.d.ts +118 -0
- package/dist/api/catalog.js +1756 -0
- package/dist/api/dispatch.d.ts +76 -0
- package/dist/api/dispatch.js +372 -0
- package/dist/canonicalize.d.ts +2 -2
- package/dist/canonicalize.js +3 -3
- package/dist/client/client.d.ts +33 -0
- package/dist/client/client.js +123 -0
- package/dist/client/ipc.d.ts +81 -0
- package/dist/client/ipc.js +314 -0
- package/dist/db-adapter/base-adapter.d.ts +6 -66
- package/dist/db-adapter/base-adapter.js +6 -6
- package/dist/db-adapter/index.d.ts +5 -5
- package/dist/db-adapter/index.js +2 -5
- package/dist/db-adapter/node-sqlite-adapter.d.ts +85 -15
- package/dist/db-adapter/node-sqlite-adapter.js +1985 -570
- package/dist/db-manager.d.ts +53 -97
- package/dist/db-manager.js +121 -603
- package/dist/embedding/embedder.d.ts +132 -0
- package/dist/embedding/embedder.js +299 -0
- package/dist/identity/principal-repository.d.ts +2 -2
- package/dist/index.d.ts +43 -58
- package/dist/index.js +97 -145
- package/dist/{context-compile/channel-grant.d.ts → knowledge/access.d.ts} +12 -17
- package/dist/knowledge/access.js +663 -0
- package/dist/knowledge/case-errors.d.ts +31 -0
- package/dist/knowledge/case-errors.js +40 -0
- package/dist/{cases/search-rollup.d.ts → knowledge/case-search-rollup.d.ts} +5 -4
- package/dist/{cases/search-rollup.js → knowledge/case-search-rollup.js} +6 -8
- package/dist/{cases/store.d.ts → knowledge/case-store.d.ts} +3 -6
- package/dist/{cases/store.js → knowledge/case-store.js} +7 -42
- package/dist/{cases/timeline-range.d.ts → knowledge/case-timeline-range.d.ts} +3 -3
- package/dist/{cases/timeline-range.js → knowledge/case-timeline-range.js} +47 -25
- package/dist/{cases/types.d.ts → knowledge/case-types.d.ts} +8 -2
- package/dist/{cases/types.js → knowledge/case-types.js} +1 -1
- package/dist/knowledge/commitment-revision-migration.d.ts +4 -0
- package/dist/knowledge/commitment-revision-migration.js +50 -0
- package/dist/knowledge/commitments.d.ts +192 -0
- package/dist/knowledge/commitments.js +386 -0
- package/dist/knowledge/decision-edges.d.ts +70 -0
- package/dist/knowledge/decision-edges.js +123 -0
- package/dist/{agent-graph/types.d.ts → knowledge/graph-query.d.ts} +80 -70
- package/dist/knowledge/graph-query.js +1585 -0
- package/dist/knowledge/identity.d.ts +16 -0
- package/dist/knowledge/identity.js +22 -0
- package/dist/knowledge/index.d.ts +47 -0
- package/dist/knowledge/index.js +90 -0
- package/dist/knowledge/judgment-edge.d.ts +6 -0
- package/dist/knowledge/judgment-edge.js +22 -0
- package/dist/knowledge/judgments.d.ts +118 -0
- package/dist/knowledge/judgments.js +802 -0
- package/dist/knowledge/observations.d.ts +160 -0
- package/dist/knowledge/observations.js +449 -0
- package/dist/knowledge/question-type.d.ts +34 -0
- package/dist/knowledge/question-type.js +70 -0
- package/dist/{search → knowledge}/ranker-features.js +1 -1
- package/dist/{search → knowledge}/search-quality.d.ts +0 -1
- package/dist/knowledge/search.d.ts +38 -0
- package/dist/knowledge/search.js +91 -0
- package/dist/knowledge/source-ingest.d.ts +65 -0
- package/dist/knowledge/source-ingest.js +174 -0
- package/dist/{edges/types.d.ts → knowledge/twin-edge-types.d.ts} +31 -4
- package/dist/{edges/types.js → knowledge/twin-edge-types.js} +13 -2
- package/dist/knowledge/work-dates.d.ts +49 -0
- package/dist/knowledge/work-dates.js +120 -0
- package/dist/mama-api.d.ts +114 -778
- package/dist/mama-api.js +313 -2461
- package/dist/memory/api.d.ts +264 -24
- package/dist/memory/api.js +1633 -843
- package/dist/memory/bootstrap-builder.d.ts +2 -1
- package/dist/memory/bootstrap-builder.js +5 -5
- package/dist/memory/channel-summary-state-store.d.ts +3 -2
- package/dist/memory/channel-summary-state-store.js +53 -51
- package/dist/memory/channel-summary-store.d.ts +5 -3
- package/dist/memory/channel-summary-store.js +3 -8
- package/dist/memory/dashboard-read.d.ts +46 -0
- package/dist/memory/dashboard-read.js +94 -0
- package/dist/memory/event-store.d.ts +3 -3
- package/dist/memory/event-store.js +3 -10
- package/dist/memory/finding-store.d.ts +3 -2
- package/dist/memory/finding-store.js +2 -7
- package/dist/memory/graph-read.d.ts +52 -0
- package/dist/memory/graph-read.js +130 -0
- package/dist/memory/judgment-types.d.ts +394 -0
- package/dist/memory/judgment-types.js +16 -0
- package/dist/memory/provenance-audit.d.ts +5 -2
- package/dist/memory/provenance-audit.js +8 -14
- package/dist/memory/provenance-live.d.ts +141 -0
- package/dist/memory/provenance-live.js +409 -0
- package/dist/memory/provenance-query.d.ts +5 -4
- package/dist/memory/provenance-query.js +21 -29
- package/dist/memory/provenance-resolver.d.ts +191 -0
- package/dist/memory/provenance-resolver.js +155 -0
- package/dist/memory/provenance.d.ts +1 -11
- package/dist/memory/provenance.js +2 -33
- package/dist/memory/recall-sanitize.d.ts +55 -0
- package/dist/memory/recall-sanitize.js +144 -0
- package/dist/memory/secret-filter.d.ts +32 -0
- package/dist/memory/secret-filter.js +126 -0
- package/dist/memory/truth-store.d.ts +2 -1
- package/dist/memory/truth-store.js +1 -4
- package/dist/memory/types.d.ts +74 -24
- package/dist/memory/types.js +75 -5
- package/dist/memory/write-adapters.d.ts +98 -0
- package/dist/memory/write-adapters.js +242 -0
- package/dist/operations/owner-action-effects.d.ts +53 -0
- package/dist/operations/owner-action-effects.js +96 -0
- package/dist/provenance/source-ref.d.ts +1 -1
- package/dist/provenance/source-ref.js +0 -2
- package/dist/registry/corrections.d.ts +31 -0
- package/dist/registry/corrections.js +332 -0
- package/dist/registry/record-identity.d.ts +49 -0
- package/dist/registry/record-identity.js +133 -0
- package/dist/registry/store.d.ts +101 -0
- package/dist/registry/store.js +395 -0
- package/dist/registry/types.d.ts +28 -0
- package/dist/registry/types.js +5 -0
- package/dist/relevance-scorer.js +2 -2
- package/dist/runtime/agent-event-bus.d.ts +50 -0
- package/dist/runtime/agent-event-bus.js +101 -0
- package/dist/runtime/drivers/claude-cli-wrapper.d.ts +152 -0
- package/dist/runtime/drivers/claude-cli-wrapper.js +375 -0
- package/dist/runtime/drivers/claude-effort.d.ts +4 -0
- package/dist/runtime/drivers/claude-effort.js +16 -0
- package/dist/runtime/drivers/cli-arg-redaction.d.ts +2 -0
- package/dist/runtime/drivers/cli-arg-redaction.js +18 -0
- package/dist/runtime/drivers/cli-secret-redaction.d.ts +17 -0
- package/dist/runtime/drivers/cli-secret-redaction.js +126 -0
- package/dist/runtime/drivers/codex-app-server-process.d.ts +277 -0
- package/dist/runtime/drivers/codex-app-server-process.js +2342 -0
- package/dist/runtime/drivers/codex-auxiliary-tools.d.ts +17 -0
- package/dist/runtime/drivers/codex-auxiliary-tools.js +267 -0
- package/dist/runtime/drivers/codex-home.d.ts +20 -0
- package/dist/runtime/drivers/codex-home.js +674 -0
- package/dist/runtime/drivers/codex-thread-registry.d.ts +63 -0
- package/dist/runtime/drivers/codex-thread-registry.js +433 -0
- package/dist/runtime/drivers/persistent-cli-adapter.d.ts +160 -0
- package/dist/runtime/drivers/persistent-cli-adapter.js +396 -0
- package/dist/runtime/drivers/persistent-cli-process.d.ts +425 -0
- package/dist/runtime/drivers/persistent-cli-process.js +1529 -0
- package/dist/runtime/drivers/types.d.ts +456 -0
- package/dist/runtime/drivers/types.js +110 -0
- package/dist/runtime/mailbox.d.ts +223 -0
- package/dist/runtime/mailbox.js +582 -0
- package/dist/runtime/model-run-store.d.ts +28 -0
- package/dist/{model-runs/store.js → runtime/model-run-store.js} +81 -41
- package/dist/{model-runs/types.d.ts → runtime/model-run-types.d.ts} +33 -4
- package/dist/{model-runs/types.js → runtime/model-run-types.js} +1 -1
- package/dist/runtime/native-effect-observer.d.ts +22 -0
- package/dist/runtime/native-effect-observer.js +103 -0
- package/dist/runtime/native-input-journal.d.ts +46 -0
- package/dist/runtime/native-input-journal.js +308 -0
- package/dist/runtime/native-prompt.d.ts +118 -0
- package/dist/runtime/native-prompt.js +496 -0
- package/dist/runtime/native-session.d.ts +10 -0
- package/dist/{agent-graph/index.js → runtime/native-session.js} +9 -6
- package/dist/runtime/native-tool-trace-observer.d.ts +5 -0
- package/dist/runtime/native-tool-trace-observer.js +65 -0
- package/dist/runtime/native-turn.d.ts +272 -0
- package/dist/runtime/native-turn.js +1041 -0
- package/dist/runtime/operations.d.ts +236 -0
- package/dist/runtime/operations.js +565 -0
- package/dist/runtime/post-tool-handler.d.ts +74 -0
- package/dist/runtime/post-tool-handler.js +137 -0
- package/dist/runtime/prompt-layers.d.ts +126 -0
- package/dist/runtime/prompt-layers.js +239 -0
- package/dist/runtime/runtime-process.d.ts +102 -0
- package/dist/runtime/runtime-process.js +206 -0
- package/dist/runtime/runtime.d.ts +278 -0
- package/dist/runtime/runtime.js +563 -0
- package/dist/runtime/session-pool.d.ts +176 -0
- package/dist/runtime/session-pool.js +369 -0
- package/dist/runtime/stimulus-payload.d.ts +5 -0
- package/dist/runtime/stimulus-payload.js +50 -0
- package/dist/runtime/subagent-bridge.d.ts +24 -0
- package/dist/{agent-graph/types.js → runtime/subagent-bridge.js} +1 -1
- package/dist/runtime/text-completion.d.ts +22 -0
- package/dist/{agent-situation/types.js → runtime/text-completion.js} +1 -1
- package/dist/runtime/token-estimator.d.ts +25 -0
- package/dist/runtime/token-estimator.js +75 -0
- package/dist/runtime/tool-trace-store.d.ts +22 -0
- package/dist/{model-runs → runtime}/tool-trace-store.js +46 -27
- package/dist/runtime/trace-summary.d.ts +5 -0
- package/dist/runtime/trace-summary.js +74 -0
- package/dist/runtime/turn-text.d.ts +23 -0
- package/dist/runtime/turn-text.js +41 -0
- package/dist/storage/database.d.ts +67 -0
- package/dist/storage/database.js +158 -0
- package/package.json +20 -34
- package/dist/agent-graph/alias-write.d.ts +0 -3
- package/dist/agent-graph/alias-write.js +0 -234
- package/dist/agent-graph/entity-resolve.d.ts +0 -3
- package/dist/agent-graph/entity-resolve.js +0 -271
- package/dist/agent-graph/errors.d.ts +0 -4
- package/dist/agent-graph/errors.js +0 -11
- package/dist/agent-graph/graph-query.d.ts +0 -5
- package/dist/agent-graph/graph-query.js +0 -360
- package/dist/agent-graph/index.d.ts +0 -6
- package/dist/agent-situation/builder.d.ts +0 -8
- package/dist/agent-situation/builder.js +0 -239
- package/dist/agent-situation/cache-key.d.ts +0 -4
- package/dist/agent-situation/cache-key.js +0 -64
- package/dist/agent-situation/index.d.ts +0 -7
- package/dist/agent-situation/index.js +0 -28
- package/dist/agent-situation/packet-store.d.ts +0 -27
- package/dist/agent-situation/packet-store.js +0 -228
- package/dist/agent-situation/ranking-policy.d.ts +0 -18
- package/dist/agent-situation/ranking-policy.js +0 -75
- package/dist/agent-situation/source-readers.d.ts +0 -52
- package/dist/agent-situation/source-readers.js +0 -300
- package/dist/agent-situation/types.d.ts +0 -195
- package/dist/cases/wiki-page-index.d.ts +0 -37
- package/dist/cases/wiki-page-index.js +0 -201
- package/dist/config-loader.d.ts +0 -68
- package/dist/config-loader.js +0 -239
- package/dist/connectors/event-index.d.ts +0 -24
- package/dist/connectors/event-index.js +0 -245
- package/dist/connectors/raw-query.d.ts +0 -40
- package/dist/connectors/raw-query.js +0 -412
- package/dist/connectors/types.d.ts +0 -117
- package/dist/connectors/types.js +0 -3
- package/dist/context-compile/boundary-defaults.d.ts +0 -12
- package/dist/context-compile/boundary-defaults.js +0 -105
- package/dist/context-compile/channel-grant.js +0 -66
- package/dist/context-compile/compiler-policy.d.ts +0 -39
- package/dist/context-compile/compiler-policy.js +0 -147
- package/dist/context-compile/compiler.d.ts +0 -18
- package/dist/context-compile/compiler.js +0 -404
- package/dist/context-compile/index.d.ts +0 -10
- package/dist/context-compile/index.js +0 -26
- package/dist/context-compile/packet-store.d.ts +0 -8
- package/dist/context-compile/packet-store.js +0 -239
- package/dist/context-compile/ref.d.ts +0 -7
- package/dist/context-compile/ref.js +0 -102
- package/dist/context-compile/source-readers.d.ts +0 -70
- package/dist/context-compile/source-readers.js +0 -840
- package/dist/context-compile/types.d.ts +0 -155
- package/dist/context-compile/types.js +0 -11
- package/dist/context-compile/visibility.d.ts +0 -21
- package/dist/context-compile/visibility.js +0 -224
- package/dist/decision-tracker.d.ts +0 -206
- package/dist/decision-tracker.js +0 -481
- package/dist/edges/ref-validation.d.ts +0 -7
- package/dist/edges/ref-validation.js +0 -321
- package/dist/edges/store.d.ts +0 -8
- package/dist/edges/store.js +0 -129
- package/dist/embedding-client.d.ts +0 -28
- package/dist/embedding-client.js +0 -53
- package/dist/embedding-server/index.d.ts +0 -67
- package/dist/embedding-server/index.js +0 -405
- package/dist/embedding-server/mobile/auth.d.ts +0 -44
- package/dist/embedding-server/mobile/auth.js +0 -150
- package/dist/embedding-server/mobile/daemon.d.ts +0 -129
- package/dist/embedding-server/mobile/daemon.js +0 -313
- package/dist/embedding-server/mobile/output-parser.d.ts +0 -115
- package/dist/embedding-server/mobile/output-parser.js +0 -241
- package/dist/embedding-server/mobile/session-api.d.ts +0 -57
- package/dist/embedding-server/mobile/session-api.js +0 -261
- package/dist/embedding-server/mobile/session-manager.d.ts +0 -138
- package/dist/embedding-server/mobile/session-manager.js +0 -378
- package/dist/embedding-server/mobile/websocket-handler.d.ts +0 -137
- package/dist/embedding-server/mobile/websocket-handler.js +0 -486
- package/dist/embeddings.d.ts +0 -68
- package/dist/embeddings.js +0 -244
- package/dist/entities/audit-metrics.d.ts +0 -68
- package/dist/entities/audit-metrics.js +0 -124
- package/dist/entities/candidate-generator.d.ts +0 -8
- package/dist/entities/candidate-generator.js +0 -24
- package/dist/entities/entity-linked-decision-counts.d.ts +0 -2
- package/dist/entities/entity-linked-decision-counts.js +0 -29
- package/dist/entities/entity-list.d.ts +0 -22
- package/dist/entities/entity-list.js +0 -3
- package/dist/entities/entity-orphan-list.d.ts +0 -17
- package/dist/entities/entity-orphan-list.js +0 -3
- package/dist/entities/entity-search.d.ts +0 -20
- package/dist/entities/entity-search.js +0 -3
- package/dist/entities/errors.d.ts +0 -55
- package/dist/entities/errors.js +0 -100
- package/dist/entities/lineage-store.d.ts +0 -28
- package/dist/entities/lineage-store.js +0 -204
- package/dist/entities/normalization.d.ts +0 -14
- package/dist/entities/normalization.js +0 -63
- package/dist/entities/policy-store.d.ts +0 -8
- package/dist/entities/policy-store.js +0 -75
- package/dist/entities/policy-types.d.ts +0 -72
- package/dist/entities/policy-types.js +0 -59
- package/dist/entities/projection.d.ts +0 -8
- package/dist/entities/projection.js +0 -82
- package/dist/entities/provenance-query.d.ts +0 -32
- package/dist/entities/provenance-query.js +0 -3
- package/dist/entities/read-identity.d.ts +0 -31
- package/dist/entities/read-identity.js +0 -264
- package/dist/entities/recall-bridge.d.ts +0 -5
- package/dist/entities/recall-bridge.js +0 -171
- package/dist/entities/resolution-engine.d.ts +0 -8
- package/dist/entities/resolution-engine.js +0 -3
- package/dist/entities/rollback-preview.d.ts +0 -35
- package/dist/entities/rollback-preview.js +0 -3
- package/dist/entities/source-locator.d.ts +0 -3
- package/dist/entities/source-locator.js +0 -32
- package/dist/entities/store.d.ts +0 -69
- package/dist/entities/store.js +0 -544
- package/dist/entities/types.d.ts +0 -166
- package/dist/entities/types.js +0 -30
- package/dist/memory/extraction-prompt.d.ts +0 -4
- package/dist/memory/extraction-prompt.js +0 -89
- package/dist/memory/scope-backfill.d.ts +0 -14
- package/dist/memory/scope-backfill.js +0 -3
- package/dist/memory/scope-store.d.ts +0 -4
- package/dist/memory/scope-store.js +0 -9
- package/dist/memory-store.d.ts +0 -87
- package/dist/memory-store.js +0 -78
- package/dist/model-runs/store.d.ts +0 -13
- package/dist/model-runs/tool-trace-store.d.ts +0 -8
- package/dist/notification-manager.d.ts +0 -7
- package/dist/notification-manager.js +0 -12
- package/dist/outcome-tracker.d.ts +0 -151
- package/dist/outcome-tracker.js +0 -271
- package/dist/query-intent.d.ts +0 -23
- package/dist/query-intent.js +0 -114
- package/dist/runtime/trusted-provenance.d.ts +0 -3
- package/dist/runtime/trusted-provenance.js +0 -6
- package/dist/search/question-type.d.ts +0 -5
- package/dist/search/question-type.js +0 -41
- package/dist/test-utils.d.ts +0 -80
- package/dist/test-utils.js +0 -218
- package/dist/tier-validator.d.ts +0 -43
- package/dist/tier-validator.js +0 -159
- package/dist/time-formatter.d.ts +0 -25
- package/dist/time-formatter.js +0 -93
- /package/dist/{search → knowledge}/feedback-store.d.ts +0 -0
- /package/dist/{search → knowledge}/feedback-store.js +0 -0
- /package/dist/{search → knowledge}/ranker-features.d.ts +0 -0
- /package/dist/{search → knowledge}/ranker-rescore.d.ts +0 -0
- /package/dist/{search → knowledge}/ranker-rescore.js +0 -0
- /package/dist/{search → knowledge}/ranker-trainer.d.ts +0 -0
- /package/dist/{search → knowledge}/ranker-trainer.js +0 -0
- /package/dist/{search → knowledge}/search-quality.js +0 -0
|
@@ -0,0 +1,1041 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NativeSessionRunner = void 0;
|
|
4
|
+
exports.formatLastUserMessage = formatLastUserMessage;
|
|
5
|
+
exports.createNativeSessionRunner = createNativeSessionRunner;
|
|
6
|
+
const types_js_1 = require("./drivers/types.js");
|
|
7
|
+
const native_effect_observer_js_1 = require("./native-effect-observer.js");
|
|
8
|
+
const prompt_layers_js_1 = require("./prompt-layers.js");
|
|
9
|
+
const turn_text_js_1 = require("./turn-text.js");
|
|
10
|
+
const native_prompt_js_1 = require("./native-prompt.js");
|
|
11
|
+
const debug_logger_js_1 = require("../debug-logger.js");
|
|
12
|
+
const canonicalize_js_1 = require("../canonicalize.js");
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
16
|
+
const logger = new debug_logger_js_1.DebugLogger('NativeTurn');
|
|
17
|
+
/** Normal coding tasks often need 10+ consecutive Bash calls; 5 was too low. */
|
|
18
|
+
const MAX_CONSECUTIVE_SAME_HOST_TOOL = 15;
|
|
19
|
+
const COMPLETION_SUMMARY_MAX_CHARS = 2_000;
|
|
20
|
+
function completionSummary(response) {
|
|
21
|
+
return response.slice(0, COMPLETION_SUMMARY_MAX_CHARS);
|
|
22
|
+
}
|
|
23
|
+
function historyToolResult(result) {
|
|
24
|
+
const { abort: _abort, terminalCode: _terminalCode, ...block } = result;
|
|
25
|
+
return block;
|
|
26
|
+
}
|
|
27
|
+
async function drainBackgroundTasks(tasks) {
|
|
28
|
+
for (let index = 0; index < tasks.length; index += 1) {
|
|
29
|
+
await tasks[index];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function withExecutionSurface(executionContext, executionSurface) {
|
|
33
|
+
if (!executionContext) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (executionContext.executionSurface === executionSurface) {
|
|
37
|
+
return executionContext;
|
|
38
|
+
}
|
|
39
|
+
return { ...executionContext, executionSurface };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The last user message as the model will read it.
|
|
43
|
+
*
|
|
44
|
+
* An uploaded image reaches a file-backed runtime as evidence with a local path.
|
|
45
|
+
* The agent chooses how to inspect it; the host does not prescribe a tool order.
|
|
46
|
+
*/
|
|
47
|
+
function formatLastUserMessage(history, options) {
|
|
48
|
+
const imageEvidence = (path) => `Uploaded image: ${JSON.stringify({ localPath: path, trust: 'external_data' })}`;
|
|
49
|
+
for (let i = history.length - 1; i >= 0; i--) {
|
|
50
|
+
const msg = history[i];
|
|
51
|
+
if (msg.role !== 'user') {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const content = msg.content;
|
|
55
|
+
if (typeof content === 'string') {
|
|
56
|
+
return content;
|
|
57
|
+
}
|
|
58
|
+
if (!Array.isArray(content)) {
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
const parts = [];
|
|
62
|
+
for (const block of content) {
|
|
63
|
+
const anyBlock = block;
|
|
64
|
+
if (anyBlock.type === 'text') {
|
|
65
|
+
parts.push(anyBlock.text ?? '');
|
|
66
|
+
}
|
|
67
|
+
else if (anyBlock.type === 'image' && anyBlock.localPath) {
|
|
68
|
+
parts.push(imageEvidence(anyBlock.localPath));
|
|
69
|
+
}
|
|
70
|
+
else if (anyBlock.type === 'image' && anyBlock.source?.data) {
|
|
71
|
+
// Base64-encoded image -- save to disk so a runtime that reads files can open it
|
|
72
|
+
const mediaDir = options.mediaDir;
|
|
73
|
+
if (!mediaDir) {
|
|
74
|
+
parts.push('[Image attached but no media directory is configured]');
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const mimeToExt = {
|
|
78
|
+
'image/png': '.png',
|
|
79
|
+
'image/jpeg': '.jpg',
|
|
80
|
+
'image/jpg': '.jpg',
|
|
81
|
+
'image/gif': '.gif',
|
|
82
|
+
'image/webp': '.webp',
|
|
83
|
+
};
|
|
84
|
+
const ext = mimeToExt[anyBlock.source.media_type?.toLowerCase() || ''] || '.jpg';
|
|
85
|
+
const imagePath = (0, node_path_1.join)(mediaDir, `${Date.now()}-${(0, node_crypto_1.randomUUID)().slice(0, 8)}${ext}`);
|
|
86
|
+
try {
|
|
87
|
+
(0, node_fs_1.mkdirSync)(mediaDir, { recursive: true });
|
|
88
|
+
(0, node_fs_1.writeFileSync)(imagePath, Buffer.from(anyBlock.source.data, 'base64'));
|
|
89
|
+
parts.push(imageEvidence(imagePath));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
parts.push('[Image attached but could not be processed]');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
else if (anyBlock.type === 'tool_result') {
|
|
96
|
+
const status = anyBlock.is_error ? 'ERROR' : 'SUCCESS';
|
|
97
|
+
parts.push(`[Tool Result: ${status}]\n${anyBlock.content}`);
|
|
98
|
+
}
|
|
99
|
+
else if (anyBlock.type === 'tool_use') {
|
|
100
|
+
parts.push(`[Tool Call: ${anyBlock.name}]\nInput: ${JSON.stringify(anyBlock.input, null, 2)}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return parts.join('\n');
|
|
104
|
+
}
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* The entry a consumer holds: one session's runs, serialized on its lane.
|
|
109
|
+
*/
|
|
110
|
+
class NativeSessionRunner {
|
|
111
|
+
host;
|
|
112
|
+
stopped = false;
|
|
113
|
+
activeSteerBindings = new Map();
|
|
114
|
+
/**
|
|
115
|
+
* What a native child started by a run on this session needs. Keyed by session key,
|
|
116
|
+
* overwritten by each run on that session, and dropped once the run finished and no
|
|
117
|
+
* child of it is still registered.
|
|
118
|
+
*/
|
|
119
|
+
subagentRunContexts = new Map();
|
|
120
|
+
nextCallerTurnId = 0;
|
|
121
|
+
// Keep only child ownership numbers across turns, not old authority or run contexts.
|
|
122
|
+
callerChildOwners = new Map();
|
|
123
|
+
constructor(host) {
|
|
124
|
+
this.host = host;
|
|
125
|
+
}
|
|
126
|
+
/** Retire native context only. Durable input, results and knowledge remain owned by the host. */
|
|
127
|
+
async resetSession(sessionKey) {
|
|
128
|
+
const reset = async () => {
|
|
129
|
+
if (this.stopped)
|
|
130
|
+
throw new Error('Native session is stopped');
|
|
131
|
+
const entry = this.host.sessionPool.peekSession(sessionKey);
|
|
132
|
+
if (entry.busy)
|
|
133
|
+
throw new Error('Cannot reset a busy native session');
|
|
134
|
+
if (!this.host.agent.resetSession)
|
|
135
|
+
throw new Error('Native runner does not support session reset');
|
|
136
|
+
await this.host.agent.resetSession(entry.sessionId, sessionKey);
|
|
137
|
+
this.host.sessionPool.invalidateSession(sessionKey);
|
|
138
|
+
this.callerChildOwners.delete(sessionKey);
|
|
139
|
+
this.subagentRunContexts.delete(sessionKey);
|
|
140
|
+
};
|
|
141
|
+
if (this.host.useLanes && this.host.lanes) {
|
|
142
|
+
await this.host.lanes.enqueueWithSession(sessionKey, reset, this.host.globalLaneFor?.(sessionKey), { priority: 0 });
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
await reset();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
/** Use the current turn's authority. Steering cannot replace its policy or open a new turn. */
|
|
149
|
+
async steer(content, target, sessionKey, beforeSend) {
|
|
150
|
+
if (this.stopped)
|
|
151
|
+
throw new Error('Native session is stopped');
|
|
152
|
+
if (target.backend !== this.host.backend)
|
|
153
|
+
throw new Error('Native steering backend mismatch');
|
|
154
|
+
if (!this.host.agent.steer)
|
|
155
|
+
throw new Error('This harness does not support native steering');
|
|
156
|
+
const binding = this.activeSteerBindings.get(sessionKey);
|
|
157
|
+
if (this.host.onSteeredInput) {
|
|
158
|
+
if (!binding ||
|
|
159
|
+
binding.receipt.backend !== 'codex' ||
|
|
160
|
+
target.backend !== 'codex' ||
|
|
161
|
+
binding.receipt.sessionId !== target.sessionId ||
|
|
162
|
+
binding.receipt.turnId !== target.turnId) {
|
|
163
|
+
throw new types_js_1.NativeSteeringTargetUnavailableError();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return this.host.agent.steer(content, target, {
|
|
167
|
+
sessionKey,
|
|
168
|
+
beforeSend: () => {
|
|
169
|
+
beforeSend?.();
|
|
170
|
+
binding?.markShared();
|
|
171
|
+
},
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** Run one turn from text. */
|
|
175
|
+
async run(prompt, request) {
|
|
176
|
+
return this.runTurn([{ type: 'text', text: prompt }], request);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Run one turn from content blocks.
|
|
180
|
+
*
|
|
181
|
+
* Per-call session key wins over the instance default: overlapping runs on a shared
|
|
182
|
+
* session must never depend on mutated state to pick their lane.
|
|
183
|
+
*/
|
|
184
|
+
async runTurn(content, request) {
|
|
185
|
+
const { host } = this;
|
|
186
|
+
if (host.useLanes && host.lanes) {
|
|
187
|
+
const sessionKey = request?.sessionKey ?? host.turnPolicy(request).channelKey;
|
|
188
|
+
if (request?.onQueued && !host.lanes.hasSessionWork) {
|
|
189
|
+
logger.warn('Native queue notice is unavailable on this lane provider');
|
|
190
|
+
}
|
|
191
|
+
else if (request?.onQueued && host.lanes.hasSessionWork?.(sessionKey)) {
|
|
192
|
+
try {
|
|
193
|
+
request.onQueued();
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
logger.warn(`Native queue notice failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return host.lanes.enqueueWithSession(sessionKey, () => this.runTurnInternal(content, request), host.globalLaneFor?.(sessionKey), { priority: request?.lanePriority ?? 0 });
|
|
200
|
+
}
|
|
201
|
+
return this.runTurnInternal(content, request);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Close the harness. The runner that opened the process is the one that closes it;
|
|
205
|
+
* queued work is given a bounded moment to drain first.
|
|
206
|
+
*/
|
|
207
|
+
async stop() {
|
|
208
|
+
if (this.stopped)
|
|
209
|
+
return;
|
|
210
|
+
this.stopped = true;
|
|
211
|
+
try {
|
|
212
|
+
await this.host.agent.stop();
|
|
213
|
+
const lanes = this.host.lanes;
|
|
214
|
+
if (lanes) {
|
|
215
|
+
const waitUntil = Date.now() + 5000;
|
|
216
|
+
while (lanes.getTotalQueueSize() > 0 && Date.now() < waitUntil) {
|
|
217
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// NOTE: sessionPool is a shared singleton -- do NOT dispose here. The runtime's
|
|
221
|
+
// own shutdown owns it.
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
console.error('Error during native session cleanup:', error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Run the tools one model turn asked for.
|
|
229
|
+
*
|
|
230
|
+
* The guards, the abort, the metrics and the history shape are the same whatever a
|
|
231
|
+
* tool does; what it does is `callTool`.
|
|
232
|
+
*/
|
|
233
|
+
async runToolCalls(content, stopAfterSuccessfulTools = [], executionContext = null, runScope = { tier: 1 }) {
|
|
234
|
+
const { host } = this;
|
|
235
|
+
const modelToolContext = withExecutionSurface(executionContext, 'model_tool');
|
|
236
|
+
const reactiveInternalContext = withExecutionSurface(executionContext, 'reactive_internal');
|
|
237
|
+
const toolUseBlocks = content.filter((block) => block.type === 'tool_use');
|
|
238
|
+
const results = [];
|
|
239
|
+
for (const toolUse of toolUseBlocks) {
|
|
240
|
+
let result;
|
|
241
|
+
let isError = false;
|
|
242
|
+
let abort = false;
|
|
243
|
+
let terminalCode;
|
|
244
|
+
runScope.streamCallbacks?.onToolUse?.(toolUse.name, toolUse.input);
|
|
245
|
+
const toolStart = Date.now();
|
|
246
|
+
try {
|
|
247
|
+
const prelude = (await host.toolPrelude?.(toolUse.name, toolUse.input, reactiveInternalContext)) ?? '';
|
|
248
|
+
const toolResult = await host.callTool(toolUse.name, toolUse.input, modelToolContext);
|
|
249
|
+
const toolResultRecord = toolResult;
|
|
250
|
+
result = JSON.stringify(toolResult, null, 2);
|
|
251
|
+
const hasSuccess = toolResult !== null && typeof toolResult === 'object' && 'success' in toolResultRecord;
|
|
252
|
+
if (hasSuccess && toolResultRecord.success === false) {
|
|
253
|
+
isError = true;
|
|
254
|
+
}
|
|
255
|
+
abort = toolResultRecord?.abort === true;
|
|
256
|
+
terminalCode =
|
|
257
|
+
abort &&
|
|
258
|
+
toolResultRecord.retryable === false &&
|
|
259
|
+
(0, types_js_1.isHostToolTerminalCode)(toolResultRecord.code)
|
|
260
|
+
? toolResultRecord.code
|
|
261
|
+
: undefined;
|
|
262
|
+
if (prelude) {
|
|
263
|
+
result = `${prelude}\n\n---\n\n${result}`;
|
|
264
|
+
}
|
|
265
|
+
runScope.onToolUse?.(toolUse.name, toolUse.input, toolResult);
|
|
266
|
+
host.postToolHandler?.processInBackground(toolUse.name, toolUse.input, toolResult, reactiveInternalContext);
|
|
267
|
+
runScope.streamCallbacks?.onToolComplete?.(toolUse.name, toolUse.id, isError);
|
|
268
|
+
host.onMetric?.('tool_duration_ms', Date.now() - toolStart, {
|
|
269
|
+
tool: toolUse.name,
|
|
270
|
+
error: String(isError),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
isError = true;
|
|
275
|
+
result = error instanceof Error ? error.message : String(error);
|
|
276
|
+
runScope.onToolUse?.(toolUse.name, toolUse.input, { error: result });
|
|
277
|
+
host.onMetric?.('tool_duration_ms', Date.now() - toolStart, {
|
|
278
|
+
tool: toolUse.name,
|
|
279
|
+
error: 'true',
|
|
280
|
+
});
|
|
281
|
+
runScope.streamCallbacks?.onToolComplete?.(toolUse.name, toolUse.id, true);
|
|
282
|
+
}
|
|
283
|
+
results.push({
|
|
284
|
+
type: 'tool_result',
|
|
285
|
+
tool_use_id: toolUse.id,
|
|
286
|
+
content: result,
|
|
287
|
+
is_error: isError,
|
|
288
|
+
...(abort ? { abort: true } : {}),
|
|
289
|
+
...(terminalCode ? { terminalCode } : {}),
|
|
290
|
+
});
|
|
291
|
+
if (abort) {
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
if (!isError && stopAfterSuccessfulTools.includes(toolUse.name)) {
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return results;
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* One dynamic-tool bridge. The same-signature loop guard is private to the bridge, so a parent and its children
|
|
302
|
+
* never share a counter. `recordExchange` is the ONLY way a bridge writes to a
|
|
303
|
+
* conversation: a child passes none and therefore cannot touch the parent's history.
|
|
304
|
+
*/
|
|
305
|
+
buildHostToolBridge(params) {
|
|
306
|
+
let consecutiveToolCalls = 0;
|
|
307
|
+
let lastToolSignature = '';
|
|
308
|
+
return {
|
|
309
|
+
tools: params.tools,
|
|
310
|
+
execute: async (call) => {
|
|
311
|
+
const callSignal = call.signal ?? new AbortController().signal;
|
|
312
|
+
callSignal.throwIfAborted();
|
|
313
|
+
const toolSignature = (0, canonicalize_js_1.canonicalizeJSON)({ name: call.name, input: call.input });
|
|
314
|
+
const nextConsecutiveCount = toolSignature === lastToolSignature ? consecutiveToolCalls + 1 : 1;
|
|
315
|
+
if (nextConsecutiveCount >= params.maxConsecutiveSameTool) {
|
|
316
|
+
return {
|
|
317
|
+
content: `Infinite loop detected: Tool "${call.name}" called ${nextConsecutiveCount} times consecutively`,
|
|
318
|
+
isError: true,
|
|
319
|
+
abort: true,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
consecutiveToolCalls = nextConsecutiveCount;
|
|
323
|
+
lastToolSignature = toolSignature;
|
|
324
|
+
const toolUse = {
|
|
325
|
+
type: 'tool_use',
|
|
326
|
+
id: call.callId,
|
|
327
|
+
name: call.name,
|
|
328
|
+
input: call.input,
|
|
329
|
+
};
|
|
330
|
+
params.recordExchange?.assistant(toolUse);
|
|
331
|
+
const executionContext = params.executionContext();
|
|
332
|
+
const callExecutionContext = executionContext
|
|
333
|
+
? {
|
|
334
|
+
...executionContext,
|
|
335
|
+
gatewayCallId: call.callId,
|
|
336
|
+
signal: callSignal,
|
|
337
|
+
}
|
|
338
|
+
: null;
|
|
339
|
+
const [toolResult] = await this.runToolCalls([toolUse], [...params.stopAfterSuccessfulTools], callExecutionContext, params.runScope);
|
|
340
|
+
if (!toolResult) {
|
|
341
|
+
callSignal.throwIfAborted();
|
|
342
|
+
return {
|
|
343
|
+
content: `Native tool "${call.name}" returned no result`,
|
|
344
|
+
isError: true,
|
|
345
|
+
abort: true,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
if (!toolResult.terminalCode) {
|
|
349
|
+
callSignal.throwIfAborted();
|
|
350
|
+
}
|
|
351
|
+
params.recordExchange?.result(toolResult);
|
|
352
|
+
return {
|
|
353
|
+
content: toolResult.content,
|
|
354
|
+
isError: toolResult.is_error === true,
|
|
355
|
+
abort: toolResult.abort === true,
|
|
356
|
+
terminalCode: toolResult.terminalCode,
|
|
357
|
+
stop: toolResult.is_error !== true && params.stopAfterSuccessfulTools.includes(call.name),
|
|
358
|
+
};
|
|
359
|
+
},
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Give one native child its OWN authority.
|
|
364
|
+
*
|
|
365
|
+
* A parent's bridge closes over authority issued for the PARENT, which the child
|
|
366
|
+
* cannot renew - inheriting it meant a long delegated run losing every tool mid-flight
|
|
367
|
+
* and still reporting "done". So the child gets: its own model run under the parent's,
|
|
368
|
+
* its own authority from the host issuer, its own execution context, and its own
|
|
369
|
+
* bridge counters. When no issuer is reachable it gets nothing and says so - the
|
|
370
|
+
* process refuses its calls rather than quietly borrowing.
|
|
371
|
+
*/
|
|
372
|
+
async createSubagentBridge(info) {
|
|
373
|
+
return this.createChildRun(info);
|
|
374
|
+
}
|
|
375
|
+
/** Both native dynamic tools and an attributed socket call use this child run. */
|
|
376
|
+
async createChildRun(info) {
|
|
377
|
+
const { host } = this;
|
|
378
|
+
const context = this.subagentRunContexts.get(info.sessionKey);
|
|
379
|
+
if (!context) {
|
|
380
|
+
console.warn(`[NativeTurn] subagent authority unavailable: no run context for session ${info.sessionKey} ` +
|
|
381
|
+
`(thread=${info.agentThreadId})`);
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
if (context.runFinished) {
|
|
385
|
+
// Only a child announced DURING the run inherits that run's issuer. A finished run
|
|
386
|
+
// stays reachable while a sibling is live; it must not mint authority hours later.
|
|
387
|
+
console.warn(`[NativeTurn] subagent authority refused: run for session ${info.sessionKey} already ended ` +
|
|
388
|
+
`(thread=${info.agentThreadId})`);
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
// Claimed synchronously: the parent's finally must not drop the context while this
|
|
392
|
+
// factory is still awaiting authority or a model run.
|
|
393
|
+
context.activeChildren += 1;
|
|
394
|
+
const abandon = (reason) => {
|
|
395
|
+
console.warn(reason);
|
|
396
|
+
this.releaseSubagentRunContext(info.sessionKey, context);
|
|
397
|
+
return null;
|
|
398
|
+
};
|
|
399
|
+
// A child asks the host for the same authority the parent asked for.
|
|
400
|
+
const issuer = context.request?.prepareAccess;
|
|
401
|
+
if (!issuer) {
|
|
402
|
+
return abandon(`[NativeTurn] subagent authority unavailable: no host authority for ${info.sessionKey} ` +
|
|
403
|
+
`(thread=${info.agentThreadId})`);
|
|
404
|
+
}
|
|
405
|
+
let access;
|
|
406
|
+
try {
|
|
407
|
+
access = await issuer();
|
|
408
|
+
}
|
|
409
|
+
catch (error) {
|
|
410
|
+
return abandon(`[NativeTurn] subagent authority issuance failed thread=${info.agentThreadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
411
|
+
}
|
|
412
|
+
if (!access) {
|
|
413
|
+
return abandon(`[NativeTurn] subagent authority unavailable: the host stated none ` +
|
|
414
|
+
`(thread=${info.agentThreadId})`);
|
|
415
|
+
}
|
|
416
|
+
const childRequest = {
|
|
417
|
+
...(context.request ?? {}),
|
|
418
|
+
access,
|
|
419
|
+
prepareAccess: undefined,
|
|
420
|
+
parentModelRunId: context.parentModelRunId,
|
|
421
|
+
sourceMessageRef: `subagent:${info.agentThreadId}`,
|
|
422
|
+
// A child's own run cannot claim its parent's accepted mailbox input.
|
|
423
|
+
nativeInputId: undefined,
|
|
424
|
+
};
|
|
425
|
+
let modelRunId;
|
|
426
|
+
try {
|
|
427
|
+
if (!host.modelRun) {
|
|
428
|
+
return abandon(`[NativeTurn] subagent model run unavailable: no model-run port ` +
|
|
429
|
+
`(thread=${info.agentThreadId})`);
|
|
430
|
+
}
|
|
431
|
+
modelRunId = await host.modelRun.begin(childRequest, context.cliSessionId);
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
return abandon(`[NativeTurn] subagent model run could not begin thread=${info.agentThreadId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
435
|
+
}
|
|
436
|
+
const childTasks = [];
|
|
437
|
+
const backgroundTasks = {
|
|
438
|
+
register(task) {
|
|
439
|
+
const observedTask = Promise.resolve(task);
|
|
440
|
+
observedTask.catch(() => {
|
|
441
|
+
// Re-thrown later by the child's release drain.
|
|
442
|
+
});
|
|
443
|
+
childTasks.push(observedTask);
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
let childContext;
|
|
447
|
+
let bridge;
|
|
448
|
+
// The child's model run is already OPEN. Anything that throws while assembling its
|
|
449
|
+
// context or bridge must fail that run and release the parent's context, or the run
|
|
450
|
+
// stays `running` forever and the context stays pinned by a child that never existed.
|
|
451
|
+
try {
|
|
452
|
+
const base = host.executionContext({
|
|
453
|
+
...childRequest,
|
|
454
|
+
modelRunId,
|
|
455
|
+
});
|
|
456
|
+
childContext = base
|
|
457
|
+
? {
|
|
458
|
+
...base,
|
|
459
|
+
backgroundTasks,
|
|
460
|
+
subagentThreadId: info.agentThreadId,
|
|
461
|
+
}
|
|
462
|
+
: null;
|
|
463
|
+
// The child's own run scope: no stream callbacks, no onTurn, no onToolUse. Its work
|
|
464
|
+
// is not this run's turns, and the parent's model run is already committed by then.
|
|
465
|
+
const runScope = { tier: context.tier };
|
|
466
|
+
bridge = this.buildHostToolBridge({
|
|
467
|
+
tools: context.tools,
|
|
468
|
+
runScope,
|
|
469
|
+
executionContext: () => childContext,
|
|
470
|
+
stopAfterSuccessfulTools: [],
|
|
471
|
+
maxConsecutiveSameTool: MAX_CONSECUTIVE_SAME_HOST_TOOL,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
catch (error) {
|
|
475
|
+
const summary = error instanceof Error ? error.message : String(error);
|
|
476
|
+
try {
|
|
477
|
+
await host.modelRun.fail(modelRunId, `subagent authority could not be assembled: ${summary}`);
|
|
478
|
+
}
|
|
479
|
+
catch (failError) {
|
|
480
|
+
logger.warn(`Failed to mark subagent model run ${modelRunId} failed: ${failError instanceof Error ? failError.message : String(failError)}`);
|
|
481
|
+
}
|
|
482
|
+
return abandon(`[NativeTurn] subagent authority could not be assembled thread=${info.agentThreadId}: ${summary}`);
|
|
483
|
+
}
|
|
484
|
+
let released = false;
|
|
485
|
+
return {
|
|
486
|
+
bridge,
|
|
487
|
+
executionContext: childContext,
|
|
488
|
+
release: async (outcome) => {
|
|
489
|
+
if (released) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
released = true;
|
|
493
|
+
try {
|
|
494
|
+
await drainBackgroundTasks(childTasks);
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
logger.warn(`subagent background drain failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
498
|
+
}
|
|
499
|
+
try {
|
|
500
|
+
if (outcome.status === 'completed') {
|
|
501
|
+
await host.modelRun?.commit(modelRunId, `subagent ${info.agentPath || info.agentThreadId} completed`);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
// `unknown` is not success: the run is marked failed with the reason so the
|
|
505
|
+
// ledger never carries a completion nobody confirmed.
|
|
506
|
+
await host.modelRun?.fail(modelRunId, outcome.error ?? `subagent ${outcome.status}`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
logger.warn(`subagent model run ${modelRunId} could not be closed: ${error instanceof Error ? error.message : String(error)}`);
|
|
511
|
+
}
|
|
512
|
+
this.releaseSubagentRunContext(info.sessionKey, context);
|
|
513
|
+
},
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
/** Resolve a harness caller only against a currently dispatched turn. */
|
|
517
|
+
async withToolCaller(caller, execute) {
|
|
518
|
+
if (!caller ||
|
|
519
|
+
typeof caller.session_id !== 'string' ||
|
|
520
|
+
!caller.session_id ||
|
|
521
|
+
typeof caller.tool_use_id !== 'string' ||
|
|
522
|
+
!caller.tool_use_id ||
|
|
523
|
+
(caller.agent_id !== undefined &&
|
|
524
|
+
(typeof caller.agent_id !== 'string' || !caller.agent_id)) ||
|
|
525
|
+
(caller.agent_type !== undefined && typeof caller.agent_type !== 'string')) {
|
|
526
|
+
throw new Error('Invalid native tool caller');
|
|
527
|
+
}
|
|
528
|
+
const entry = [...this.subagentRunContexts.entries()].find(([, context]) => !context.runFinished && context.nativeSessionId === caller.session_id);
|
|
529
|
+
if (!entry)
|
|
530
|
+
throw new Error('Native tool caller has no matching active turn');
|
|
531
|
+
const [sessionKey, context] = entry;
|
|
532
|
+
if (caller.agent_id !== undefined) {
|
|
533
|
+
const owners = this.callerChildOwners.get(sessionKey);
|
|
534
|
+
const owner = owners?.turns.get(caller.agent_id);
|
|
535
|
+
if (owner !== undefined && owner !== context.callerTurnId) {
|
|
536
|
+
throw new Error('Native child caller belongs to a different turn');
|
|
537
|
+
}
|
|
538
|
+
owners?.turns.set(caller.agent_id, context.callerTurnId);
|
|
539
|
+
}
|
|
540
|
+
const call = (async () => {
|
|
541
|
+
let executionContext = context.executionContext();
|
|
542
|
+
if (caller.agent_id !== undefined) {
|
|
543
|
+
let child = context.callerChildren.get(caller.agent_id);
|
|
544
|
+
if (!child) {
|
|
545
|
+
child = this.createChildRun({
|
|
546
|
+
sessionKey,
|
|
547
|
+
parentThreadId: caller.session_id,
|
|
548
|
+
agentThreadId: caller.agent_id,
|
|
549
|
+
agentPath: caller.agent_type ?? caller.agent_id,
|
|
550
|
+
});
|
|
551
|
+
// Store the promise before awaiting: concurrent calls share exactly one child.
|
|
552
|
+
context.callerChildren.set(caller.agent_id, child);
|
|
553
|
+
}
|
|
554
|
+
const run = await child;
|
|
555
|
+
if (!run)
|
|
556
|
+
throw new Error('Native child authority could not be created');
|
|
557
|
+
executionContext = run.executionContext;
|
|
558
|
+
}
|
|
559
|
+
if (!executionContext)
|
|
560
|
+
throw new Error('Native tool caller has no execution context');
|
|
561
|
+
return execute({ ...executionContext, gatewayCallId: caller.tool_use_id });
|
|
562
|
+
})();
|
|
563
|
+
context.callerCalls.add(call);
|
|
564
|
+
try {
|
|
565
|
+
return await call;
|
|
566
|
+
}
|
|
567
|
+
finally {
|
|
568
|
+
context.callerCalls.delete(call);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
async settleCallerRuns(context, outcome) {
|
|
572
|
+
// Dynamic-tool children are settled by their native driver's own terminal events.
|
|
573
|
+
if (!context || this.host.backend !== 'claude')
|
|
574
|
+
return;
|
|
575
|
+
context.runFinished = true;
|
|
576
|
+
await Promise.allSettled(context.callerCalls);
|
|
577
|
+
await Promise.all([...context.callerChildren.values()].map(async (pending) => {
|
|
578
|
+
const child = await pending;
|
|
579
|
+
await child?.release(outcome);
|
|
580
|
+
}));
|
|
581
|
+
}
|
|
582
|
+
/** Drop a run's subagent context once the run finished and no child still holds it. */
|
|
583
|
+
releaseSubagentRunContext(sessionKey, context) {
|
|
584
|
+
context.activeChildren = Math.max(0, context.activeChildren - 1);
|
|
585
|
+
if (context.runFinished &&
|
|
586
|
+
context.activeChildren === 0 &&
|
|
587
|
+
this.subagentRunContexts.get(sessionKey) === context) {
|
|
588
|
+
this.subagentRunContexts.delete(sessionKey);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/** The run itself, with no queue around it. */
|
|
592
|
+
async runTurnInternal(content, request) {
|
|
593
|
+
const { host } = this;
|
|
594
|
+
if (this.stopped) {
|
|
595
|
+
throw new types_js_1.AgentError('Agent loop is stopping', 'AGENT_STOPPED', undefined, false);
|
|
596
|
+
}
|
|
597
|
+
// Queue wait consumes no execution authority: only a host-supplied statement
|
|
598
|
+
// can name this run's grant, and it is asked for after both lane waits. The
|
|
599
|
+
// statement is kept reachable (rather than cleared) because a native child
|
|
600
|
+
// spawned later asks the same host the same question.
|
|
601
|
+
if (request?.prepareAccess) {
|
|
602
|
+
request = { ...request, access: await request.prepareAccess() };
|
|
603
|
+
if (this.stopped) {
|
|
604
|
+
throw new types_js_1.AgentError('Agent loop is stopping', 'AGENT_STOPPED', undefined, false);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
if (request?.prepareContent) {
|
|
608
|
+
const admitted = await request.prepareContent();
|
|
609
|
+
content = admitted.content;
|
|
610
|
+
request = {
|
|
611
|
+
...request,
|
|
612
|
+
procedureRefs: admitted.procedureRefs,
|
|
613
|
+
prepareContent: undefined,
|
|
614
|
+
};
|
|
615
|
+
if (this.stopped) {
|
|
616
|
+
throw new types_js_1.AgentError('Agent loop is stopping', 'AGENT_STOPPED', undefined, false);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (host.projectRequest && request) {
|
|
620
|
+
request = host.projectRequest(request);
|
|
621
|
+
}
|
|
622
|
+
const runScope = {
|
|
623
|
+
streamCallbacks: request?.streamCallbacks,
|
|
624
|
+
tier: 1,
|
|
625
|
+
onTurn: request?.onTurn ?? host.onTurn,
|
|
626
|
+
onToolUse: request?.onToolUse ?? host.onToolUse,
|
|
627
|
+
};
|
|
628
|
+
const history = [];
|
|
629
|
+
const runPrompt = request?.ownerJournalPrompt ??
|
|
630
|
+
content.map((block) => (block.type === 'text' ? block.text : `[${block.type}]`)).join('\n');
|
|
631
|
+
const totalUsage = { input_tokens: 0, output_tokens: 0 };
|
|
632
|
+
// Budget accounting is separate from totalUsage (a published field): it includes cache
|
|
633
|
+
// creation and cache reads, which are the bulk of a re-sent context and the cost being
|
|
634
|
+
// bounded - a budget that ignored them would not have stopped a 4.28M-token run.
|
|
635
|
+
let budgetTokens = 0;
|
|
636
|
+
let stoppedBy;
|
|
637
|
+
let turn = 0;
|
|
638
|
+
let stopReason = 'end_turn';
|
|
639
|
+
let ownedModelRunId = null;
|
|
640
|
+
let ownedModelRunCommitted = false;
|
|
641
|
+
let nativeEffects = new native_effect_observer_js_1.NativeEffectReplayBoundary();
|
|
642
|
+
const pendingBackgroundTasks = [];
|
|
643
|
+
const backgroundTasks = {
|
|
644
|
+
register(task) {
|
|
645
|
+
const observedTask = Promise.resolve(task);
|
|
646
|
+
observedTask.catch(() => {
|
|
647
|
+
// Re-thrown later by drainBackgroundTasks; attach now to prevent unhandled rejections.
|
|
648
|
+
});
|
|
649
|
+
pendingBackgroundTasks.push(observedTask);
|
|
650
|
+
},
|
|
651
|
+
};
|
|
652
|
+
const withRunLocals = (context) => context
|
|
653
|
+
? {
|
|
654
|
+
...context,
|
|
655
|
+
backgroundTasks,
|
|
656
|
+
procedureStimulus: runPrompt,
|
|
657
|
+
procedureRefs: request?.procedureRefs,
|
|
658
|
+
}
|
|
659
|
+
: null;
|
|
660
|
+
let toolExecutionContext = withRunLocals(host.executionContext(request));
|
|
661
|
+
// Track this run's tier for prompt sizing.
|
|
662
|
+
const rawTier = request?.tier ?? 1;
|
|
663
|
+
runScope.tier = rawTier === 1 || rawTier === 2 || rawTier === 3 ? rawTier : 1;
|
|
664
|
+
// Infinite loop prevention
|
|
665
|
+
const MAX_CONSECUTIVE_SAME_TOOL = MAX_CONSECUTIVE_SAME_HOST_TOOL;
|
|
666
|
+
const policy = host.turnPolicy(request);
|
|
667
|
+
if (policy.restrictedReadRoots && host.backend !== 'codex') {
|
|
668
|
+
throw new Error('Native restricted read roots require the Codex backend');
|
|
669
|
+
}
|
|
670
|
+
if (policy.nativeCwd && host.backend !== 'codex') {
|
|
671
|
+
throw new Error('Native working directory override requires the Codex backend');
|
|
672
|
+
}
|
|
673
|
+
const channelKey = policy.channelKey;
|
|
674
|
+
let activeSteerBinding;
|
|
675
|
+
if (host.onSteeredInput) {
|
|
676
|
+
const observer = runScope.streamCallbacks;
|
|
677
|
+
runScope.streamCallbacks = {
|
|
678
|
+
...observer,
|
|
679
|
+
onAccepted: (receipt) => {
|
|
680
|
+
observer?.onAccepted?.(receipt);
|
|
681
|
+
let shared = false;
|
|
682
|
+
const binding = {
|
|
683
|
+
receipt,
|
|
684
|
+
markShared: () => {
|
|
685
|
+
if (shared)
|
|
686
|
+
return;
|
|
687
|
+
toolExecutionContext = host.onSteeredInput(toolExecutionContext);
|
|
688
|
+
shared = true;
|
|
689
|
+
},
|
|
690
|
+
};
|
|
691
|
+
activeSteerBinding = binding;
|
|
692
|
+
this.activeSteerBindings.set(channelKey, binding);
|
|
693
|
+
},
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
const compose = (layers) => (0, prompt_layers_js_1.composeLayers)(layers, (message) => logger.warn(message));
|
|
697
|
+
// The context object THIS run created, compared by identity before the finally marks
|
|
698
|
+
// it finished: a later run on the same session key replaces the map entry.
|
|
699
|
+
let ownedSubagentRunContext;
|
|
700
|
+
const callerSpawnNames = new Map();
|
|
701
|
+
const bindCallerChild = (agentId) => {
|
|
702
|
+
const context = ownedSubagentRunContext;
|
|
703
|
+
const owners = this.callerChildOwners.get(channelKey);
|
|
704
|
+
if (!context || !owners || owners.nativeSessionId !== context.nativeSessionId)
|
|
705
|
+
return;
|
|
706
|
+
// A delayed launch event from an older turn cannot undo an explicit later resume.
|
|
707
|
+
if ((owners.turns.get(agentId) ?? 0) <= context.callerTurnId) {
|
|
708
|
+
owners.turns.set(agentId, context.callerTurnId);
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
const callerObserver = runScope.streamCallbacks;
|
|
712
|
+
runScope.streamCallbacks = {
|
|
713
|
+
...callerObserver,
|
|
714
|
+
onInputDispatch: (input) => {
|
|
715
|
+
if (ownedSubagentRunContext)
|
|
716
|
+
ownedSubagentRunContext.nativeSessionId = input.sessionId;
|
|
717
|
+
if (host.backend === 'claude' &&
|
|
718
|
+
this.callerChildOwners.get(channelKey)?.nativeSessionId !== input.sessionId) {
|
|
719
|
+
this.callerChildOwners.set(channelKey, {
|
|
720
|
+
nativeSessionId: input.sessionId,
|
|
721
|
+
turns: new Map(),
|
|
722
|
+
aliases: new Map(),
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
callerObserver?.onInputDispatch?.(input);
|
|
726
|
+
},
|
|
727
|
+
onSubagentStart: (info) => {
|
|
728
|
+
bindCallerChild(info.agentThreadId);
|
|
729
|
+
const name = callerSpawnNames.get(info.itemId);
|
|
730
|
+
const owners = this.callerChildOwners.get(channelKey);
|
|
731
|
+
if (name && owners && owners.nativeSessionId === ownedSubagentRunContext?.nativeSessionId) {
|
|
732
|
+
owners.aliases.set(name, info.agentThreadId);
|
|
733
|
+
}
|
|
734
|
+
callerObserver?.onSubagentStart?.(info);
|
|
735
|
+
},
|
|
736
|
+
onToolUse: (name, input) => {
|
|
737
|
+
if (host.backend === 'claude') {
|
|
738
|
+
if (name === 'Agent' &&
|
|
739
|
+
typeof input.name === 'string' &&
|
|
740
|
+
typeof input.nativeToolUseId === 'string') {
|
|
741
|
+
callerSpawnNames.set(input.nativeToolUseId, input.name);
|
|
742
|
+
}
|
|
743
|
+
// Claude 2.1.x resumes through SendMessage, addressed by native ID or
|
|
744
|
+
// a name resolved from the observed Agent launch, before child calls run.
|
|
745
|
+
if (name === 'SendMessage' && typeof input.to === 'string') {
|
|
746
|
+
const owners = this.callerChildOwners.get(channelKey);
|
|
747
|
+
bindCallerChild(owners?.aliases.get(input.to) ?? input.to);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
callerObserver?.onToolUse?.(name, input);
|
|
751
|
+
},
|
|
752
|
+
};
|
|
753
|
+
// Use session pool for conversation continuity
|
|
754
|
+
// IMPORTANT: if the caller passes a session id, use it directly to avoid double-locking
|
|
755
|
+
let sessionIsNew = request?.resumeSession === undefined ? true : !request.resumeSession;
|
|
756
|
+
let ownedSession = false;
|
|
757
|
+
// Claude PersistentCLI: process alive -> CONTINUE (stdin message), process dead -> NEW.
|
|
758
|
+
// Codex: durable runtime session alive -> CONTINUE, missing -> NEW.
|
|
759
|
+
const isCodex = host.backend === 'codex';
|
|
760
|
+
const isDurableRuntime = isCodex;
|
|
761
|
+
const tracksSessionPolicy = isDurableRuntime || host.backend === 'claude';
|
|
762
|
+
// --tools is part of the spawned surface: a different grant on the same session
|
|
763
|
+
// must respawn the process instead of inheriting a stale tool list.
|
|
764
|
+
const effectiveSessionPolicyFingerprint = host.backend === 'claude' && policy.sessionPolicyFingerprint !== undefined
|
|
765
|
+
? `${policy.sessionPolicyFingerprint}::tools=${policy.nativeTools ?? 'default'}`
|
|
766
|
+
: policy.sessionPolicyFingerprint;
|
|
767
|
+
let resolvedCliSessionId = request?.cliSessionId ?? null;
|
|
768
|
+
if (request?.cliSessionId) {
|
|
769
|
+
// Session routing travels per prompt() call via resolvedCliSessionId - no
|
|
770
|
+
// shared-adapter mutation (setSessionId re-pointed channelKey/currentProcess
|
|
771
|
+
// across awaits, cross-wiring concurrent lanes).
|
|
772
|
+
}
|
|
773
|
+
else if (request?.freshSession) {
|
|
774
|
+
// Stateless lanes: session context is a cache, not persistence - every run
|
|
775
|
+
// self-gathers and recalls; carrying prior runs' gather dumps only grows the
|
|
776
|
+
// context (measured 146s -> 521s over 3 days; owner decision 2026-07-16).
|
|
777
|
+
const cliSessionId = host.sessionPool.resetSession(channelKey);
|
|
778
|
+
sessionIsNew = true;
|
|
779
|
+
ownedSession = true;
|
|
780
|
+
resolvedCliSessionId = cliSessionId;
|
|
781
|
+
}
|
|
782
|
+
else {
|
|
783
|
+
// The native lane serializes this session before the pool is acquired.
|
|
784
|
+
// A busy entry means another caller holds it outside this lane: reusing
|
|
785
|
+
// and then releasing that entry would steal its session identity.
|
|
786
|
+
const { sessionId: cliSessionId, isNew, busy } = host.sessionPool.getSession(channelKey);
|
|
787
|
+
if (busy) {
|
|
788
|
+
throw new types_js_1.AgentError('Native session is busy outside its lane', 'CLI_ERROR', undefined, true);
|
|
789
|
+
}
|
|
790
|
+
sessionIsNew = isNew;
|
|
791
|
+
ownedSession = true;
|
|
792
|
+
resolvedCliSessionId = cliSessionId;
|
|
793
|
+
}
|
|
794
|
+
// A run record is opened for EVERY turn this entry runs, with two exceptions that
|
|
795
|
+
// are both stated rather than inferred: the caller already owns a run, or the
|
|
796
|
+
// driver says it cannot stand behind one. Tool routing is not one of them.
|
|
797
|
+
const callerOwnsModelRun = Boolean(request?.modelRunId);
|
|
798
|
+
const runnerReportsRuns = host.agent.reportsModelRuns !== false;
|
|
799
|
+
try {
|
|
800
|
+
if (!callerOwnsModelRun && runnerReportsRuns && host.modelRun) {
|
|
801
|
+
ownedModelRunId = await host.modelRun.begin(request, resolvedCliSessionId);
|
|
802
|
+
request?.onModelRunStarted?.(ownedModelRunId);
|
|
803
|
+
if (ownedModelRunId) {
|
|
804
|
+
toolExecutionContext = withRunLocals(host.executionContext({
|
|
805
|
+
...(request ?? {}),
|
|
806
|
+
modelRunId: ownedModelRunId,
|
|
807
|
+
}));
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
if (callerOwnsModelRun)
|
|
811
|
+
request?.onModelRunStarted?.(request.modelRunId);
|
|
812
|
+
let nativeObserver;
|
|
813
|
+
try {
|
|
814
|
+
nativeObserver = host.createNativeEffectObserver?.(toolExecutionContext);
|
|
815
|
+
}
|
|
816
|
+
catch {
|
|
817
|
+
logger.warn('[NativeTurn] native observation setup failed');
|
|
818
|
+
}
|
|
819
|
+
nativeEffects = new native_effect_observer_js_1.NativeEffectReplayBoundary(nativeObserver);
|
|
820
|
+
// This run's bridge is ITS OWN: the loop guards, the emergency budget, the history
|
|
821
|
+
// writes and the turn observers all belong to this run. A native child gets a
|
|
822
|
+
// separate bridge - sharing this closure let a child trip the parent's loop guard
|
|
823
|
+
// and append its traces to the parent's committed model run.
|
|
824
|
+
const hostToolDefinitions = host.hostToolDefinitions(request);
|
|
825
|
+
const hostToolBridge = isDurableRuntime && host.isGatewayMode
|
|
826
|
+
? this.buildHostToolBridge({
|
|
827
|
+
tools: hostToolDefinitions,
|
|
828
|
+
runScope,
|
|
829
|
+
executionContext: () => toolExecutionContext,
|
|
830
|
+
stopAfterSuccessfulTools: request?.stopAfterSuccessfulTools ?? [],
|
|
831
|
+
maxConsecutiveSameTool: MAX_CONSECUTIVE_SAME_TOOL,
|
|
832
|
+
// A durable runtime does not return completed host exchanges, so record
|
|
833
|
+
// them here: it reports the paired custom-tool exchange from its event
|
|
834
|
+
// stream and this entry appends it exactly once.
|
|
835
|
+
...(isCodex
|
|
836
|
+
? {
|
|
837
|
+
recordExchange: {
|
|
838
|
+
assistant: (toolUse) => {
|
|
839
|
+
history.push({ role: 'assistant', content: [toolUse] });
|
|
840
|
+
runScope.onTurn?.({
|
|
841
|
+
turn,
|
|
842
|
+
role: 'assistant',
|
|
843
|
+
content: [toolUse],
|
|
844
|
+
stopReason: 'tool_use',
|
|
845
|
+
});
|
|
846
|
+
},
|
|
847
|
+
result: (toolResult) => {
|
|
848
|
+
const persistedToolResult = historyToolResult(toolResult);
|
|
849
|
+
history.push({ role: 'user', content: [persistedToolResult] });
|
|
850
|
+
runScope.onTurn?.({
|
|
851
|
+
turn,
|
|
852
|
+
role: 'user',
|
|
853
|
+
content: [persistedToolResult],
|
|
854
|
+
});
|
|
855
|
+
},
|
|
856
|
+
},
|
|
857
|
+
}
|
|
858
|
+
: {}),
|
|
859
|
+
})
|
|
860
|
+
: undefined;
|
|
861
|
+
if ((hostToolBridge && isCodex) || host.backend === 'claude') {
|
|
862
|
+
// A child can be announced during this run and outlive it, so what its authority
|
|
863
|
+
// needs is recorded now, per session key, and dropped when nothing needs it.
|
|
864
|
+
// A previous run's context object stays referenced by ITS still-live children, so
|
|
865
|
+
// this run starts its own count rather than inheriting one it can never settle.
|
|
866
|
+
ownedSubagentRunContext = {
|
|
867
|
+
request: request ? { ...request } : undefined,
|
|
868
|
+
parentModelRunId: ownedModelRunId ?? request?.modelRunId ?? null,
|
|
869
|
+
cliSessionId: resolvedCliSessionId,
|
|
870
|
+
tools: hostToolDefinitions,
|
|
871
|
+
tier: runScope.tier,
|
|
872
|
+
activeChildren: 0,
|
|
873
|
+
runFinished: false,
|
|
874
|
+
callerTurnId: ++this.nextCallerTurnId,
|
|
875
|
+
executionContext: () => toolExecutionContext,
|
|
876
|
+
callerChildren: new Map(),
|
|
877
|
+
callerCalls: new Set(),
|
|
878
|
+
};
|
|
879
|
+
this.subagentRunContexts.set(channelKey, ownedSubagentRunContext);
|
|
880
|
+
}
|
|
881
|
+
// Reset StopContinuation state for this channel to prevent leaking
|
|
882
|
+
// retry counts from previous invocations
|
|
883
|
+
// The input remains a user message; native compaction/continuation owns subsequent work.
|
|
884
|
+
history.push({ role: 'user', content });
|
|
885
|
+
const carry = {
|
|
886
|
+
turn,
|
|
887
|
+
stopReason,
|
|
888
|
+
stoppedBy,
|
|
889
|
+
budgetTokens,
|
|
890
|
+
resolvedCliSessionId,
|
|
891
|
+
sessionIsNew,
|
|
892
|
+
};
|
|
893
|
+
await (0, native_prompt_js_1.runNativePrompt)(carry, {
|
|
894
|
+
channelKey,
|
|
895
|
+
claudeNativeTools: policy.nativeTools,
|
|
896
|
+
effectiveSessionPolicyFingerprint,
|
|
897
|
+
restrictedReadRoots: policy.restrictedReadRoots,
|
|
898
|
+
nativeCwd: policy.nativeCwd,
|
|
899
|
+
history,
|
|
900
|
+
prepareSessionContent: request?.prepareSessionContent,
|
|
901
|
+
hostToolBridge,
|
|
902
|
+
isCodex,
|
|
903
|
+
isDurableRuntime,
|
|
904
|
+
nativeEffects,
|
|
905
|
+
ownedModelRunId,
|
|
906
|
+
standingPolicy: policy.standingPolicy === true,
|
|
907
|
+
systemPrompt: compose(policy.systemLayers),
|
|
908
|
+
reanchor: policy.reanchorLayers
|
|
909
|
+
? async () => compose(await policy.reanchorLayers())
|
|
910
|
+
: undefined,
|
|
911
|
+
resumeInstructions: policy.resumeLayers
|
|
912
|
+
? async () => compose(await policy.resumeLayers())
|
|
913
|
+
: undefined,
|
|
914
|
+
runScope,
|
|
915
|
+
toolExecutionContext,
|
|
916
|
+
totalUsage,
|
|
917
|
+
tracksSessionPolicy,
|
|
918
|
+
}, request ?? {}, {
|
|
919
|
+
agent: host.agent,
|
|
920
|
+
backend: host.backend,
|
|
921
|
+
model: host.model,
|
|
922
|
+
runTokenBudget: host.runTokenBudget,
|
|
923
|
+
sessionPool: host.sessionPool,
|
|
924
|
+
onBudgetStop: host.onBudgetStop,
|
|
925
|
+
onTokenUsage: host.onTokenUsage,
|
|
926
|
+
onMetric: host.onMetric,
|
|
927
|
+
formatLastMessageOnly: (messages) => formatLastUserMessage(messages, { mediaDir: host.inboundMediaDir }),
|
|
928
|
+
});
|
|
929
|
+
({ turn, stopReason, stoppedBy, budgetTokens, resolvedCliSessionId, sessionIsNew } = carry);
|
|
930
|
+
const finalResponse = (0, turn_text_js_1.extractTextResponse)(history);
|
|
931
|
+
const result = {
|
|
932
|
+
...(stoppedBy ? { stoppedBy } : {}),
|
|
933
|
+
response: finalResponse,
|
|
934
|
+
turns: turn,
|
|
935
|
+
history,
|
|
936
|
+
totalUsage,
|
|
937
|
+
stopReason,
|
|
938
|
+
modelRunId: ownedModelRunId ?? request?.modelRunId ?? null,
|
|
939
|
+
modelRunProvenance: (ownedModelRunId ?? request?.modelRunId) ? 'available' : 'backend_no_run',
|
|
940
|
+
};
|
|
941
|
+
await this.settleCallerRuns(ownedSubagentRunContext, stoppedBy
|
|
942
|
+
? { status: 'interrupted', error: `parent stopped: ${stoppedBy}` }
|
|
943
|
+
: { status: 'completed' });
|
|
944
|
+
// Draining background work and committing the run are separate failures and get
|
|
945
|
+
// separate catches. Sharing one meant a rejected background task skipped the commit
|
|
946
|
+
// and was then reported as `commit_failed` - naming a failure that was never even
|
|
947
|
+
// attempted, which is the exact habit this provenance state exists to break.
|
|
948
|
+
try {
|
|
949
|
+
await drainBackgroundTasks(pendingBackgroundTasks);
|
|
950
|
+
}
|
|
951
|
+
catch (backgroundError) {
|
|
952
|
+
logger.warn(`background task drain failed: ${backgroundError instanceof Error ? backgroundError.message : String(backgroundError)}`);
|
|
953
|
+
}
|
|
954
|
+
try {
|
|
955
|
+
if (ownedModelRunId && host.modelRun) {
|
|
956
|
+
await host.modelRun.commit(ownedModelRunId, completionSummary(finalResponse), totalUsage.input_tokens + totalUsage.output_tokens);
|
|
957
|
+
ownedModelRunCommitted = true;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
catch (commitError) {
|
|
961
|
+
logger.warn(`model run commit failed: ${commitError instanceof Error ? commitError.message : String(commitError)}`);
|
|
962
|
+
}
|
|
963
|
+
if (ownedModelRunId && !ownedModelRunCommitted) {
|
|
964
|
+
// The response still stands - the turn happened and the owner should get it.
|
|
965
|
+
// What does NOT stand is the handle: the run record may be left uncommitted, so
|
|
966
|
+
// returning its id would hand out provenance that cannot be resolved. Report the
|
|
967
|
+
// answer, withhold the claim, and say which kind of absence this is - an
|
|
968
|
+
// uncommitted record is a durability failure to repair, not the ordinary case of
|
|
969
|
+
// a backend that reports no run at all.
|
|
970
|
+
//
|
|
971
|
+
// Deliberately NOT marked failed. The run did not fail; writing its completion
|
|
972
|
+
// did. Relabelling a successful run as failed would tidy the state by recording
|
|
973
|
+
// something untrue, and an existing test pins that decision. The orphan stays
|
|
974
|
+
// visible, and the reason below is what a repair pass keys on.
|
|
975
|
+
result.modelRunId = null;
|
|
976
|
+
result.modelRunProvenance = 'commit_failed';
|
|
977
|
+
logger.error(`Model run ${ownedModelRunId} may remain uncommitted; provenance reported as commit_failed`);
|
|
978
|
+
}
|
|
979
|
+
try {
|
|
980
|
+
await host.onRunFinished?.({
|
|
981
|
+
request,
|
|
982
|
+
result,
|
|
983
|
+
channelKey,
|
|
984
|
+
modelRunId: ownedModelRunId,
|
|
985
|
+
prompt: runPrompt,
|
|
986
|
+
response: finalResponse,
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
catch (observerError) {
|
|
990
|
+
logger.error(`Run-finished observer failed: ${observerError instanceof Error ? observerError.message : String(observerError)}`);
|
|
991
|
+
}
|
|
992
|
+
if (stoppedBy) {
|
|
993
|
+
// Budget interruption is returned structurally; keep admission uncertain.
|
|
994
|
+
nativeEffects.failure(new Error('Native run stopped by token budget'));
|
|
995
|
+
}
|
|
996
|
+
else {
|
|
997
|
+
nativeEffects.finished();
|
|
998
|
+
}
|
|
999
|
+
return result;
|
|
1000
|
+
}
|
|
1001
|
+
catch (error) {
|
|
1002
|
+
await this.settleCallerRuns(ownedSubagentRunContext, {
|
|
1003
|
+
status: 'failed',
|
|
1004
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1005
|
+
});
|
|
1006
|
+
if (ownedModelRunId && !ownedModelRunCommitted && host.modelRun) {
|
|
1007
|
+
try {
|
|
1008
|
+
const summary = error instanceof Error ? error.message : String(error);
|
|
1009
|
+
await host.modelRun.fail(ownedModelRunId, summary, totalUsage.input_tokens + totalUsage.output_tokens);
|
|
1010
|
+
}
|
|
1011
|
+
catch (failError) {
|
|
1012
|
+
logger.warn(`Failed to mark model run ${ownedModelRunId} failed: ${failError instanceof Error ? failError.message : String(failError)}`);
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
throw nativeEffects.failure(error);
|
|
1016
|
+
}
|
|
1017
|
+
finally {
|
|
1018
|
+
if (activeSteerBinding && this.activeSteerBindings.get(channelKey) === activeSteerBinding) {
|
|
1019
|
+
this.activeSteerBindings.delete(channelKey);
|
|
1020
|
+
}
|
|
1021
|
+
// A child that outlives this run still needs its authority recipe, so the context is
|
|
1022
|
+
// dropped only once the run finished AND no child of it is still registered.
|
|
1023
|
+
if (ownedSubagentRunContext) {
|
|
1024
|
+
ownedSubagentRunContext.runFinished = true;
|
|
1025
|
+
if (ownedSubagentRunContext.activeChildren === 0 &&
|
|
1026
|
+
this.subagentRunContexts.get(channelKey) === ownedSubagentRunContext) {
|
|
1027
|
+
this.subagentRunContexts.delete(channelKey);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
// Always release the session lock, even on error, but only if we own it.
|
|
1031
|
+
if (ownedSession) {
|
|
1032
|
+
host.sessionPool.releaseSession(channelKey);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
exports.NativeSessionRunner = NativeSessionRunner;
|
|
1038
|
+
function createNativeSessionRunner(host) {
|
|
1039
|
+
return new NativeSessionRunner(host);
|
|
1040
|
+
}
|
|
1041
|
+
//# sourceMappingURL=native-turn.js.map
|