@llblab/pi-kit 0.38.3 → 0.39.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/CHANGELOG.md +10 -0
- package/README.md +2 -2
- package/node_modules/@llblab/pi-actors/AGENTS.md +108 -20
- package/node_modules/@llblab/pi-actors/BACKLOG.md +207 -4
- package/node_modules/@llblab/pi-actors/CHANGELOG.md +13 -0
- package/node_modules/@llblab/pi-actors/README.md +31 -1
- package/node_modules/@llblab/pi-actors/dist/index.js +1 -1
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.d.ts +37 -0
- package/node_modules/@llblab/pi-actors/dist/lib/async-runs.js +131 -62
- package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.d.ts +2 -0
- package/node_modules/@llblab/pi-actors/dist/lib/automatic-review-runtime.js +3 -2
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.d.ts +37 -0
- package/node_modules/@llblab/pi-actors/dist/lib/command-templates.js +263 -129
- package/node_modules/@llblab/pi-actors/dist/lib/execution-files.d.ts +32 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution-files.js +128 -0
- package/node_modules/@llblab/pi-actors/dist/lib/execution-output.js +5 -4
- package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.d.ts +19 -1
- package/node_modules/@llblab/pi-actors/dist/lib/execution-sessions.js +62 -26
- package/node_modules/@llblab/pi-actors/dist/lib/execution.d.ts +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/execution.js +6 -1
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.d.ts +9 -2
- package/node_modules/@llblab/pi-actors/dist/lib/extension-runtime.js +92 -14
- package/node_modules/@llblab/pi-actors/dist/lib/file-state.js +21 -2
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-command.js +2 -2
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.d.ts +25 -7
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-overlay.js +562 -262
- package/node_modules/@llblab/pi-actors/dist/lib/inspector.d.ts +20 -10
- package/node_modules/@llblab/pi-actors/dist/lib/inspector.js +80 -91
- package/node_modules/@llblab/pi-actors/dist/lib/limits.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/limits.js +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/observability.d.ts +15 -1
- package/node_modules/@llblab/pi-actors/dist/lib/observability.js +216 -99
- package/node_modules/@llblab/pi-actors/dist/lib/pi.d.ts +5 -4
- package/node_modules/@llblab/pi-actors/dist/lib/pi.js +64 -10
- package/node_modules/@llblab/pi-actors/dist/lib/recipe-projection.d.ts +18 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipe-projection.js +220 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.d.ts +2 -0
- package/node_modules/@llblab/pi-actors/dist/lib/recipes-context.js +30 -6
- package/node_modules/@llblab/pi-actors/dist/lib/registry.js +270 -36
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.d.ts +213 -5
- package/node_modules/@llblab/pi-actors/dist/lib/run-delivery.js +1909 -40
- package/node_modules/@llblab/pi-actors/dist/lib/run-supervision.d.ts +107 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-supervision.js +412 -0
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.d.ts +9 -2
- package/node_modules/@llblab/pi-actors/dist/lib/run-ui-runtime.js +283 -60
- package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.d.ts +26 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-artifacts.js +68 -2
- package/node_modules/@llblab/pi-actors/dist/lib/runs-context-exports.d.ts +451 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-context-exports.js +2315 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-context-index.d.ts +97 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-context-index.js +368 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control-delivery.js +46 -7
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control.d.ts +78 -2
- package/node_modules/@llblab/pi-actors/dist/lib/runs-control.js +334 -2
- package/node_modules/@llblab/pi-actors/dist/lib/runs-controls.js +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-descendant-launches.d.ts +80 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-descendant-launches.js +287 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-execution-evidence.d.ts +110 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-execution-evidence.js +282 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-index.d.ts +66 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-index.js +349 -13
- package/node_modules/@llblab/pi-actors/dist/lib/runs-native-launches.d.ts +73 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-native-launches.js +227 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-outcomes.d.ts +141 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-outcomes.js +292 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.d.ts +10 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-ownership.js +36 -4
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process-windows-mi.d.ts +46 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process-windows-mi.js +114 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process.d.ts +34 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-process.js +158 -26
- package/node_modules/@llblab/pi-actors/dist/lib/runs-results.d.ts +116 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-results.js +518 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.d.ts +15 -2
- package/node_modules/@llblab/pi-actors/dist/lib/runs-retention.js +148 -9
- package/node_modules/@llblab/pi-actors/dist/lib/runs-session-exports.d.ts +95 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-session-exports.js +470 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-snapshot.d.ts +58 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-snapshot.js +92 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-start.d.ts +2 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-start.js +44 -3
- package/node_modules/@llblab/pi-actors/dist/lib/runs-status.d.ts +15 -1
- package/node_modules/@llblab/pi-actors/dist/lib/runs-status.js +337 -13
- package/node_modules/@llblab/pi-actors/dist/lib/runs-supervision.d.ts +105 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-supervision.js +501 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/runs-trace.js +14 -3
- package/node_modules/@llblab/pi-actors/dist/lib/runtime.js +22 -3
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.d.ts +37 -0
- package/node_modules/@llblab/pi-actors/dist/lib/session-evidence.js +59 -3
- package/node_modules/@llblab/pi-actors/dist/lib/state-readers.d.ts +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/state-readers.js +14 -8
- package/node_modules/@llblab/pi-actors/dist/lib/temp.d.ts +2 -4
- package/node_modules/@llblab/pi-actors/dist/lib/temp.js +4 -56
- package/node_modules/@llblab/pi-actors/dist/lib/tools-access.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-access.js +13 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.d.ts +1 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-inspect.js +110 -43
- package/node_modules/@llblab/pi-actors/dist/lib/tools-local.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-local.js +18 -6
- package/node_modules/@llblab/pi-actors/dist/lib/tools-message.d.ts +4 -1
- package/node_modules/@llblab/pi-actors/dist/lib/tools-message.js +126 -31
- package/node_modules/@llblab/pi-actors/dist/lib/tools-response.d.ts +31 -3
- package/node_modules/@llblab/pi-actors/dist/lib/tools-response.js +311 -47
- package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.d.ts +3 -0
- package/node_modules/@llblab/pi-actors/dist/lib/tools-spawn.js +29 -7
- package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.d.ts +20 -0
- package/node_modules/@llblab/pi-actors/dist/lib/trace-projection.js +192 -43
- package/node_modules/@llblab/pi-actors/dist/scripts/async-runner.mjs +272 -194
- package/node_modules/@llblab/pi-actors/dist/scripts/conformance.mjs +14 -2
- package/node_modules/@llblab/pi-actors/dist/skills/actors/SKILL.md +46 -2
- package/node_modules/@llblab/pi-actors/dist/skills/actors/references/diagnostics.md +40 -0
- package/node_modules/@llblab/pi-actors/dist/skills/actors/references/runs.md +55 -1
- package/node_modules/@llblab/pi-actors/docs/README.md +9 -1
- package/node_modules/@llblab/pi-actors/docs/actor-inspector.md +84 -15
- package/node_modules/@llblab/pi-actors/docs/agent-experience.md +216 -0
- package/node_modules/@llblab/pi-actors/docs/async-runs.md +18 -2
- package/node_modules/@llblab/pi-actors/docs/composition-transport.md +103 -0
- package/node_modules/@llblab/pi-actors/docs/coordinator-delivery.md +204 -4
- package/node_modules/@llblab/pi-actors/docs/inspection.md +38 -2
- package/node_modules/@llblab/pi-actors/docs/output-projection-map.md +130 -0
- package/node_modules/@llblab/pi-actors/docs/releasing.md +19 -2
- package/node_modules/@llblab/pi-actors/docs/run-supervision.md +1622 -0
- package/node_modules/@llblab/pi-actors/docs/template-recipes.md +4 -0
- package/node_modules/@llblab/pi-actors/docs/windows-process-observation.md +248 -0
- package/node_modules/@llblab/pi-actors/index.ts +1 -1
- package/node_modules/@llblab/pi-actors/lib/async-runs.ts +164 -73
- package/node_modules/@llblab/pi-actors/lib/automatic-review-runtime.ts +4 -2
- package/node_modules/@llblab/pi-actors/lib/command-templates.ts +287 -138
- package/node_modules/@llblab/pi-actors/lib/execution-files.ts +118 -0
- package/node_modules/@llblab/pi-actors/lib/execution-output.ts +4 -4
- package/node_modules/@llblab/pi-actors/lib/execution-sessions.ts +57 -27
- package/node_modules/@llblab/pi-actors/lib/execution.ts +8 -0
- package/node_modules/@llblab/pi-actors/lib/extension-runtime.ts +102 -12
- package/node_modules/@llblab/pi-actors/lib/file-state.ts +16 -2
- package/node_modules/@llblab/pi-actors/lib/inspector-command.ts +2 -2
- package/node_modules/@llblab/pi-actors/lib/inspector-overlay.ts +524 -261
- package/node_modules/@llblab/pi-actors/lib/inspector.ts +101 -130
- package/node_modules/@llblab/pi-actors/lib/limits.ts +1 -0
- package/node_modules/@llblab/pi-actors/lib/observability.ts +235 -116
- package/node_modules/@llblab/pi-actors/lib/pi.ts +59 -11
- package/node_modules/@llblab/pi-actors/lib/recipe-projection.ts +232 -0
- package/node_modules/@llblab/pi-actors/lib/recipes-context.ts +27 -6
- package/node_modules/@llblab/pi-actors/lib/registry.ts +266 -36
- package/node_modules/@llblab/pi-actors/lib/run-delivery.ts +1737 -49
- package/node_modules/@llblab/pi-actors/lib/run-supervision.ts +411 -0
- package/node_modules/@llblab/pi-actors/lib/run-ui-runtime.ts +237 -73
- package/node_modules/@llblab/pi-actors/lib/runs-artifacts.ts +73 -1
- package/node_modules/@llblab/pi-actors/lib/runs-context-exports.ts +1871 -0
- package/node_modules/@llblab/pi-actors/lib/runs-context-index.ts +372 -0
- package/node_modules/@llblab/pi-actors/lib/runs-control-delivery.ts +36 -18
- package/node_modules/@llblab/pi-actors/lib/runs-control.ts +297 -1
- package/node_modules/@llblab/pi-actors/lib/runs-controls.ts +3 -1
- package/node_modules/@llblab/pi-actors/lib/runs-descendant-launches.ts +338 -0
- package/node_modules/@llblab/pi-actors/lib/runs-execution-evidence.ts +302 -0
- package/node_modules/@llblab/pi-actors/lib/runs-index.ts +345 -17
- package/node_modules/@llblab/pi-actors/lib/runs-native-launches.ts +270 -0
- package/node_modules/@llblab/pi-actors/lib/runs-outcomes.ts +377 -0
- package/node_modules/@llblab/pi-actors/lib/runs-ownership.ts +37 -4
- package/node_modules/@llblab/pi-actors/lib/runs-process-windows-mi.ts +113 -0
- package/node_modules/@llblab/pi-actors/lib/runs-process.ts +210 -34
- package/node_modules/@llblab/pi-actors/lib/runs-results.ts +601 -0
- package/node_modules/@llblab/pi-actors/lib/runs-retention.ts +134 -10
- package/node_modules/@llblab/pi-actors/lib/runs-session-exports.ts +406 -0
- package/node_modules/@llblab/pi-actors/lib/runs-snapshot.ts +143 -0
- package/node_modules/@llblab/pi-actors/lib/runs-start.ts +44 -2
- package/node_modules/@llblab/pi-actors/lib/runs-status.ts +252 -10
- package/node_modules/@llblab/pi-actors/lib/runs-supervision.ts +394 -0
- package/node_modules/@llblab/pi-actors/lib/runs-trace.ts +14 -3
- package/node_modules/@llblab/pi-actors/lib/runtime.ts +22 -3
- package/node_modules/@llblab/pi-actors/lib/session-evidence.ts +76 -4
- package/node_modules/@llblab/pi-actors/lib/state-readers.ts +16 -7
- package/node_modules/@llblab/pi-actors/lib/temp.ts +3 -56
- package/node_modules/@llblab/pi-actors/lib/tools-access.ts +13 -0
- package/node_modules/@llblab/pi-actors/lib/tools-inspect.ts +110 -51
- package/node_modules/@llblab/pi-actors/lib/tools-local.ts +37 -24
- package/node_modules/@llblab/pi-actors/lib/tools-message.ts +120 -37
- package/node_modules/@llblab/pi-actors/lib/tools-response.ts +273 -50
- package/node_modules/@llblab/pi-actors/lib/tools-spawn.ts +31 -10
- package/node_modules/@llblab/pi-actors/lib/trace-projection.ts +165 -42
- package/node_modules/@llblab/pi-actors/package.json +10 -6
- package/node_modules/@llblab/pi-actors/scripts/async-runner.mjs +272 -194
- package/node_modules/@llblab/pi-actors/scripts/conformance.mjs +14 -2
- package/node_modules/@llblab/pi-actors/skills/actors/SKILL.md +46 -2
- package/node_modules/@llblab/pi-actors/skills/actors/references/diagnostics.md +40 -0
- package/node_modules/@llblab/pi-actors/skills/actors/references/runs.md +55 -1
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +2 -2
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +6 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +9 -5
- package/node_modules/@llblab/pi-codex-usage/lib/query.ts +229 -50
- package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +9 -1
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/yaml/LICENSE +13 -0
- package/node_modules/yaml/README.md +172 -0
- package/node_modules/yaml/bin.mjs +11 -0
- package/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
- package/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
- package/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
- package/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
- package/node_modules/yaml/browser/dist/compose/composer.js +219 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
- package/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
- package/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
- package/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
- package/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
- package/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
- package/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
- package/node_modules/yaml/browser/dist/doc/Document.js +335 -0
- package/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
- package/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
- package/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
- package/node_modules/yaml/browser/dist/doc/directives.js +176 -0
- package/node_modules/yaml/browser/dist/errors.js +57 -0
- package/node_modules/yaml/browser/dist/index.js +17 -0
- package/node_modules/yaml/browser/dist/log.js +11 -0
- package/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
- package/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
- package/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
- package/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
- package/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
- package/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
- package/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
- package/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
- package/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
- package/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
- package/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
- package/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
- package/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
- package/node_modules/yaml/browser/dist/parse/cst.js +98 -0
- package/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
- package/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
- package/node_modules/yaml/browser/dist/parse/parser.js +975 -0
- package/node_modules/yaml/browser/dist/public-api.js +102 -0
- package/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
- package/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
- package/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
- package/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
- package/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
- package/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
- package/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
- package/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
- package/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
- package/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
- package/node_modules/yaml/browser/dist/schema/tags.js +96 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
- package/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
- package/node_modules/yaml/browser/dist/util.js +11 -0
- package/node_modules/yaml/browser/dist/visit.js +233 -0
- package/node_modules/yaml/browser/index.js +5 -0
- package/node_modules/yaml/browser/package.json +3 -0
- package/node_modules/yaml/dist/cli.d.ts +8 -0
- package/node_modules/yaml/dist/cli.mjs +201 -0
- package/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
- package/node_modules/yaml/dist/compose/compose-collection.js +90 -0
- package/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
- package/node_modules/yaml/dist/compose/compose-doc.js +45 -0
- package/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
- package/node_modules/yaml/dist/compose/compose-node.js +112 -0
- package/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
- package/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
- package/node_modules/yaml/dist/compose/composer.d.ts +63 -0
- package/node_modules/yaml/dist/compose/composer.js +224 -0
- package/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
- package/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
- package/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
- package/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-end.js +39 -0
- package/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
- package/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
- package/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
- package/node_modules/yaml/dist/compose/resolve-props.js +148 -0
- package/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
- package/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
- package/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
- package/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
- package/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
- package/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
- package/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
- package/node_modules/yaml/dist/doc/Document.d.ts +141 -0
- package/node_modules/yaml/dist/doc/Document.js +337 -0
- package/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
- package/node_modules/yaml/dist/doc/anchors.js +76 -0
- package/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
- package/node_modules/yaml/dist/doc/applyReviver.js +57 -0
- package/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
- package/node_modules/yaml/dist/doc/createNode.js +90 -0
- package/node_modules/yaml/dist/doc/directives.d.ts +49 -0
- package/node_modules/yaml/dist/doc/directives.js +178 -0
- package/node_modules/yaml/dist/errors.d.ts +21 -0
- package/node_modules/yaml/dist/errors.js +62 -0
- package/node_modules/yaml/dist/index.d.ts +25 -0
- package/node_modules/yaml/dist/index.js +50 -0
- package/node_modules/yaml/dist/log.d.ts +3 -0
- package/node_modules/yaml/dist/log.js +19 -0
- package/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
- package/node_modules/yaml/dist/nodes/Alias.js +118 -0
- package/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
- package/node_modules/yaml/dist/nodes/Collection.js +151 -0
- package/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
- package/node_modules/yaml/dist/nodes/Node.js +40 -0
- package/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
- package/node_modules/yaml/dist/nodes/Pair.js +39 -0
- package/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
- package/node_modules/yaml/dist/nodes/Scalar.js +27 -0
- package/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
- package/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
- package/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
- package/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
- package/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
- package/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
- package/node_modules/yaml/dist/nodes/identity.js +53 -0
- package/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
- package/node_modules/yaml/dist/nodes/toJS.js +39 -0
- package/node_modules/yaml/dist/options.d.ts +350 -0
- package/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
- package/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
- package/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
- package/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
- package/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
- package/node_modules/yaml/dist/parse/cst-visit.js +99 -0
- package/node_modules/yaml/dist/parse/cst.d.ts +109 -0
- package/node_modules/yaml/dist/parse/cst.js +112 -0
- package/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
- package/node_modules/yaml/dist/parse/lexer.js +723 -0
- package/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
- package/node_modules/yaml/dist/parse/line-counter.js +41 -0
- package/node_modules/yaml/dist/parse/parser.d.ts +84 -0
- package/node_modules/yaml/dist/parse/parser.js +980 -0
- package/node_modules/yaml/dist/public-api.d.ts +44 -0
- package/node_modules/yaml/dist/public-api.js +107 -0
- package/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
- package/node_modules/yaml/dist/schema/Schema.js +39 -0
- package/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/map.js +19 -0
- package/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
- package/node_modules/yaml/dist/schema/common/null.js +17 -0
- package/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/seq.js +19 -0
- package/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/string.js +16 -0
- package/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/bool.js +21 -0
- package/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/float.js +47 -0
- package/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/int.js +42 -0
- package/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
- package/node_modules/yaml/dist/schema/core/schema.js +25 -0
- package/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
- package/node_modules/yaml/dist/schema/json/schema.js +64 -0
- package/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
- package/node_modules/yaml/dist/schema/tags.d.ts +48 -0
- package/node_modules/yaml/dist/schema/tags.js +99 -0
- package/node_modules/yaml/dist/schema/types.d.ts +92 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
- package/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
- package/node_modules/yaml/dist/stringify/stringify.js +132 -0
- package/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
- package/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
- package/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
- package/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
- package/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
- package/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
- package/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
- package/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
- package/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
- package/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
- package/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
- package/node_modules/yaml/dist/test-events.d.ts +4 -0
- package/node_modules/yaml/dist/test-events.js +134 -0
- package/node_modules/yaml/dist/util.d.ts +16 -0
- package/node_modules/yaml/dist/util.js +28 -0
- package/node_modules/yaml/dist/visit.d.ts +102 -0
- package/node_modules/yaml/dist/visit.js +236 -0
- package/node_modules/yaml/package.json +97 -0
- package/node_modules/yaml/util.js +2 -0
- package/package.json +3 -3
- package/node_modules/@llblab/pi-actors/banner.jpg +0 -0
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.d.ts +0 -16
- package/node_modules/@llblab/pi-actors/dist/lib/inspector-actions.js +0 -57
- package/node_modules/@llblab/pi-actors/lib/inspector-actions.ts +0 -79
|
@@ -1,25 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Coordinator delivery persistence.
|
|
3
|
-
* Zones: owner-scoped completion batches, delivery phase fencing, bounded receipts
|
|
4
|
-
* Owns
|
|
3
|
+
* Zones: owner-scoped completion streams/batches, delivery phase fencing, bounded receipts
|
|
4
|
+
* Owns atomic stream allocations, immutable member snapshots and bounded projections; excludes logical-session admission, Run discovery, primary authorization and Pi scheduling.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { types as utilTypes } from "node:util";
|
|
9
|
+
import { parseDocument } from "yaml";
|
|
10
|
+
import { renderYamlMetadata } from "./tools-response.ts";
|
|
11
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { basename, dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
10
13
|
|
|
11
14
|
import { withFileMutationLock, writeTextAtomic } from "./file-state.ts";
|
|
12
15
|
import * as Limits from "./limits.ts";
|
|
16
|
+
import { decodeRunDirectoryNavigation, type RunDirectoryNavigation } from "./runs-index.ts";
|
|
17
|
+
import { formatInspectNextAction, formatLiteralBlock, formatMarkdownField, formatRunFileNavigationHandoff } from "./tools-response.ts";
|
|
18
|
+
import { decodeRunCompletionText, projectRunCompletionText, type RunCompletionText, decodeRunPrimaryTextBinding, decodeRunArtifactInventoryBinding, decodeRunResultDiagnosticsBinding, decodeRunResultDiagnosticExcerpt, type RunResultDiagnosticsReference, type RunResultDiagnosticPacketExcerpt, type RunPrimaryTextBinding, type RunArtifactInventoryReference } from "./runs-outcomes.ts";
|
|
13
19
|
|
|
14
20
|
export type RunCompletionDeliveryStatus = "done" | "failed" | "killed" | "exited";
|
|
15
21
|
export type RunDeliveryPhase = "pending" | "queued" | "presented";
|
|
16
22
|
|
|
23
|
+
export type RunCompletionIndexRange =
|
|
24
|
+
| Readonly<{ state: "available"; indices: readonly number[]; next_index: number }>
|
|
25
|
+
| Readonly<{ state: "unavailable"; reason: "range_unavailable"; next_index: number }>;
|
|
26
|
+
|
|
27
|
+
export type RunCompletionNumbering =
|
|
28
|
+
| Readonly<{ kind: "stream"; session_id: string; stream_id: string; first_index: number; member_order: readonly number[] }>
|
|
29
|
+
| Readonly<{ kind: "unavailable"; session_id: string; stream_id?: string;
|
|
30
|
+
reason: "counter_unavailable" | "range_unavailable" | "stream_state_unavailable"; member_order: readonly number[] }>;
|
|
31
|
+
|
|
17
32
|
export interface RunCompletionBatchMember {
|
|
18
33
|
artifacts?: Record<string, string>;
|
|
34
|
+
artifact_inventory?: Readonly<RunArtifactInventoryReference> | null;
|
|
35
|
+
artifact_inventory_unavailable?: string;
|
|
36
|
+
result_diagnostics?: Readonly<RunResultDiagnosticsReference> | null;
|
|
37
|
+
result_diagnostics_unavailable?: string;
|
|
38
|
+
diagnostic_excerpt?: RunResultDiagnosticPacketExcerpt;
|
|
19
39
|
parent_run?: string;
|
|
20
40
|
parent_run_instance_id?: string;
|
|
21
41
|
parent_state_dir?: string;
|
|
22
42
|
output?: string;
|
|
43
|
+
primary_text?: Readonly<RunPrimaryTextBinding>;
|
|
44
|
+
completion_text?: Readonly<RunCompletionText>;
|
|
23
45
|
run: string;
|
|
24
46
|
run_instance_id: string;
|
|
25
47
|
state_dir: string;
|
|
@@ -28,13 +50,40 @@ export interface RunCompletionBatchMember {
|
|
|
28
50
|
terminal_at: string;
|
|
29
51
|
}
|
|
30
52
|
|
|
53
|
+
export interface RunCompletionInventorySource {
|
|
54
|
+
readonly path: string;
|
|
55
|
+
readonly sha256: string;
|
|
56
|
+
readonly bytes_total: number;
|
|
57
|
+
readonly members_total: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RunCompletionFileNavigation {
|
|
61
|
+
readonly member_index: number;
|
|
62
|
+
readonly locations: Readonly<RunDirectoryNavigation>;
|
|
63
|
+
}
|
|
64
|
+
|
|
31
65
|
export interface RunCompletionBatch {
|
|
32
66
|
attempts?: number;
|
|
67
|
+
/** AX capture marker; numbered spellings/absence are retained codecs, never new-capture choices. */
|
|
68
|
+
readonly presentation_format?: "ax_records" | "ax_records_v1" | "ax_records_v2" | "ax_records_v3";
|
|
69
|
+
/** Retained singleton capture; absence preserves older indexed bytes. Never a new-capture selector. */
|
|
70
|
+
readonly single_result?: true;
|
|
71
|
+
/** Captured record-list shape; no public format/version choice or source authority. */
|
|
72
|
+
readonly result_layout?: "records" | "run_headers" | "headings" | "headings_flat_omissions" | "headings_outcomes" | "headings_shared_results" | "headings_shared_kinds" | "frontmatter_v1" | "frontmatter_v2" | "completion_yaml_core_v1";
|
|
73
|
+
/** Immutable scope/order/range data, not a caller-selected format or allocation authority. */
|
|
74
|
+
readonly numbering?: RunCompletionNumbering;
|
|
75
|
+
/** Formatting version/data only; no recipient ownership, read or publication capability. */
|
|
76
|
+
readonly navigation_handoff?: "current_session_request" | "captured_file_locations";
|
|
77
|
+
/** Formatting only; omission counts remain the already-admitted source facts. */
|
|
78
|
+
readonly excerpt_format?: "bracketed_omissions_v1";
|
|
79
|
+
readonly file_navigation?: readonly Readonly<RunCompletionFileNavigation>[];
|
|
80
|
+
readonly text_budget?: number;
|
|
81
|
+
readonly inventory_source?: Readonly<RunCompletionInventorySource>;
|
|
33
82
|
batch_id: string;
|
|
34
83
|
created_at: string;
|
|
35
84
|
last_error?: string;
|
|
36
85
|
last_failed_at?: string;
|
|
37
|
-
members: RunCompletionBatchMember[];
|
|
86
|
+
members: readonly RunCompletionBatchMember[];
|
|
38
87
|
phase: RunDeliveryPhase;
|
|
39
88
|
presented_at?: string;
|
|
40
89
|
queued_at?: string;
|
|
@@ -65,7 +114,32 @@ export interface RunDeliveryReceipt {
|
|
|
65
114
|
presented_at: string;
|
|
66
115
|
}
|
|
67
116
|
|
|
117
|
+
/** A requested fresh target only; requesting it does not activate a stream. */
|
|
118
|
+
export interface RunCompletionStreamIntent {
|
|
119
|
+
readonly pending_stream_id: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export type RunCompletionStreamCounter =
|
|
123
|
+
| Readonly<{ state: "available"; next_index: number }>
|
|
124
|
+
| Readonly<{ state: "unavailable" }>;
|
|
125
|
+
|
|
126
|
+
export interface RunCompletionStreamActive {
|
|
127
|
+
readonly stream_id: string;
|
|
128
|
+
readonly counter: RunCompletionStreamCounter;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export type RunCompletionStreamState =
|
|
132
|
+
| Readonly<RunCompletionStreamIntent & { active?: never }>
|
|
133
|
+
| Readonly<{ active: Readonly<RunCompletionStreamActive>; pending_stream_id?: string }>;
|
|
134
|
+
|
|
135
|
+
export type RunCompletionStreamStatus =
|
|
136
|
+
| Readonly<{ state: "unavailable"; reason: "stream_state_unavailable" }>
|
|
137
|
+
| Readonly<{ state: "pending"; session_id: string; pending_stream_id: string;
|
|
138
|
+
active?: Readonly<RunCompletionStreamActive>; blocking_batch_id?: string }>
|
|
139
|
+
| Readonly<{ state: "active"; session_id: string; active: Readonly<RunCompletionStreamActive>; blocking_batch_id?: string }>;
|
|
140
|
+
|
|
68
141
|
export interface RunDeliveryJournal {
|
|
142
|
+
completion_stream?: RunCompletionStreamState;
|
|
69
143
|
completion_batch?: RunCompletionBatch;
|
|
70
144
|
owner_id: string;
|
|
71
145
|
receipts: RunDeliveryReceipt[];
|
|
@@ -75,7 +149,8 @@ export interface RunDeliveryJournal {
|
|
|
75
149
|
|
|
76
150
|
export interface AdmitRunCompletionBatchInput {
|
|
77
151
|
batchId?: string;
|
|
78
|
-
|
|
152
|
+
fileNavigation?: readonly Readonly<RunCompletionFileNavigation>[];
|
|
153
|
+
members: readonly RunCompletionBatchMember[];
|
|
79
154
|
now?: Date;
|
|
80
155
|
ownerId: string;
|
|
81
156
|
tempDir: string;
|
|
@@ -111,12 +186,20 @@ export interface RunSteerTransitionInput {
|
|
|
111
186
|
}
|
|
112
187
|
|
|
113
188
|
const RUN_DELIVERY_SCHEMA = "run-delivery-v1";
|
|
189
|
+
const COMPLETION_STREAM_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
|
|
114
190
|
const MEMBER_FIELDS = new Set([
|
|
115
191
|
"artifacts",
|
|
192
|
+
"artifact_inventory",
|
|
193
|
+
"artifact_inventory_unavailable",
|
|
194
|
+
"result_diagnostics",
|
|
195
|
+
"result_diagnostics_unavailable",
|
|
196
|
+
"diagnostic_excerpt",
|
|
116
197
|
"parent_run",
|
|
117
198
|
"parent_run_instance_id",
|
|
118
199
|
"parent_state_dir",
|
|
119
200
|
"output",
|
|
201
|
+
"primary_text",
|
|
202
|
+
"completion_text",
|
|
120
203
|
"run",
|
|
121
204
|
"run_instance_id",
|
|
122
205
|
"state_dir",
|
|
@@ -126,6 +209,15 @@ const MEMBER_FIELDS = new Set([
|
|
|
126
209
|
]);
|
|
127
210
|
const BATCH_FIELDS = new Set([
|
|
128
211
|
"attempts",
|
|
212
|
+
"presentation_format",
|
|
213
|
+
"single_result",
|
|
214
|
+
"result_layout",
|
|
215
|
+
"numbering",
|
|
216
|
+
"excerpt_format",
|
|
217
|
+
"navigation_handoff",
|
|
218
|
+
"file_navigation",
|
|
219
|
+
"text_budget",
|
|
220
|
+
"inventory_source",
|
|
129
221
|
"batch_id",
|
|
130
222
|
"created_at",
|
|
131
223
|
"last_error",
|
|
@@ -136,6 +228,7 @@ const BATCH_FIELDS = new Set([
|
|
|
136
228
|
"queued_at",
|
|
137
229
|
]);
|
|
138
230
|
const JOURNAL_FIELDS = new Set([
|
|
231
|
+
"completion_stream",
|
|
139
232
|
"completion_batch",
|
|
140
233
|
"owner_id",
|
|
141
234
|
"receipts",
|
|
@@ -166,6 +259,59 @@ const STEER_FIELDS = new Set([
|
|
|
166
259
|
"steer_id",
|
|
167
260
|
]);
|
|
168
261
|
|
|
262
|
+
/** Pure whole-capture arithmetic, not a reservation; commit with the packet under its owner lock. */
|
|
263
|
+
export function projectRunCompletionIndexRange(nextIndex: number, memberCount: number): RunCompletionIndexRange {
|
|
264
|
+
if (!Number.isSafeInteger(nextIndex) || nextIndex < 0) throw new Error("Completion stream counter is invalid");
|
|
265
|
+
if (!Number.isSafeInteger(memberCount) || memberCount < 1 || memberCount > Limits.RUN_DELIVERY_BATCH_MAX_MEMBERS) {
|
|
266
|
+
throw new Error("Completion stream member count is invalid");
|
|
267
|
+
}
|
|
268
|
+
// Subtract before adding so an unfitting range never produces rounded indices or a partial allocation.
|
|
269
|
+
if (memberCount > Number.MAX_SAFE_INTEGER - nextIndex) {
|
|
270
|
+
return Object.freeze({ state: "unavailable", reason: "range_unavailable", next_index: nextIndex });
|
|
271
|
+
}
|
|
272
|
+
return Object.freeze({ state: "available", next_index: nextIndex + memberCount,
|
|
273
|
+
indices: Object.freeze(Array.from({ length: memberCount }, (_, offset) => nextIndex + offset)) });
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function parseCompletionNumbering(value: unknown, memberCount: number): RunCompletionNumbering {
|
|
277
|
+
const record = asRecord(value, "Completion numbering"), fields = Object.getOwnPropertyDescriptors(record);
|
|
278
|
+
const allowed = fields.kind?.value === "stream" ? ["kind", "session_id", "stream_id", "first_index", "member_order"]
|
|
279
|
+
: ["kind", "session_id", "stream_id", "reason", "member_order"];
|
|
280
|
+
if (Reflect.ownKeys(fields).some(key => typeof key !== "string" || !allowed.includes(key)) ||
|
|
281
|
+
Object.values(fields).some(field => !Object.hasOwn(field, "value") || !field.enumerable)) {
|
|
282
|
+
throw new Error("Completion numbering must contain only own data");
|
|
283
|
+
}
|
|
284
|
+
const kind = fields.kind?.value, session = fields.session_id?.value, stream = fields.stream_id?.value, order = fields.member_order?.value;
|
|
285
|
+
if ((kind !== "stream" && kind !== "unavailable") || typeof session !== "string" || !session.trim() || session.length > 4096 ||
|
|
286
|
+
!Array.isArray(order) || order.length !== memberCount || Reflect.ownKeys(order).length !== memberCount + 1) {
|
|
287
|
+
throw new Error("Completion numbering is invalid");
|
|
288
|
+
}
|
|
289
|
+
const memberOrder: number[] = [], selected = new Set<number>();
|
|
290
|
+
for (let slot = 0; slot < memberCount; slot++) {
|
|
291
|
+
const descriptor = Object.getOwnPropertyDescriptor(order, String(slot));
|
|
292
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value") || !descriptor.enumerable || !Number.isSafeInteger(descriptor.value) ||
|
|
293
|
+
descriptor.value < 0 || descriptor.value >= memberCount || selected.has(descriptor.value)) {
|
|
294
|
+
throw new Error("Completion numbering member order is invalid");
|
|
295
|
+
}
|
|
296
|
+
selected.add(descriptor.value); memberOrder.push(descriptor.value);
|
|
297
|
+
}
|
|
298
|
+
if (kind === "stream") {
|
|
299
|
+
const first = fields.first_index?.value;
|
|
300
|
+
if (typeof stream !== "string" || !COMPLETION_STREAM_ID.test(stream) || !Number.isSafeInteger(first) || first < 0 ||
|
|
301
|
+
projectRunCompletionIndexRange(first, memberCount).state !== "available") {
|
|
302
|
+
throw new Error("Completion numbering range is invalid");
|
|
303
|
+
}
|
|
304
|
+
return Object.freeze({ kind, session_id: session, stream_id: stream, first_index: first + 0, member_order: Object.freeze(memberOrder) });
|
|
305
|
+
}
|
|
306
|
+
const reason = fields.reason?.value;
|
|
307
|
+
if ((reason !== "counter_unavailable" && reason !== "range_unavailable" && reason !== "stream_state_unavailable") ||
|
|
308
|
+
(reason === "stream_state_unavailable" ? fields.stream_id !== undefined : typeof stream !== "string" || !COMPLETION_STREAM_ID.test(stream))) {
|
|
309
|
+
throw new Error("Completion numbering unavailability is invalid");
|
|
310
|
+
}
|
|
311
|
+
return Object.freeze({ kind, session_id: session, ...(stream === undefined ? {} : { stream_id: stream }),
|
|
312
|
+
reason, member_order: Object.freeze(memberOrder) });
|
|
313
|
+
}
|
|
314
|
+
|
|
169
315
|
function assertExactFields(
|
|
170
316
|
value: Record<string, unknown>,
|
|
171
317
|
fields: Set<string>,
|
|
@@ -248,8 +394,25 @@ function parseMember(value: unknown): RunCompletionBatchMember {
|
|
|
248
394
|
const output = record.output === undefined
|
|
249
395
|
? undefined
|
|
250
396
|
: boundedString(record.output, "Completion batch member output", 4_000);
|
|
251
|
-
|
|
252
|
-
|
|
397
|
+
const stateDir = boundedString(record.state_dir, "Completion batch member state_dir", 4_096);
|
|
398
|
+
const generation = boundedString(record.run_instance_id, "Completion batch member run_instance_id", 256);
|
|
399
|
+
const primary = record.primary_text === undefined ? undefined : decodeRunPrimaryTextBinding(stateDir, generation, record.primary_text);
|
|
400
|
+
const inventory = Object.hasOwn(record, "artifact_inventory") || Object.hasOwn(record, "artifact_inventory_unavailable")
|
|
401
|
+
? decodeRunArtifactInventoryBinding(stateDir, generation, record) : undefined;
|
|
402
|
+
const diagnostics = Object.hasOwn(record, "result_diagnostics") || Object.hasOwn(record, "result_diagnostics_unavailable")
|
|
403
|
+
? decodeRunResultDiagnosticsBinding(stateDir, generation, record) : undefined;
|
|
404
|
+
if (diagnostics && output) throw new Error("Completion result diagnostics cannot also carry a legacy display.");
|
|
405
|
+
if (record.diagnostic_excerpt !== undefined && !diagnostics?.result_diagnostics) throw new Error("Completion diagnostic excerpt requires its available source binding.");
|
|
406
|
+
const excerpt = record.diagnostic_excerpt === undefined ? undefined : decodeRunResultDiagnosticExcerpt(diagnostics!.result_diagnostics!, record.diagnostic_excerpt);
|
|
407
|
+
if (inventory && Object.keys(parsedArtifacts ?? {}).length) throw new Error("Completion artifact inventory cannot also carry legacy artifact paths.");
|
|
408
|
+
if (primary?.output && output) throw new Error("Completion primary text cannot also carry a legacy display.");
|
|
409
|
+
return Object.freeze({
|
|
410
|
+
...(parsedArtifacts ? { artifacts: Object.freeze(parsedArtifacts) } : {}),
|
|
411
|
+
...(primary ? { primary_text: primary } : {}),
|
|
412
|
+
...(record.completion_text === undefined ? {} : { completion_text: decodeRunCompletionText(primary, record.completion_text) }),
|
|
413
|
+
...inventory,
|
|
414
|
+
...diagnostics,
|
|
415
|
+
...(excerpt ? { diagnostic_excerpt: excerpt } : {}),
|
|
253
416
|
...(output ? { output } : {}),
|
|
254
417
|
...(parentRun
|
|
255
418
|
? {
|
|
@@ -259,21 +422,69 @@ function parseMember(value: unknown): RunCompletionBatchMember {
|
|
|
259
422
|
}
|
|
260
423
|
: {}),
|
|
261
424
|
run: boundedString(record.run, "Completion batch member run", 120),
|
|
262
|
-
run_instance_id:
|
|
263
|
-
|
|
264
|
-
"Completion batch member run_instance_id",
|
|
265
|
-
256,
|
|
266
|
-
),
|
|
267
|
-
state_dir: boundedString(record.state_dir, "Completion batch member state_dir", 4_096),
|
|
425
|
+
run_instance_id: generation,
|
|
426
|
+
state_dir: stateDir,
|
|
268
427
|
status: completionStatus(record.status),
|
|
269
428
|
summary: boundedString(record.summary, "Completion batch member summary", 1_000),
|
|
270
429
|
terminal_at: timestamp(record.terminal_at, "Completion batch member terminal_at"),
|
|
271
|
-
};
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Only already admitted source bindings cross this boundary; private locations and diagnostics do not.
|
|
434
|
+
function completionInventoryText(batch: Pick<RunCompletionBatch, "batch_id" | "created_at" | "members" | "numbering">): string {
|
|
435
|
+
return `${JSON.stringify({
|
|
436
|
+
schema: "run-completion-inventory-v1", batch_id: batch.batch_id, created_at: batch.created_at,
|
|
437
|
+
scope: "captured_completion_members", members_total: batch.members.length,
|
|
438
|
+
artifacts: "not_included", diagnostics: "not_included", private_locations: "not_included",
|
|
439
|
+
...(batch.members.some(member => member.artifact_inventory !== undefined) ? { artifact_inventory: "captured_bindings_only" } : {}),
|
|
440
|
+
...(batch.members.some(member => member.result_diagnostics !== undefined) ? { result_diagnostics: "captured_bindings_only" } : {}),
|
|
441
|
+
...(batch.numbering ? { numbering: batch.numbering } : {}),
|
|
442
|
+
members: batch.members.map(member => ({
|
|
443
|
+
run: member.run, run_instance_id: member.run_instance_id, status: member.status, terminal_at: member.terminal_at,
|
|
444
|
+
...(member.parent_run ? { parent_run: member.parent_run, parent_run_instance_id: member.parent_run_instance_id } : {}),
|
|
445
|
+
...(member.primary_text ? { primary_text: member.primary_text } : {}),
|
|
446
|
+
...(member.artifact_inventory === undefined ? {} : { artifact_inventory: member.artifact_inventory,
|
|
447
|
+
...(member.artifact_inventory_unavailable === undefined ? {} : { artifact_inventory_unavailable: member.artifact_inventory_unavailable }) }),
|
|
448
|
+
...(member.result_diagnostics === undefined ? {} : { result_diagnostics: member.result_diagnostics,
|
|
449
|
+
...(member.result_diagnostics_unavailable === undefined ? {} : { result_diagnostics_unavailable: member.result_diagnostics_unavailable }) }),
|
|
450
|
+
})),
|
|
451
|
+
}, null, 2)}\n`;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function parseInventorySource(value: unknown, batch: RunCompletionBatch): Readonly<RunCompletionInventorySource> {
|
|
455
|
+
const record = asRecord(value, "Completion inventory source");
|
|
456
|
+
assertExactFields(record, new Set(["path", "sha256", "bytes_total", "members_total"]), "Completion inventory source");
|
|
457
|
+
const content = completionInventoryText(batch); const sha256 = createHash("sha256").update(content).digest("hex");
|
|
458
|
+
const path = boundedString(record.path, "Completion inventory source path", 4_096);
|
|
459
|
+
if (!isAbsolute(path) || normalize(path) !== path || basename(path) !== `inventory-${sha256}.json` ||
|
|
460
|
+
record.sha256 !== sha256 || record.bytes_total !== Buffer.byteLength(content) || record.members_total !== batch.members.length) {
|
|
461
|
+
throw new Error("Completion inventory source binding is invalid");
|
|
462
|
+
}
|
|
463
|
+
return Object.freeze({ path, sha256, bytes_total: record.bytes_total as number, members_total: batch.members.length });
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function isFrontmatterCompletionLayout(layout: unknown): boolean {
|
|
467
|
+
return layout === "frontmatter_v1" || layout === "frontmatter_v2";
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function isSharedCompletionLayout(layout: unknown): boolean {
|
|
471
|
+
return layout === "headings_shared_results" || layout === "headings_shared_kinds" || layout === "completion_yaml_core_v1" || isFrontmatterCompletionLayout(layout);
|
|
272
472
|
}
|
|
273
473
|
|
|
274
474
|
function parseBatch(value: unknown): RunCompletionBatch {
|
|
275
475
|
const record = asRecord(value, "Completion batch");
|
|
276
476
|
assertExactFields(record, BATCH_FIELDS, "Completion batch");
|
|
477
|
+
const presentationFormat = record.presentation_format;
|
|
478
|
+
if ((presentationFormat !== undefined && presentationFormat !== "ax_records" && presentationFormat !== "ax_records_v1" && presentationFormat !== "ax_records_v2" && presentationFormat !== "ax_records_v3") ||
|
|
479
|
+
(presentationFormat !== undefined && record.excerpt_format !== undefined)) {
|
|
480
|
+
throw new Error("Completion batch presentation format is invalid");
|
|
481
|
+
}
|
|
482
|
+
if (record.excerpt_format !== undefined && record.excerpt_format !== "bracketed_omissions_v1") {
|
|
483
|
+
throw new Error("Completion batch excerpt format is invalid");
|
|
484
|
+
}
|
|
485
|
+
if (record.navigation_handoff !== undefined && record.navigation_handoff !== "current_session_request" && record.navigation_handoff !== "captured_file_locations") {
|
|
486
|
+
throw new Error("Completion batch navigation handoff is invalid");
|
|
487
|
+
}
|
|
277
488
|
if (!Array.isArray(record.members) || record.members.length === 0) {
|
|
278
489
|
throw new Error("Completion batch members must be a non-empty array");
|
|
279
490
|
}
|
|
@@ -281,6 +492,30 @@ function parseBatch(value: unknown): RunCompletionBatch {
|
|
|
281
492
|
throw new Error("Completion batch exceeds the member limit");
|
|
282
493
|
}
|
|
283
494
|
const members = record.members.map(parseMember);
|
|
495
|
+
const singleDescriptor = Object.getOwnPropertyDescriptor(record, "single_result");
|
|
496
|
+
if (singleDescriptor && (!Object.hasOwn(singleDescriptor, "value") || (singleDescriptor.value !== undefined &&
|
|
497
|
+
(singleDescriptor.value !== true || presentationFormat !== "ax_records" || members.length !== 1)))) {
|
|
498
|
+
throw new Error("Completion singleton layout is invalid");
|
|
499
|
+
}
|
|
500
|
+
const singleResult = singleDescriptor?.value;
|
|
501
|
+
const layoutDescriptor = Object.getOwnPropertyDescriptor(record, "result_layout");
|
|
502
|
+
if (layoutDescriptor && (!Object.hasOwn(layoutDescriptor, "value") || (layoutDescriptor.value !== undefined &&
|
|
503
|
+
((layoutDescriptor.value !== "records" && layoutDescriptor.value !== "run_headers" && layoutDescriptor.value !== "headings" && layoutDescriptor.value !== "headings_flat_omissions" && layoutDescriptor.value !== "headings_outcomes" && !isSharedCompletionLayout(layoutDescriptor.value)) || presentationFormat !== "ax_records" || singleResult !== undefined)))) {
|
|
504
|
+
throw new Error("Completion result layout is invalid");
|
|
505
|
+
}
|
|
506
|
+
const resultLayout = layoutDescriptor?.value;
|
|
507
|
+
const budgetDescriptor = Object.getOwnPropertyDescriptor(record, "text_budget");
|
|
508
|
+
if (budgetDescriptor && (!Object.hasOwn(budgetDescriptor, "value") || !budgetDescriptor.enumerable ||
|
|
509
|
+
(budgetDescriptor.value !== undefined && (!isSharedCompletionLayout(resultLayout) ||
|
|
510
|
+
!Number.isSafeInteger(budgetDescriptor.value) || budgetDescriptor.value < 0 ||
|
|
511
|
+
budgetDescriptor.value > Math.floor(Limits.RUN_COMPLETION_MODEL_MAX_BYTES / Math.min(members.length, Limits.RUN_DELIVERY_MODEL_MAX_MEMBERS)))))) {
|
|
512
|
+
throw new Error("Completion shared text budget is invalid");
|
|
513
|
+
}
|
|
514
|
+
const numberingDescriptor = Object.getOwnPropertyDescriptor(record, "numbering");
|
|
515
|
+
if (numberingDescriptor && (resultLayout === "completion_yaml_core_v1" || !Object.hasOwn(numberingDescriptor, "value") || presentationFormat !== "ax_records" || (resultLayout !== "headings" && resultLayout !== "headings_flat_omissions" && resultLayout !== "headings_outcomes" && !isSharedCompletionLayout(resultLayout)))) {
|
|
516
|
+
throw new Error("Completion numbering requires a canonical captured heading layout");
|
|
517
|
+
}
|
|
518
|
+
const numbering = numberingDescriptor ? parseCompletionNumbering(numberingDescriptor.value, members.length) : undefined;
|
|
284
519
|
const identities = new Set<string>();
|
|
285
520
|
for (const member of members) {
|
|
286
521
|
const identity = `${member.state_dir}\0${member.run_instance_id}`;
|
|
@@ -323,17 +558,48 @@ function parseBatch(value: unknown): RunCompletionBatch {
|
|
|
323
558
|
if (phase === "presented" && (!queuedAt || !presentedAt)) {
|
|
324
559
|
throw new Error("Presented completion batch requires queued_at and presented_at");
|
|
325
560
|
}
|
|
326
|
-
|
|
561
|
+
const navigation = record.file_navigation;
|
|
562
|
+
if ((record.navigation_handoff === "captured_file_locations") !== (navigation !== undefined)) throw new Error("Completion file navigation version/data mismatch");
|
|
563
|
+
let fileNavigation: readonly Readonly<RunCompletionFileNavigation>[] | undefined;
|
|
564
|
+
if (navigation !== undefined) {
|
|
565
|
+
if (!Array.isArray(navigation) || navigation.length > members.length || Object.keys(navigation).length !== navigation.length) throw new Error("Completion file navigation is invalid");
|
|
566
|
+
const selected = new Set<number>();
|
|
567
|
+
fileNavigation = Object.freeze(Array.from({ length: navigation.length }, (_, slot) => {
|
|
568
|
+
const entry = Object.getOwnPropertyDescriptor(navigation, String(slot));
|
|
569
|
+
if (!entry || !Object.hasOwn(entry, "value")) throw new Error("Completion file navigation is invalid");
|
|
570
|
+
const row = asRecord(entry.value, "Completion file navigation");
|
|
571
|
+
assertExactFields(row, new Set(["member_index", "locations"]), "Completion file navigation");
|
|
572
|
+
const descriptors = Object.getOwnPropertyDescriptors(row);
|
|
573
|
+
if (!descriptors.member_index || !descriptors.locations || !Object.hasOwn(descriptors.member_index, "value") || !Object.hasOwn(descriptors.locations, "value")) throw new Error("Completion file navigation is invalid");
|
|
574
|
+
const index = descriptors.member_index.value;
|
|
575
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index >= members.length || selected.has(index)) throw new Error("Completion file navigation member is invalid");
|
|
576
|
+
selected.add(index);
|
|
577
|
+
const locations = decodeRunDirectoryNavigation(descriptors.locations.value), member = members[index];
|
|
578
|
+
if (locations.state === "available" && (locations.run !== member.run || locations.run_instance_id !== member.run_instance_id || locations.directory !== member.state_dir)) throw new Error("Completion file navigation identity mismatch");
|
|
579
|
+
return Object.freeze({ member_index: index, locations });
|
|
580
|
+
}));
|
|
581
|
+
if (Buffer.byteLength(JSON.stringify(fileNavigation), "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) throw new Error("Completion file navigation exceeds byte limit");
|
|
582
|
+
}
|
|
583
|
+
const batch: RunCompletionBatch = {
|
|
584
|
+
...(presentationFormat === undefined ? {} : { presentation_format: presentationFormat }),
|
|
585
|
+
...(singleResult === true ? { single_result: true as const } : {}),
|
|
586
|
+
...(resultLayout === undefined ? {} : { result_layout: resultLayout }),
|
|
587
|
+
...(budgetDescriptor?.value === undefined ? {} : { text_budget: budgetDescriptor.value as number }),
|
|
588
|
+
...(numbering ? { numbering } : {}),
|
|
327
589
|
...(attempts !== undefined ? { attempts } : {}),
|
|
590
|
+
...(record.navigation_handoff === undefined ? {} : { navigation_handoff: record.navigation_handoff }),
|
|
591
|
+
...(record.excerpt_format === undefined ? {} : { excerpt_format: record.excerpt_format }),
|
|
592
|
+
...(fileNavigation ? { file_navigation: fileNavigation } : {}),
|
|
328
593
|
batch_id: boundedString(record.batch_id, "Completion batch id", 128),
|
|
329
594
|
created_at: timestamp(record.created_at, "Completion batch created_at"),
|
|
330
595
|
...(lastError ? { last_error: lastError } : {}),
|
|
331
596
|
...(lastFailedAt ? { last_failed_at: lastFailedAt } : {}),
|
|
332
|
-
members,
|
|
597
|
+
members: Object.freeze(members),
|
|
333
598
|
phase,
|
|
334
599
|
...(queuedAt ? { queued_at: queuedAt } : {}),
|
|
335
600
|
...(presentedAt ? { presented_at: presentedAt } : {}),
|
|
336
601
|
};
|
|
602
|
+
return record.inventory_source === undefined ? batch : { ...batch, inventory_source: parseInventorySource(record.inventory_source, batch) };
|
|
337
603
|
}
|
|
338
604
|
|
|
339
605
|
function parseSteer(value: unknown): RunSteerEnvelope {
|
|
@@ -424,6 +690,58 @@ function parseReceipt(value: unknown): RunDeliveryReceipt {
|
|
|
424
690
|
};
|
|
425
691
|
}
|
|
426
692
|
|
|
693
|
+
function completionStreamFields(value: unknown, allowed: readonly string[]): PropertyDescriptorMap {
|
|
694
|
+
const fields = Object.getOwnPropertyDescriptors(asRecord(value, "Completion stream intent"));
|
|
695
|
+
if (Reflect.ownKeys(fields).some(key => typeof key !== "string" || !allowed.includes(key)) ||
|
|
696
|
+
Object.values(fields).some(field => !Object.hasOwn(field, "value") || !field.enumerable)) {
|
|
697
|
+
throw new Error("Completion stream intent is invalid");
|
|
698
|
+
}
|
|
699
|
+
return fields;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function parseCompletionStreamState(value: unknown): RunCompletionStreamState {
|
|
703
|
+
const fields = completionStreamFields(value, ["active", "pending_stream_id"]);
|
|
704
|
+
const pending = fields.pending_stream_id?.value;
|
|
705
|
+
if ((!fields.active && !fields.pending_stream_id) || (fields.pending_stream_id &&
|
|
706
|
+
(typeof pending !== "string" || !COMPLETION_STREAM_ID.test(pending)))) {
|
|
707
|
+
throw new Error("Completion stream intent is invalid");
|
|
708
|
+
}
|
|
709
|
+
if (!fields.active) return Object.freeze({ pending_stream_id: pending });
|
|
710
|
+
const active = completionStreamFields(fields.active.value, ["stream_id", "counter"]);
|
|
711
|
+
const id = active.stream_id?.value;
|
|
712
|
+
if (typeof id !== "string" || !COMPLETION_STREAM_ID.test(id) || id === pending) {
|
|
713
|
+
throw new Error("Completion stream active identity is invalid");
|
|
714
|
+
}
|
|
715
|
+
const counter = completionStreamFields(active.counter?.value, ["state", "next_index"]);
|
|
716
|
+
let retainedCounter: RunCompletionStreamCounter;
|
|
717
|
+
if (counter.state?.value === "available") {
|
|
718
|
+
const next = counter.next_index?.value;
|
|
719
|
+
// A newly activated stream already captured at least one member in its first commit.
|
|
720
|
+
if (!Number.isSafeInteger(next) || next < 1) throw new Error("Completion stream active counter is invalid");
|
|
721
|
+
retainedCounter = Object.freeze({ state: "available", next_index: next });
|
|
722
|
+
} else if (counter.state?.value === "unavailable" && !counter.next_index) {
|
|
723
|
+
retainedCounter = Object.freeze({ state: "unavailable" });
|
|
724
|
+
} else throw new Error("Completion stream active counter is invalid");
|
|
725
|
+
return Object.freeze({ active: Object.freeze({ stream_id: id, counter: retainedCounter }),
|
|
726
|
+
...(fields.pending_stream_id ? { pending_stream_id: pending } : {}) });
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function assertCompletionStreamBatch(stream: RunCompletionStreamState | undefined, batch: RunCompletionBatch | undefined, ownerId: string): void {
|
|
730
|
+
if (!batch) return;
|
|
731
|
+
const numbering = batch.numbering, active = stream?.active;
|
|
732
|
+
if (!numbering) {
|
|
733
|
+
if (active) throw new Error("Active completion stream cannot contain a packet-local batch");
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
if (!active || numbering.session_id !== ownerId || numbering.stream_id !== active.stream_id) {
|
|
737
|
+
throw new Error("Completion numbering has no matching active session stream");
|
|
738
|
+
}
|
|
739
|
+
if (numbering.kind === "stream" && active.counter.state === "available" &&
|
|
740
|
+
active.counter.next_index < numbering.first_index + batch.members.length) {
|
|
741
|
+
throw new Error("Completion stream counter is behind its captured packet");
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
427
745
|
function emptyJournal(ownerId: string): RunDeliveryJournal {
|
|
428
746
|
return {
|
|
429
747
|
owner_id: ownerId,
|
|
@@ -433,7 +751,7 @@ function emptyJournal(ownerId: string): RunDeliveryJournal {
|
|
|
433
751
|
};
|
|
434
752
|
}
|
|
435
753
|
|
|
436
|
-
function parseJournal(value: unknown, ownerId: string): RunDeliveryJournal {
|
|
754
|
+
function parseJournal(value: unknown, ownerId: string, path: string): RunDeliveryJournal {
|
|
437
755
|
const record = asRecord(value, "Run delivery journal");
|
|
438
756
|
assertExactFields(record, JOURNAL_FIELDS, "Run delivery journal");
|
|
439
757
|
if (record.schema !== RUN_DELIVERY_SCHEMA) {
|
|
@@ -459,10 +777,20 @@ function parseJournal(value: unknown, ownerId: string): RunDeliveryJournal {
|
|
|
459
777
|
if (new Set(steers.map((steer) => steer.steer_id)).size !== steers.length) {
|
|
460
778
|
throw new Error("Run delivery journal has duplicate urgent steer ids");
|
|
461
779
|
}
|
|
780
|
+
const streamDescriptor = Object.getOwnPropertyDescriptor(record, "completion_stream");
|
|
781
|
+
if (streamDescriptor && (!Object.hasOwn(streamDescriptor, "value") || !streamDescriptor.enumerable)) {
|
|
782
|
+
throw new Error("Completion stream intent must be stored as own data");
|
|
783
|
+
}
|
|
784
|
+
const stream = streamDescriptor ? parseCompletionStreamState(streamDescriptor.value) : undefined;
|
|
785
|
+
const batch = record.completion_batch === undefined ? undefined : parseBatch(record.completion_batch);
|
|
786
|
+
if (batch && isSharedCompletionLayout(batch.result_layout) && batch.text_budget === undefined) throw new Error("Captured completion shared text budget is unavailable");
|
|
787
|
+
assertCompletionStreamBatch(stream, batch, ownerId);
|
|
788
|
+
if (batch?.inventory_source && batch.inventory_source.path !== resolve(dirname(path), `inventory-${batch.inventory_source.sha256}.json`)) {
|
|
789
|
+
throw new Error("Completion inventory source belongs to another delivery location");
|
|
790
|
+
}
|
|
462
791
|
return {
|
|
463
|
-
...(
|
|
464
|
-
|
|
465
|
-
: {}),
|
|
792
|
+
...(stream ? { completion_stream: stream } : {}),
|
|
793
|
+
...(batch ? { completion_batch: batch } : {}),
|
|
466
794
|
owner_id: ownerId,
|
|
467
795
|
receipts,
|
|
468
796
|
schema: RUN_DELIVERY_SCHEMA,
|
|
@@ -487,8 +815,11 @@ function assertNoDeliverySymlinks(tempDir: string, path: string): void {
|
|
|
487
815
|
}
|
|
488
816
|
}
|
|
489
817
|
|
|
490
|
-
function readJournalAtPath(path: string, ownerId: string): RunDeliveryJournal {
|
|
491
|
-
if (!existsSync(path))
|
|
818
|
+
function readJournalAtPath(path: string, ownerId: string, requireExisting = false): RunDeliveryJournal {
|
|
819
|
+
if (!existsSync(path)) {
|
|
820
|
+
if (requireExisting) throw new Error("Completion stream request requires an existing delivery journal");
|
|
821
|
+
return emptyJournal(ownerId);
|
|
822
|
+
}
|
|
492
823
|
const stat = statSync(path);
|
|
493
824
|
if (!stat.isFile() || stat.size > Limits.RUN_DELIVERY_JOURNAL_MAX_BYTES) {
|
|
494
825
|
throw new Error("Run delivery journal is not a bounded regular file");
|
|
@@ -499,7 +830,7 @@ function readJournalAtPath(path: string, ownerId: string): RunDeliveryJournal {
|
|
|
499
830
|
} catch (error) {
|
|
500
831
|
throw new Error("Run delivery journal is malformed", { cause: error });
|
|
501
832
|
}
|
|
502
|
-
return parseJournal(parsed, ownerId);
|
|
833
|
+
return parseJournal(parsed, ownerId, path);
|
|
503
834
|
}
|
|
504
835
|
|
|
505
836
|
export function readRunDeliveryJournal(
|
|
@@ -511,49 +842,137 @@ export function readRunDeliveryJournal(
|
|
|
511
842
|
return readJournalAtPath(path, ownerId);
|
|
512
843
|
}
|
|
513
844
|
|
|
514
|
-
function
|
|
845
|
+
function journalText(journal: RunDeliveryJournal): string {
|
|
515
846
|
const content = `${JSON.stringify(journal, null, 2)}\n`;
|
|
516
847
|
if (Buffer.byteLength(content, "utf8") > Limits.RUN_DELIVERY_JOURNAL_MAX_BYTES) {
|
|
517
848
|
throw new Error("Run delivery journal exceeds the byte limit");
|
|
518
849
|
}
|
|
519
|
-
|
|
850
|
+
return content;
|
|
520
851
|
}
|
|
521
852
|
|
|
522
853
|
function mutateJournal<T>(
|
|
523
854
|
tempDir: string,
|
|
524
855
|
ownerId: string,
|
|
525
856
|
mutate: (journal: RunDeliveryJournal) => T,
|
|
857
|
+
requireExisting = false,
|
|
526
858
|
): T {
|
|
527
859
|
const path = getRunDeliveryJournalPath(tempDir, ownerId);
|
|
528
860
|
return withFileMutationLock(path, () => {
|
|
529
861
|
assertNoDeliverySymlinks(tempDir, path);
|
|
530
|
-
const journal = readJournalAtPath(path, ownerId);
|
|
862
|
+
const journal = readJournalAtPath(path, ownerId, requireExisting);
|
|
531
863
|
const result = mutate(journal);
|
|
532
|
-
|
|
864
|
+
writeTextAtomic(path, journalText(parseJournal(journal, ownerId, path)));
|
|
533
865
|
return result;
|
|
534
866
|
});
|
|
535
867
|
}
|
|
536
868
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
)
|
|
869
|
+
/** Read-only persisted facts, not proof of non-connection or current SDK ownership. */
|
|
870
|
+
export function readRunCompletionStreamStatus(tempDir: string, ownerId: string): RunCompletionStreamStatus {
|
|
871
|
+
const journal = readRunDeliveryJournal(tempDir, ownerId), stream = journal.completion_stream;
|
|
872
|
+
if (!stream) return Object.freeze({ state: "unavailable", reason: "stream_state_unavailable" });
|
|
873
|
+
const blocking = journal.completion_batch ? { blocking_batch_id: journal.completion_batch.batch_id } : {};
|
|
874
|
+
if (stream.pending_stream_id) return Object.freeze({ state: "pending", session_id: journal.owner_id,
|
|
875
|
+
pending_stream_id: stream.pending_stream_id, ...(stream.active ? { active: stream.active } : {}), ...blocking });
|
|
876
|
+
return Object.freeze({ state: "active", session_id: journal.owner_id, active: stream.active!, ...blocking });
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/** Kernel-only explicit request; fresh-session bootstrap and public command admission remain separate. */
|
|
880
|
+
export function requestRunCompletionStream(input: { tempDir: string; ownerId: string }): Readonly<RunCompletionStreamIntent> {
|
|
881
|
+
const path = getRunDeliveryJournalPath(input.tempDir, input.ownerId);
|
|
882
|
+
return withFileMutationLock(path, () => {
|
|
883
|
+
assertNoDeliverySymlinks(input.tempDir, path);
|
|
884
|
+
const journal = readJournalAtPath(path, input.ownerId, true);
|
|
885
|
+
if (journal.completion_stream?.pending_stream_id) return Object.freeze({ pending_stream_id: journal.completion_stream.pending_stream_id });
|
|
886
|
+
const stream = Object.freeze({ pending_stream_id: randomUUID() });
|
|
887
|
+
journal.completion_stream = parseCompletionStreamState({ ...journal.completion_stream, ...stream });
|
|
888
|
+
writeTextAtomic(path, journalText(parseJournal(journal, input.ownerId, path)));
|
|
889
|
+
return stream;
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
export function admitRunCompletionBatch(input: AdmitRunCompletionBatchInput): RunCompletionBatch {
|
|
894
|
+
return captureCompletionBatch(input, false, "completion_yaml_core_v1");
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/** Kernel-only opt-in; ordinary Pi capture stays fenced until session/lifecycle integration is admitted. */
|
|
898
|
+
export function captureRunCompletionStreamBatch(input: AdmitRunCompletionBatchInput): RunCompletionBatch {
|
|
899
|
+
return captureCompletionBatch(input, true);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/** Private ordinary candidate composition; stream capture retains its separately gated codec. */
|
|
903
|
+
export function captureRunCompletionYamlCandidateBatch(input: AdmitRunCompletionBatchInput): RunCompletionBatch {
|
|
904
|
+
return captureCompletionBatch(input, false, "completion_yaml_core_v1");
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function captureCompletionBatch(input: AdmitRunCompletionBatchInput, numbered: boolean, layout: "frontmatter_v2" | "completion_yaml_core_v1" = "frontmatter_v2"): RunCompletionBatch {
|
|
540
908
|
const now = (input.now ?? new Date()).toISOString();
|
|
541
|
-
|
|
909
|
+
if (Object.hasOwn(input, "presentationFormat")) throw new Error("Completion presentation selection is not supported; new batches use AX.");
|
|
910
|
+
if (Object.hasOwn(input, "numbering")) throw new Error("Completion numbering is assigned only by journal capture");
|
|
911
|
+
const captured = parseBatch({
|
|
912
|
+
presentation_format: "ax_records",
|
|
913
|
+
navigation_handoff: input.fileNavigation === undefined ? "current_session_request" : "captured_file_locations",
|
|
914
|
+
...(input.fileNavigation === undefined ? {} : { file_navigation: input.fileNavigation }),
|
|
542
915
|
batch_id: input.batchId ?? randomUUID(),
|
|
543
916
|
created_at: now,
|
|
544
917
|
members: input.members,
|
|
545
918
|
phase: "pending",
|
|
546
919
|
});
|
|
920
|
+
const draft: RunCompletionBatch = layout === "completion_yaml_core_v1"
|
|
921
|
+
? parseBatch({ ...captured, result_layout: layout }) : { ...captured, result_layout: layout };
|
|
547
922
|
return mutateJournal(input.tempDir, input.ownerId, (journal) => {
|
|
923
|
+
let batch = draft;
|
|
548
924
|
if (journal.completion_batch) {
|
|
549
925
|
throw new Error("Run delivery journal already has an active completion batch");
|
|
550
926
|
}
|
|
551
927
|
if (journal.receipts.some((receipt) => receipt.delivery_id === batch.batch_id)) {
|
|
552
928
|
throw new Error("Run delivery batch id already has a presented receipt");
|
|
553
929
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
930
|
+
if (numbered) {
|
|
931
|
+
const stream = journal.completion_stream;
|
|
932
|
+
if (!stream) throw new Error("Completion stream capture requires retained stream state");
|
|
933
|
+
const target = stream.pending_stream_id, active = stream.active;
|
|
934
|
+
const streamId = target ?? active!.stream_id;
|
|
935
|
+
const counter: RunCompletionStreamCounter = target ? { state: "available", next_index: 0 } : active!.counter;
|
|
936
|
+
const range = counter.state === "available" ? projectRunCompletionIndexRange(counter.next_index, batch.members.length) : undefined;
|
|
937
|
+
const slots = new Map(batch.members.map((member, slot) => [memberIdentity(member), slot]));
|
|
938
|
+
const member_order = orderRunCompletionMembers(batch.members).map(member => slots.get(memberIdentity(member))!);
|
|
939
|
+
const numbering: RunCompletionNumbering = range?.state === "available"
|
|
940
|
+
? { kind: "stream", session_id: input.ownerId, stream_id: streamId, first_index: range.indices[0]!, member_order }
|
|
941
|
+
: { kind: "unavailable", session_id: input.ownerId, stream_id: streamId,
|
|
942
|
+
reason: range ? "range_unavailable" : "counter_unavailable", member_order };
|
|
943
|
+
batch = parseBatch({ ...batch, numbering });
|
|
944
|
+
journal.completion_stream = parseCompletionStreamState({ active: { stream_id: streamId,
|
|
945
|
+
counter: range?.state === "available" ? { state: "available", next_index: range.next_index } : counter } });
|
|
946
|
+
} else if (journal.completion_stream) {
|
|
947
|
+
throw new Error("Completion stream capture requires the numbered kernel path; stream state is retained");
|
|
948
|
+
}
|
|
949
|
+
if (layout !== "completion_yaml_core_v1") batch = retainSharedCompletionTexts(batch);
|
|
950
|
+
if (projectCompletionBatchMessage(batch).membersShown === batch.members.length) {
|
|
951
|
+
if (layout === "completion_yaml_core_v1") batch = retainSharedCompletionTexts(batch);
|
|
952
|
+
journal.completion_batch = batch;
|
|
953
|
+
return batch;
|
|
954
|
+
}
|
|
955
|
+
const content = completionInventoryText(batch);
|
|
956
|
+
const sha256 = createHash("sha256").update(content).digest("hex");
|
|
957
|
+
const journalPath = getRunDeliveryJournalPath(input.tempDir, input.ownerId);
|
|
958
|
+
const path = resolve(dirname(journalPath), `inventory-${sha256}.json`);
|
|
959
|
+
const retained = retainSharedCompletionTexts(parseBatch({ ...batch, inventory_source: { path, sha256, bytes_total: Buffer.byteLength(content), members_total: batch.members.length } }));
|
|
960
|
+
journal.completion_batch = retained;
|
|
961
|
+
// Refuse known journal overflow before a separate source effect. Never overwrite/reuse an orphan.
|
|
962
|
+
journalText(parseJournal(journal, input.ownerId, journalPath));
|
|
963
|
+
assertNoDeliverySymlinks(input.tempDir, path);
|
|
964
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
965
|
+
assertNoDeliverySymlinks(input.tempDir, path);
|
|
966
|
+
const directory = lstatSync(dirname(path));
|
|
967
|
+
if (!directory.isDirectory()) throw new Error("Completion inventory directory is invalid");
|
|
968
|
+
writeFileSync(path, content, { encoding: "utf8", flag: "wx", mode: 0o600 });
|
|
969
|
+
assertNoDeliverySymlinks(input.tempDir, path);
|
|
970
|
+
const current = lstatSync(dirname(path)); const source = lstatSync(path);
|
|
971
|
+
if (current.dev !== directory.dev || current.ino !== directory.ino || !source.isFile() || source.size !== Buffer.byteLength(content)) {
|
|
972
|
+
throw new Error("Completion inventory source binding changed during retention");
|
|
973
|
+
}
|
|
974
|
+
return retained;
|
|
975
|
+
}, numbered);
|
|
557
976
|
}
|
|
558
977
|
|
|
559
978
|
export function markRunCompletionBatchQueued(
|
|
@@ -803,8 +1222,10 @@ function completionDepth(
|
|
|
803
1222
|
return depth;
|
|
804
1223
|
}
|
|
805
1224
|
|
|
806
|
-
|
|
807
|
-
|
|
1225
|
+
/** Captured membership ordering only; never native lineage or a complete descendant tree. */
|
|
1226
|
+
export function orderRunCompletionMembers(
|
|
1227
|
+
members: readonly RunCompletionBatchMember[],
|
|
1228
|
+
prioritizeFailed = false,
|
|
808
1229
|
): RunCompletionBatchMember[] {
|
|
809
1230
|
const byIdentity = new Map(members.map((member) => [memberIdentity(member), member]));
|
|
810
1231
|
const children = new Map<string, RunCompletionBatchMember[]>();
|
|
@@ -813,7 +1234,7 @@ function orderCompletionForest(
|
|
|
813
1234
|
const parentIdentity = member.parent_state_dir && member.parent_run_instance_id
|
|
814
1235
|
? `${member.parent_state_dir}\0${member.parent_run_instance_id}`
|
|
815
1236
|
: undefined;
|
|
816
|
-
if (!parentIdentity || !byIdentity.has(parentIdentity)) {
|
|
1237
|
+
if (!parentIdentity || !byIdentity.has(parentIdentity) || (prioritizeFailed && byIdentity.get(parentIdentity)!.run !== member.parent_run)) {
|
|
817
1238
|
roots.push(member);
|
|
818
1239
|
continue;
|
|
819
1240
|
}
|
|
@@ -821,6 +1242,17 @@ function orderCompletionForest(
|
|
|
821
1242
|
siblings.push(member);
|
|
822
1243
|
children.set(parentIdentity, siblings);
|
|
823
1244
|
}
|
|
1245
|
+
const failed = new Set(members.filter(member => member.status === "failed").map(memberIdentity));
|
|
1246
|
+
if (prioritizeFailed) for (const member of members) {
|
|
1247
|
+
let current = member; const seen = new Set<string>();
|
|
1248
|
+
while (failed.has(memberIdentity(current)) && current.parent_state_dir && current.parent_run_instance_id) {
|
|
1249
|
+
const key = `${current.parent_state_dir}\0${current.parent_run_instance_id}`, parent = byIdentity.get(key);
|
|
1250
|
+
if (!parent || parent.run !== current.parent_run || seen.has(key)) break;
|
|
1251
|
+
seen.add(key); failed.add(key); current = parent;
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
const peers = (items: RunCompletionBatchMember[]) => prioritizeFailed
|
|
1255
|
+
? [...items.filter(member => failed.has(memberIdentity(member))), ...items.filter(member => !failed.has(memberIdentity(member)))] : items;
|
|
824
1256
|
const ordered: RunCompletionBatchMember[] = [];
|
|
825
1257
|
const visited = new Set<string>();
|
|
826
1258
|
const visit = (member: RunCompletionBatchMember): void => {
|
|
@@ -828,7 +1260,7 @@ function orderCompletionForest(
|
|
|
828
1260
|
if (visited.has(identity)) return;
|
|
829
1261
|
visited.add(identity);
|
|
830
1262
|
ordered.push(member);
|
|
831
|
-
for (const child of children.get(identity) ?? []) visit(child);
|
|
1263
|
+
for (const child of peers(children.get(identity) ?? [])) visit(child);
|
|
832
1264
|
};
|
|
833
1265
|
for (const root of roots) visit(root);
|
|
834
1266
|
for (const member of members) visit(member);
|
|
@@ -845,9 +1277,41 @@ function formatModelOutput(value: string, indent: string): string {
|
|
|
845
1277
|
.join("\n");
|
|
846
1278
|
}
|
|
847
1279
|
|
|
1280
|
+
function formatPrimaryText(binding: Readonly<RunPrimaryTextBinding>, indent: string, bracketed: boolean): string {
|
|
1281
|
+
if (!binding.output) return `\n${indent} Primary output unavailable: ${binding.output_unavailable}.`;
|
|
1282
|
+
const output = binding.output;
|
|
1283
|
+
return `\n${indent} Primary output: ${output.chars_shown}/${output.chars_total} code points; ${bracketed ? `[${output.chars_omitted} characters omitted]` : `${output.chars_omitted} omitted`}; ${output.truncated ? "clipped" : "complete exported text"}${output.redacted ? "; redacted" : ""}.` +
|
|
1284
|
+
`\n${indent} UTF-8 bytes: [${output.shown_bytes.start}, ${output.shown_bytes.end}) / ${output.bytes_total}.` +
|
|
1285
|
+
`\n${formatLiteralBlock(output.preview)}\n${indent} Retained source at capture:\n${formatLiteralBlock(JSON.stringify(output.source, null, 2))}`;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
function formatArtifactInventory(member: RunCompletionBatchMember, indent: string): string {
|
|
1289
|
+
if (member.artifact_inventory === undefined) return "";
|
|
1290
|
+
if (member.artifact_inventory === null) return member.artifact_inventory_unavailable === "not_declared" ? ""
|
|
1291
|
+
: `\n${indent} Artifact inventory unavailable: ${member.artifact_inventory_unavailable}; no legacy path fallback.`;
|
|
1292
|
+
const inventory = member.artifact_inventory;
|
|
1293
|
+
return `\n${indent} Artifact declarations: ${inventory.declarations_total}; metadata only; target availability/production not asserted; bodies not read.` +
|
|
1294
|
+
`\n${indent} Retained declaration inventory at capture:\n${formatLiteralBlock(JSON.stringify({ ...inventory.source,
|
|
1295
|
+
bytes_total: inventory.bytes_total, declarations_total: inventory.declarations_total }, null, 2))}`;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function formatResultDiagnostics(member: RunCompletionBatchMember, indent: string, bracketed: boolean): string {
|
|
1299
|
+
if (member.result_diagnostics === undefined) return "";
|
|
1300
|
+
if (member.result_diagnostics === null) return member.result_diagnostics_unavailable === "not_recorded" ? ""
|
|
1301
|
+
: `\n${indent} Diagnostic metadata unavailable: ${member.result_diagnostics_unavailable}; no legacy body fallback.`;
|
|
1302
|
+
const reference = member.result_diagnostics; const excerpt = member.diagnostic_excerpt;
|
|
1303
|
+
const others = reference.fields.filter(field => field !== excerpt?.field);
|
|
1304
|
+
return `\n${indent} Diagnostic metadata only; not primary output; admission privacy for recorded fields.` +
|
|
1305
|
+
(excerpt ? `\n${indent} Field ${excerpt.field}: ${excerpt.chars_shown}/${excerpt.chars_total} code points; ${bracketed ? `[${excerpt.chars_omitted} characters omitted]` : `${excerpt.chars_omitted} omitted`}; ${excerpt.truncated ? "clipped" : "complete field"}.` +
|
|
1306
|
+
`\n${indent} Field UTF-8 bytes: [${excerpt.shown_bytes.start}, ${excerpt.shown_bytes.end}) / ${excerpt.bytes_total}.\n${formatLiteralBlock(excerpt.text)}` : "") +
|
|
1307
|
+
(others.length ? `\n${indent} Recorded fields not shown: ${others.join(", ")}.` : "") +
|
|
1308
|
+
`\n${indent} Retained diagnostic source at capture:\n${formatLiteralBlock(JSON.stringify({ ...reference.source, bytes_total: reference.bytes_total, fields: reference.fields }, null, 2))}`;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
848
1311
|
function formatCompletionMember(
|
|
849
1312
|
member: RunCompletionBatchMember,
|
|
850
1313
|
byIdentity: Map<string, RunCompletionBatchMember>,
|
|
1314
|
+
bracketedExcerpts: boolean,
|
|
851
1315
|
): string {
|
|
852
1316
|
const summary = compactModelText(member.summary, 200);
|
|
853
1317
|
const artifactEntries = Object.entries(member.artifacts ?? {}).slice(0, 4);
|
|
@@ -858,12 +1322,13 @@ function formatCompletionMember(
|
|
|
858
1322
|
).join(", ")}`;
|
|
859
1323
|
const indent = " ".repeat(Math.min(completionDepth(member, byIdentity), 8));
|
|
860
1324
|
const output = member.output
|
|
861
|
-
? `\n${indent} Output:\n${formatModelOutput(member.output, indent)}`
|
|
1325
|
+
? `\n${indent} ${member.primary_text ? "Legacy diagnostic display (not a complete primary source)" : "Output"}:\n${formatModelOutput(member.output, indent)}`
|
|
862
1326
|
: "";
|
|
863
|
-
|
|
1327
|
+
const primary = member.primary_text ? formatPrimaryText(member.primary_text, indent, bracketedExcerpts) : "";
|
|
1328
|
+
return `${indent}- \`${compactModelText(member.run, 120)}\` — \`${member.status}\`: ${summary}${artifactText}${primary}${formatArtifactInventory(member, indent)}${formatResultDiagnostics(member, indent, bracketedExcerpts)}${output}`;
|
|
864
1329
|
}
|
|
865
1330
|
|
|
866
|
-
function formatStatusCounts(members: RunCompletionBatchMember[]): string {
|
|
1331
|
+
function formatStatusCounts(members: readonly RunCompletionBatchMember[]): string {
|
|
867
1332
|
const counts = new Map<RunCompletionDeliveryStatus, number>();
|
|
868
1333
|
for (const member of members) {
|
|
869
1334
|
counts.set(member.status, (counts.get(member.status) ?? 0) + 1);
|
|
@@ -874,23 +1339,1030 @@ function formatStatusCounts(members: RunCompletionBatchMember[]): string {
|
|
|
874
1339
|
.join(" ");
|
|
875
1340
|
}
|
|
876
1341
|
|
|
877
|
-
|
|
878
|
-
|
|
1342
|
+
function completionOmissionField(chars: number, direct: boolean): [string, string] {
|
|
1343
|
+
return direct ? ["omitted", `${chars} chars`] : ["omission", `[omitted: ${chars} chars]`];
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
function axCompletionExcerpt(preview: string, coverage: Pick<RunResultDiagnosticPacketExcerpt,
|
|
1347
|
+
"chars_shown" | "chars_total" | "chars_omitted" | "bytes_total" | "shown_bytes" | "truncated">, directOmission = false): string[] {
|
|
1348
|
+
return [
|
|
1349
|
+
formatMarkdownField("state", coverage.truncated ? "clipped" : "complete retained text"),
|
|
1350
|
+
formatMarkdownField("chars_shown", String(coverage.chars_shown)), formatMarkdownField("chars_total", String(coverage.chars_total)),
|
|
1351
|
+
formatMarkdownField("utf8_range", `[${coverage.shown_bytes.start}, ${coverage.shown_bytes.end}) / ${coverage.bytes_total}`),
|
|
1352
|
+
formatMarkdownField("preview", (coverage.shown_bytes.start > 0 ? "…\n" : "") + formatLiteralBlock(preview) +
|
|
1353
|
+
(coverage.shown_bytes.end < coverage.bytes_total ? "\n…" : "")),
|
|
1354
|
+
...(coverage.chars_omitted > 0 ? [formatMarkdownField(...completionOmissionField(coverage.chars_omitted, directOmission))] : []),
|
|
1355
|
+
];
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function formatRetainedCompletionMember(member: RunCompletionBatchMember, byIdentity: Map<string, RunCompletionBatchMember>, batchLegacyBasis = false): string {
|
|
1359
|
+
const run = member.run.startsWith("run:") ? member.run.slice(4) : member.run;
|
|
1360
|
+
// Keep the existing 200 UTF-16 display allowance, without normalizing opaque Markdown or leaking legacy tail lengths.
|
|
1361
|
+
let summary = member.summary;
|
|
1362
|
+
if (summary.length > 200) {
|
|
1363
|
+
let end = 199;
|
|
1364
|
+
if (/[\uD800-\uDBFF]/u.test(summary[end - 1]) && /[\uDC00-\uDFFF]/u.test(summary[end])) end--;
|
|
1365
|
+
summary = summary.slice(0, end) + "…";
|
|
1366
|
+
}
|
|
1367
|
+
const fields = [formatMarkdownField("run", run), formatMarkdownField("state", member.status), formatMarkdownField("summary", summary),
|
|
1368
|
+
...(member.summary.length > 200 ? [formatMarkdownField("summary_omission", "[omitted: unknown]")] : []),
|
|
1369
|
+
...(batchLegacyBasis ? [] : [formatMarkdownField("legacy_basis", "Recorded summary/output; original completeness/privacy unproven."),
|
|
1370
|
+
formatMarkdownField("legacy_upstream", "[omitted: unknown]")])];
|
|
1371
|
+
if (member.parent_state_dir && member.parent_run_instance_id) {
|
|
1372
|
+
const parent = byIdentity.get(`${member.parent_state_dir}\0${member.parent_run_instance_id}`);
|
|
1373
|
+
if (parent && parent.run === member.parent_run) fields.push(formatMarkdownField("parent_run", parent.run.startsWith("run:") ? parent.run.slice(4) : parent.run));
|
|
1374
|
+
}
|
|
1375
|
+
const primary = member.primary_text;
|
|
1376
|
+
if (primary) fields.push(formatMarkdownField("primary", primary.output ? [
|
|
1377
|
+
formatMarkdownField("scope", "Exported primary text at capture; admission privacy, not complete Actor output."),
|
|
1378
|
+
...axCompletionExcerpt(primary.output.preview, primary.output),
|
|
1379
|
+
...(primary.output.redacted ? [formatMarkdownField("redacted", "true")] : []),
|
|
1380
|
+
formatMarkdownField("source", formatLiteralBlock(JSON.stringify(primary.output.source, null, 2))),
|
|
1381
|
+
].join("\n") : [formatMarkdownField("state", "unavailable"), formatMarkdownField("reason", primary.output_unavailable)].join("\n")));
|
|
1382
|
+
if (member.artifact_inventory) {
|
|
1383
|
+
const inventory = member.artifact_inventory;
|
|
1384
|
+
fields.push(formatMarkdownField("artifact_inventory", [formatMarkdownField("declarations", String(inventory.declarations_total)),
|
|
1385
|
+
formatMarkdownField("scope", "Metadata only; target availability/production not asserted; bodies not read."),
|
|
1386
|
+
formatMarkdownField("source", formatLiteralBlock(JSON.stringify({ ...inventory.source, bytes_total: inventory.bytes_total,
|
|
1387
|
+
declarations_total: inventory.declarations_total }, null, 2)))].join("\n")));
|
|
1388
|
+
} else if (member.artifact_inventory === null && member.artifact_inventory_unavailable !== "not_declared") {
|
|
1389
|
+
fields.push(formatMarkdownField("artifact_inventory", [formatMarkdownField("state", "unavailable"),
|
|
1390
|
+
formatMarkdownField("reason", member.artifact_inventory_unavailable!), formatMarkdownField("warning", "no legacy path fallback")].join("\n")));
|
|
1391
|
+
}
|
|
1392
|
+
const artifacts = Object.entries(member.artifacts ?? {});
|
|
1393
|
+
if (artifacts.length) fields.push(formatMarkdownField("artifacts", [formatMarkdownField("scope", "Declared paths; existence/production unproven."),
|
|
1394
|
+
...artifacts.map(([name, path]) => formatMarkdownField("artifact",
|
|
1395
|
+
[formatMarkdownField("name", name), formatMarkdownField("path", formatLiteralBlock(JSON.stringify(path)))].join("\n")))].join("\n")));
|
|
1396
|
+
if (member.result_diagnostics) {
|
|
1397
|
+
const reference = member.result_diagnostics, excerpt = member.diagnostic_excerpt;
|
|
1398
|
+
const others = reference.fields.filter(field => field !== excerpt?.field);
|
|
1399
|
+
fields.push(formatMarkdownField("diagnostic_metadata", [
|
|
1400
|
+
formatMarkdownField("scope", "Diagnostic metadata only; not primary output; admission privacy for recorded fields."),
|
|
1401
|
+
...(excerpt ? [formatMarkdownField("field", excerpt.field), ...axCompletionExcerpt(excerpt.text, excerpt)] : []),
|
|
1402
|
+
...(others.length ? [formatMarkdownField("recorded_fields_not_shown", others.join(", "))] : []),
|
|
1403
|
+
formatMarkdownField("source", formatLiteralBlock(JSON.stringify({ ...reference.source, bytes_total: reference.bytes_total, fields: reference.fields }, null, 2))),
|
|
1404
|
+
].join("\n")));
|
|
1405
|
+
} else if (member.result_diagnostics === null && member.result_diagnostics_unavailable !== "not_recorded") {
|
|
1406
|
+
fields.push(formatMarkdownField("diagnostic_metadata", [formatMarkdownField("state", "unavailable"),
|
|
1407
|
+
formatMarkdownField("reason", member.result_diagnostics_unavailable!), formatMarkdownField("warning", "no legacy body fallback")].join("\n")));
|
|
1408
|
+
}
|
|
1409
|
+
if (member.output) {
|
|
1410
|
+
if (primary) fields.push(formatMarkdownField("output_scope", "Legacy diagnostic display (not a complete primary source)"));
|
|
1411
|
+
fields.push(formatMarkdownField("output", member.output));
|
|
1412
|
+
}
|
|
1413
|
+
return fields.join("\n");
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
export interface RunCompletionPresentation {
|
|
1417
|
+
readonly format: "ax_records" | "ax_records_v3" | "completion_frontmatter_v1";
|
|
1418
|
+
readonly blocks?: readonly CompletionBlock[];
|
|
1419
|
+
readonly content_sha256: string;
|
|
1420
|
+
/** UTF-16 offsets in the exact content, not UTF-8 byte or source excerpt ranges. */
|
|
1421
|
+
readonly compact_ranges: readonly (readonly [number, number])[];
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function completionRecordHeader(content: string): { format: string; single: boolean; length: number } | undefined {
|
|
1425
|
+
const single = /^- completion: [^\r\n\u2028\u2029]{1,128}\n- presentation: (ax_records)\n/u.exec(content);
|
|
1426
|
+
const match = single ?? /^- completions: [1-9]\d{0,2}\n- batch: [^\r\n\u2028\u2029]{1,128}\n- presentation: (ax_records(?:_v3)?)\n/u.exec(content);
|
|
1427
|
+
return match ? { format: match[1]!, single: single !== null, length: match[0].length } : undefined;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function formatCompletionRecordMarker(batchId: string): string {
|
|
1431
|
+
const literal = JSON.stringify(batchId).replaceAll(/[<>\u2028\u2029]/gu, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
1432
|
+
return `<!-- pi-actors:completion ${literal} -->`;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function completionStreamBoundary(streamId: string): string {
|
|
1436
|
+
return `— Новый поток ${streamId} · отсчёт с #0 —`;
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
function completionNumberingTechnical(numbering: RunCompletionNumbering): string {
|
|
1440
|
+
return formatMarkdownField("technical", formatLiteralBlock(JSON.stringify({ completion_numbering: numbering })));
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
function readCompletionNumberingFrame(content: string, markerStart: number): RunCompletionNumbering | { malformed: true } | undefined {
|
|
1444
|
+
const match = /\n- technical:\n (`{3,})text\n ([^\r\n]*)\n \1$/u.exec(content.slice(0, markerStart));
|
|
1445
|
+
if (!match) return undefined;
|
|
1446
|
+
let data: Record<string, unknown>;
|
|
1447
|
+
try { data = JSON.parse(match[2]!); } catch { return undefined; }
|
|
1448
|
+
if (!data || typeof data !== "object" || !Object.hasOwn(data, "completion_numbering")) return undefined;
|
|
1449
|
+
try {
|
|
1450
|
+
const raw = asRecord(data.completion_numbering, "Completion numbering"), order = raw.member_order;
|
|
1451
|
+
if (Object.keys(data).length !== 1 || !Array.isArray(order) || order.length < 1 || order.length > Limits.RUN_DELIVERY_BATCH_MAX_MEMBERS) return { malformed: true };
|
|
1452
|
+
const numbering = parseCompletionNumbering(raw, order.length);
|
|
1453
|
+
return completionNumberingTechnical(numbering) === match[0].slice(1) ? numbering : { malformed: true };
|
|
1454
|
+
} catch { return { malformed: true }; }
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
/** Compiler-bound correlation data, not authentication or permission. Body equality cannot identify an epoch. */
|
|
1458
|
+
export function readRunCompletionRecordMarker(content: string): { batchId: string; start: number; single: boolean; headers?: true } | undefined {
|
|
1459
|
+
if (Buffer.byteLength(content, "utf8") > Limits.RUN_COMPLETION_MODEL_MAX_BYTES) return undefined;
|
|
1460
|
+
const fresh = readFrontmatterFrame(content);
|
|
1461
|
+
if (fresh) return { batchId: fresh.batchId, start: fresh.markerStart, single: false, headers: true };
|
|
1462
|
+
const boundary = /^— Новый поток ([^\r\n]+) · отсчёт с #0 —\n/u.exec(content);
|
|
1463
|
+
const body = boundary ? content.slice(boundary[0].length) : content;
|
|
1464
|
+
const numbered = /^#(\?|0|[1-9]\d{0,15}):\n/u.exec(body);
|
|
1465
|
+
const headers = content.startsWith("#0:\n") || content.startsWith("Run #0:\n");
|
|
1466
|
+
const single = content.startsWith("- run:") || content.startsWith("- result: omitted (model byte limit)\n");
|
|
1467
|
+
if (!boundary && !numbered && !headers && !single && !/^- #0:\n|^- omitted_results: [1-9]\d{0,2}\n/u.test(content)) return undefined;
|
|
1468
|
+
const match = /\n<!-- pi-actors:completion ("[^\r\n]{0,768}") -->$/u.exec(content);
|
|
1469
|
+
if (!match) return undefined;
|
|
1470
|
+
try {
|
|
1471
|
+
const batchId: unknown = JSON.parse(match[1]!);
|
|
1472
|
+
if (typeof batchId !== "string" || !batchId.trim() || batchId.length > 128 || formatCompletionRecordMarker(batchId) !== match[0].slice(1)) return undefined;
|
|
1473
|
+
const frame = readCompletionNumberingFrame(content, match.index);
|
|
1474
|
+
if (frame && "malformed" in frame) return undefined;
|
|
1475
|
+
if (frame) {
|
|
1476
|
+
const startsStream = frame.kind === "stream" && frame.first_index === 0;
|
|
1477
|
+
if (startsStream !== !!boundary || (boundary && completionStreamBoundary(frame.stream_id!) + "\n" !== boundary[0])) return undefined;
|
|
1478
|
+
const expected = frame.kind === "stream" ? String(frame.first_index) : "?";
|
|
1479
|
+
if (numbered ? numbered[1] !== expected : !body.startsWith(`- omitted_results: ${frame.member_order.length}\n`)) return undefined;
|
|
1480
|
+
return { batchId, start: match.index, single: false, headers: true };
|
|
1481
|
+
}
|
|
1482
|
+
if (boundary || (numbered && numbered[1] !== "0")) return undefined;
|
|
1483
|
+
return { batchId, start: match.index, single, ...(headers ? { headers: true as const } : {}) };
|
|
1484
|
+
} catch { return undefined; }
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
export type CompletionBlock = readonly [kind: "header" | "body" | "marker" | "service", start: number, end: number];
|
|
1488
|
+
type CompletionPiece = { text: string; compact: boolean; block?: CompletionBlock[0]; field?: readonly [string, string] };
|
|
1489
|
+
type CompletionRow = { pieces: CompletionPiece[]; technical: string; technical_data: Record<string, unknown>; legacy: boolean;
|
|
1490
|
+
completion_text?: Readonly<RunCompletionText> };
|
|
1491
|
+
|
|
1492
|
+
function completionPiece(key: string, value: string, depth = 0, compact = true): CompletionPiece {
|
|
1493
|
+
return { text: formatMarkdownField(key, value, depth), compact, field: [key, value] };
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
function completionPreview(value: string, depth: number, before: boolean, after: boolean, field = "preview"): CompletionPiece[] {
|
|
1497
|
+
const indent = " ".repeat(depth), opening = formatLiteralBlock(value).split("\n", 1)[0]!;
|
|
1498
|
+
return [
|
|
1499
|
+
{ text: `${indent}- ${field}:${before ? `\n${indent} …` : ""}`, compact: true },
|
|
1500
|
+
{ text: `${indent} ${opening}`, compact: false },
|
|
1501
|
+
{ text: `${indent} ${value.replaceAll(/\r\n|\r|\n/gu, newline => `${newline}${indent} `)}`, compact: true },
|
|
1502
|
+
{ text: `${indent} ${opening.slice(0, -4)}`, compact: false },
|
|
1503
|
+
...(after ? [{ text: `${indent} …`, compact: true }] : []),
|
|
1504
|
+
];
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
function completionCoverage(value: Pick<RunResultDiagnosticPacketExcerpt,
|
|
1508
|
+
"chars_shown" | "chars_total" | "chars_omitted" | "bytes_total" | "shown_bytes" | "truncated">) {
|
|
1509
|
+
return { state: value.truncated ? "clipped" : "complete retained text", chars_shown: value.chars_shown,
|
|
1510
|
+
chars_total: value.chars_total, utf8_range: `[${value.shown_bytes.start}, ${value.shown_bytes.end}) / ${value.bytes_total}` };
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function isRoutineCompletionSummary(member: RunCompletionBatchMember): boolean {
|
|
1514
|
+
return member.summary === (member.status === "done" ? "Run completed." : `Run ${member.status}.`);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
function completionRecordRow(member: RunCompletionBatchMember, index: number, byIdentity: Map<string, RunCompletionBatchMember>, flat = false, layout?: RunCompletionBatch["result_layout"], textBudget?: number): CompletionRow {
|
|
1518
|
+
const frontmatter = isFrontmatterCompletionLayout(layout) || layout === "completion_yaml_core_v1";
|
|
1519
|
+
const simpleKinds = frontmatter || layout === "headings_shared_kinds", shared = isSharedCompletionLayout(layout);
|
|
1520
|
+
const outcomes = shared || layout === "headings_outcomes", directOmission = outcomes || layout === "headings_flat_omissions";
|
|
1521
|
+
const run = member.run.startsWith("run:") ? member.run.slice(4) : member.run;
|
|
1522
|
+
const depth = flat ? 0 : 2;
|
|
1523
|
+
const pieces: CompletionPiece[] = [...(flat ? [] : [{ text: ` - #${index}:`, compact: true }]), completionPiece("run", run, depth), completionPiece("state", member.status, depth)];
|
|
1524
|
+
const technical: Record<string, unknown> = {};
|
|
1525
|
+
let primary = member.primary_text?.output;
|
|
1526
|
+
let diagnostic = outcomes && member.diagnostic_excerpt?.field === "command" ? undefined : member.diagnostic_excerpt;
|
|
1527
|
+
let completionText: Readonly<RunCompletionText> | undefined;
|
|
1528
|
+
if (shared && primary) {
|
|
1529
|
+
const captured = member.completion_text;
|
|
1530
|
+
const selected = projectRunCompletionText(captured?.text ?? primary.preview, textBudget!);
|
|
1531
|
+
const start = captured ? 0 : primary.shown_bytes.start, shown = [...selected.text].length;
|
|
1532
|
+
if (captured) completionText = selected;
|
|
1533
|
+
primary = { ...primary, preview: selected.text, shown_bytes: { start, end: start + selected.shown_bytes.end },
|
|
1534
|
+
chars_shown: shown, chars_omitted: primary.chars_total - shown, truncated: shown < primary.chars_total };
|
|
1535
|
+
} else if (shared && diagnostic) {
|
|
1536
|
+
const selected = projectRunCompletionText(diagnostic.text, textBudget!), shown = [...selected.text].length;
|
|
1537
|
+
diagnostic = { ...diagnostic, text: selected.text, shown_bytes: { start: diagnostic.shown_bytes.start,
|
|
1538
|
+
end: diagnostic.shown_bytes.start + selected.shown_bytes.end }, chars_shown: shown,
|
|
1539
|
+
chars_omitted: diagnostic.chars_total - shown, truncated: shown < diagnostic.chars_total };
|
|
1540
|
+
}
|
|
1541
|
+
const kind = primary ? primary.text_basis === "actor_response" ? "response" : "primary" : diagnostic ? "diagnostic" : member.output ? "legacy" : undefined;
|
|
1542
|
+
const boilerplate = isRoutineCompletionSummary(member);
|
|
1543
|
+
let legacy = !!member.output;
|
|
1544
|
+
if (!boilerplate) {
|
|
1545
|
+
legacy = true;
|
|
1546
|
+
let end = Math.min(member.summary.length, 200);
|
|
1547
|
+
if (member.summary.length > 200) {
|
|
1548
|
+
end = 199;
|
|
1549
|
+
if (/[\uD800-\uDBFF]/u.test(member.summary[end - 1]!) && /[\uDC00-\uDFFF]/u.test(member.summary[end]!)) end--;
|
|
1550
|
+
}
|
|
1551
|
+
const summary = member.summary.slice(0, end);
|
|
1552
|
+
if (frontmatter) pieces.push({ text: Buffer.from(summary).toString("utf8") === summary ? summary : "Summary unavailable (malformed retained text)", compact: true, block: "body" });
|
|
1553
|
+
else {
|
|
1554
|
+
pieces.push(completionPiece("summary", Buffer.from(summary).toString("utf8") === summary
|
|
1555
|
+
? summary + (end < member.summary.length ? "…" : "") : "unavailable (malformed retained text)", depth));
|
|
1556
|
+
if (end < member.summary.length || Buffer.from(summary).toString("utf8") !== summary) pieces.push(completionPiece("summary_omission", "[omitted: unknown]", depth));
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
const chosen = primary ?? diagnostic;
|
|
1560
|
+
const legacyText = shared && member.output && Buffer.from(member.output).toString("utf8") === member.output
|
|
1561
|
+
? projectRunCompletionText(member.output, textBudget!).text : member.output;
|
|
1562
|
+
const value = primary?.preview ?? diagnostic?.text ?? legacyText;
|
|
1563
|
+
if (kind && value !== undefined) {
|
|
1564
|
+
if (!simpleKinds || kind !== "legacy") {
|
|
1565
|
+
pieces.push(completionPiece(simpleKinds ? "kind" : shared ? "result_kind" : "preview_kind",
|
|
1566
|
+
simpleKinds && kind === "response" ? "primary" : kind, depth));
|
|
1567
|
+
}
|
|
1568
|
+
if (diagnostic && !primary) {
|
|
1569
|
+
technical.preview_field = diagnostic.field;
|
|
1570
|
+
if (outcomes) pieces.push(completionPiece("preview_field", diagnostic.field, depth));
|
|
1571
|
+
}
|
|
1572
|
+
if (Buffer.from(value).toString("utf8") !== value) {
|
|
1573
|
+
pieces.push(frontmatter ? { text: "[omitted: unknown] (malformed retained text)", compact: true, block: "marker" }
|
|
1574
|
+
: completionPiece("preview_unavailable", "malformed retained text [omitted: unknown]", depth));
|
|
1575
|
+
} else {
|
|
1576
|
+
if (frontmatter) {
|
|
1577
|
+
if (chosen && chosen.shown_bytes.start > 0) pieces.push({ text: "[omitted: unknown] (retained excerpt starts after the source boundary)", compact: true, block: "marker" });
|
|
1578
|
+
pieces.push({ text: value, compact: true, block: "body" });
|
|
1579
|
+
if (chosen && chosen.chars_omitted > 0) pieces.push({ text: `[${chosen.chars_omitted} characters omitted]`, compact: true, block: "marker" });
|
|
1580
|
+
} else {
|
|
1581
|
+
const complete = shared && primary && primary.shown_bytes.start === 0 && primary.shown_bytes.end === primary.bytes_total && !primary.upstream_truncated;
|
|
1582
|
+
pieces.push(...completionPreview(value, depth, !!chosen && chosen.shown_bytes.start > 0,
|
|
1583
|
+
!!chosen && chosen.shown_bytes.end < chosen.bytes_total, complete ? "result" : "preview"));
|
|
1584
|
+
if (shared && kind === "legacy" && member.output && value !== member.output) pieces.push(completionPiece(simpleKinds ? "text_omission" : "legacy_omission", "[omitted: unknown]", depth));
|
|
1585
|
+
if (chosen && chosen.chars_omitted > 0) pieces.push(completionPiece(...completionOmissionField(chosen.chars_omitted, directOmission), depth));
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (primary?.redacted) pieces.push(completionPiece("redacted", "true", depth));
|
|
1589
|
+
if (outcomes && primary?.upstream_truncated) pieces.push(frontmatter
|
|
1590
|
+
? { text: "[omitted: unknown] (upstream completeness unproven)", compact: true, block: "marker" }
|
|
1591
|
+
: completionPiece("upstream_omission", "[omitted: unknown]", depth));
|
|
1592
|
+
} else pieces.push(completionPiece(shared ? "result_state" : "preview_state", "not captured", depth));
|
|
1593
|
+
if (member.parent_state_dir && member.parent_run_instance_id) {
|
|
1594
|
+
const parent = byIdentity.get(`${member.parent_state_dir}\0${member.parent_run_instance_id}`);
|
|
1595
|
+
if (parent && parent.run === member.parent_run) pieces.push(completionPiece("parent_run", parent.run.startsWith("run:") ? parent.run.slice(4) : parent.run, depth));
|
|
1596
|
+
}
|
|
1597
|
+
if (member.primary_text) {
|
|
1598
|
+
if (primary) technical.primary = { scope: primary.text_basis === "actor_response"
|
|
1599
|
+
? "Executor-selected final Actor response at capture; admission privacy; prior stream clipping remains separate."
|
|
1600
|
+
: "Exported primary text at capture; admission privacy, not complete Actor output.",
|
|
1601
|
+
...(primary.text_basis ? { text_basis: primary.text_basis, upstream_truncated: primary.upstream_truncated } : {}),
|
|
1602
|
+
...completionCoverage(primary), ...(primary.redacted ? { redacted: true } : {}), source: primary.source };
|
|
1603
|
+
else if (!member.primary_text.output) pieces.push(completionPiece("primary_unavailable", member.primary_text.output_unavailable, depth));
|
|
1604
|
+
}
|
|
1605
|
+
if (member.result_diagnostics) {
|
|
1606
|
+
const reference = member.result_diagnostics;
|
|
1607
|
+
technical.diagnostic_metadata = { scope: "Diagnostic metadata only; not primary output; admission privacy for recorded fields.",
|
|
1608
|
+
...(diagnostic ? { field: diagnostic.field, ...completionCoverage(diagnostic) } : {}),
|
|
1609
|
+
recorded_fields_not_shown: reference.fields.filter(field => field !== diagnostic?.field),
|
|
1610
|
+
source: { ...reference.source, bytes_total: reference.bytes_total, fields: reference.fields } };
|
|
1611
|
+
if (primary && diagnostic) technical.secondary_diagnostic_excerpt = axCompletionExcerpt(diagnostic.text, diagnostic, directOmission).join("\n");
|
|
1612
|
+
} else if (member.result_diagnostics === null && member.result_diagnostics_unavailable !== "not_recorded") {
|
|
1613
|
+
pieces.push(completionPiece("diagnostic_metadata_unavailable", member.result_diagnostics_unavailable!, depth));
|
|
1614
|
+
technical.diagnostic_warning = "no legacy body fallback";
|
|
1615
|
+
}
|
|
1616
|
+
if (member.artifact_inventory) {
|
|
1617
|
+
const inventory = member.artifact_inventory;
|
|
1618
|
+
pieces.push(completionPiece("artifact_declarations", String(inventory.declarations_total), depth));
|
|
1619
|
+
technical.artifact_inventory = { scope: "Metadata only; target availability/production not asserted; bodies not read.",
|
|
1620
|
+
source: { ...inventory.source, bytes_total: inventory.bytes_total, declarations_total: inventory.declarations_total } };
|
|
1621
|
+
} else if (member.artifact_inventory === null && member.artifact_inventory_unavailable !== "not_declared") {
|
|
1622
|
+
pieces.push(completionPiece("artifact_inventory_unavailable", member.artifact_inventory_unavailable!, depth));
|
|
1623
|
+
technical.artifact_warning = "no legacy path fallback";
|
|
1624
|
+
}
|
|
1625
|
+
const artifacts = Object.entries(member.artifacts ?? {});
|
|
1626
|
+
if (artifacts.length) {
|
|
1627
|
+
pieces.push(completionPiece("artifact_declarations", String(artifacts.length), depth));
|
|
1628
|
+
technical.artifacts = { scope: "Declared paths; existence/production unproven.", declarations: artifacts.map(([name, path]) => ({ name, path })) };
|
|
1629
|
+
}
|
|
1630
|
+
return { pieces, technical: Object.keys(technical).length ? formatMarkdownField(flat ? "result_details" : `#${index}`, formatLiteralBlock(JSON.stringify(technical, null, 2))) : "", technical_data: technical, legacy, ...(completionText ? { completion_text: completionText } : {}) };
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
const FRONTMATTER_FRAME = "<!-- pi-actors:frontmatter ";
|
|
1634
|
+
|
|
1635
|
+
function readFrontmatterFrame(content: string): { blocks: readonly CompletionBlock[]; batchId: string; markerStart: number } | undefined {
|
|
1636
|
+
try {
|
|
1637
|
+
if (Buffer.byteLength(content) > Limits.RUN_COMPLETION_MODEL_MAX_BYTES) return undefined;
|
|
1638
|
+
const match = /\n<!-- pi-actors:frontmatter ([^\r\n]+) -->\n<!-- pi-actors:completion ("[^\r\n]+") -->$/u.exec(content);
|
|
1639
|
+
if (!match) return undefined;
|
|
1640
|
+
const frame = JSON.parse(match[1]!), batchId = JSON.parse(match[2]!);
|
|
1641
|
+
if (typeof batchId !== "string" || !batchId.trim() || batchId.length > 128 || formatCompletionRecordMarker(batchId) !== `<!-- pi-actors:completion ${match[2]} -->`) return undefined;
|
|
1642
|
+
if (frontmatterJson(frame) !== match[1] || Object.keys(frame).sort().join() !== (frame.numbering ? "blocks,numbering,prefix_sha256" : "blocks,prefix_sha256")) return undefined;
|
|
1643
|
+
const prefix = content.slice(0, match.index);
|
|
1644
|
+
if (frame.prefix_sha256 !== createHash("sha256").update(prefix).digest("hex") || !Array.isArray(frame.blocks)) return undefined;
|
|
1645
|
+
let previous = 0, rows = 0, inRow = false;
|
|
1646
|
+
const numbering = frame.numbering ? parseCompletionNumbering(frame.numbering, frame.numbering.member_order.length) : undefined;
|
|
1647
|
+
const startsStream = numbering?.kind === "stream" && numbering.first_index === 0;
|
|
1648
|
+
if (startsStream && (frame.blocks[0]?.[0] !== "service" || content.slice(frame.blocks[0][1], frame.blocks[0][2]) !== completionStreamBoundary(numbering.stream_id))) return undefined;
|
|
1649
|
+
const boundary = (n: number) => !(n > 0 && n < content.length && /[\uD800-\uDBFF]/u.test(content[n - 1]!) && /[\uDC00-\uDFFF]/u.test(content[n]!)) && !(content[n - 1] === "\r" && content[n] === "\n");
|
|
1650
|
+
for (const block of frame.blocks) {
|
|
1651
|
+
if (!Array.isArray(block) || block.length !== 3) return undefined;
|
|
1652
|
+
const [kind, start, end] = block;
|
|
1653
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start !== previous + (previous ? kind === "header" ? 2 : 1 : 0) || end < start || end > prefix.length || !boundary(start) || !boundary(end)) return undefined;
|
|
1654
|
+
if (content.slice(previous, start) !== (previous ? kind === "header" ? "\n\n" : "\n" : "")) return undefined;
|
|
1655
|
+
const text = content.slice(start, end);
|
|
1656
|
+
if (kind === "header") {
|
|
1657
|
+
const expected = numbering ? numbering.kind === "stream" ? numbering.first_index + rows : "?" : rows;
|
|
1658
|
+
if (!(text.startsWith(`---\nindex: ${expected}\nrun: `) || expected === "?" && text.startsWith('---\nindex: "?"\nrun: ')) || !text.endsWith("\n---\n")) return undefined;
|
|
1659
|
+
const lines = text.slice(4, -5).split("\n");
|
|
1660
|
+
if (!/^state: (done|failed|killed|exited)$/u.test(lines[2] ?? "")) return undefined;
|
|
1661
|
+
const keys = new Set<string>();
|
|
1662
|
+
for (const line of lines) {
|
|
1663
|
+
const field = /^([a-z_]+): (.+)$/u.exec(line);
|
|
1664
|
+
if (!field || keys.has(field[1]!) || !/^(?:index|run|state|kind|preview_field|redacted|result_state|parent_run|primary_unavailable|diagnostic_metadata_unavailable|artifact_declarations|artifact_inventory_unavailable|run_files_unavailable|references|run_files)$/u.test(field[1]!)) return undefined;
|
|
1665
|
+
if (field[1] === "kind" && (keys.size !== 3 || !/^(?:primary|diagnostic)$/u.test(field[2]!))) return undefined;
|
|
1666
|
+
keys.add(field[1]!);
|
|
1667
|
+
const value = field[2]!;
|
|
1668
|
+
if (!/^(?:[a-z][a-z0-9_-]*|\?|\d+)$/u.test(value) && frontmatterJson(JSON.parse(value)) !== value) return undefined;
|
|
1669
|
+
}
|
|
1670
|
+
rows++; inRow = true;
|
|
1671
|
+
} else if (kind === "body") { if (!inRow) return undefined; }
|
|
1672
|
+
else if (kind === "marker") {
|
|
1673
|
+
if (!inRow || !/^(?:\[[1-9]\d* characters omitted\]|\[omitted: unknown\] \((?:retained excerpt starts after the source boundary|upstream completeness unproven|summary\/fallback text: original completeness\/privacy unproven|malformed retained text)\)|Run-file reference omitted \(packet byte limit\)\.)$/u.test(text)) return undefined;
|
|
1674
|
+
} else if (kind === "service") {
|
|
1675
|
+
if (!(text === (numbering?.kind === "stream" && numbering.first_index === 0 ? completionStreamBoundary(numbering.stream_id) : "") || /^- (?:omitted_results|omitted_states|inventory_warning|numbering_unavailable|result_inventory): /u.test(text) || text.startsWith("- omitted_states:\n"))) return undefined;
|
|
1676
|
+
inRow = false;
|
|
1677
|
+
} else return undefined;
|
|
1678
|
+
previous = end;
|
|
1679
|
+
}
|
|
1680
|
+
if (previous !== prefix.length || rows > Limits.RUN_DELIVERY_MODEL_MAX_MEMBERS) return undefined;
|
|
1681
|
+
return { blocks: frame.blocks, batchId, markerStart: content.lastIndexOf("\n<!-- pi-actors:completion ") };
|
|
1682
|
+
} catch { return undefined; }
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
function frontmatterJson(value: unknown): string {
|
|
1686
|
+
return JSON.stringify(value).replaceAll(/[<>\u2028\u2029]/gu, char => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
/** All prefix bytes belong to a visible block. Only this closed compiler footer can be hidden. */
|
|
1690
|
+
function assembleFrontmatter(safe: RunCompletionBatch, candidates: readonly { row: CompletionRow; location?: CompletionPiece }[], references: ReadonlySet<number>, ordered: readonly RunCompletionBatchMember[]) {
|
|
1691
|
+
let message = "";
|
|
1692
|
+
const blocks: CompletionBlock[] = [];
|
|
1693
|
+
const add = (kind: CompletionBlock[0], text: string) => {
|
|
1694
|
+
if (message) message += kind === "header" ? "\n\n" : "\n";
|
|
1695
|
+
const start = message.length; message += text;
|
|
1696
|
+
blocks.push(Object.freeze([kind, start, message.length]));
|
|
1697
|
+
};
|
|
1698
|
+
if (safe.numbering?.kind === "stream" && safe.numbering.first_index === 0) add("service", completionStreamBoundary(safe.numbering.stream_id));
|
|
1699
|
+
for (const [index, { row, location }] of candidates.entries()) {
|
|
1700
|
+
const displayed = safe.numbering ? safe.numbering.kind === "stream" ? safe.numbering.first_index + index : "?" : index;
|
|
1701
|
+
const fields = row.pieces.filter(piece => !piece.block);
|
|
1702
|
+
const technical = { ...row.technical_data };
|
|
1703
|
+
for (const key of ["primary", "diagnostic_metadata"]) {
|
|
1704
|
+
if (!technical[key]) continue;
|
|
1705
|
+
const detail = { ...technical[key] as Record<string, unknown> };
|
|
1706
|
+
for (const field of ["state", "chars_shown", "chars_total", "utf8_range", "upstream_truncated"]) {
|
|
1707
|
+
delete detail[field];
|
|
1708
|
+
}
|
|
1709
|
+
technical[key] = detail;
|
|
1710
|
+
}
|
|
1711
|
+
if (Object.hasOwn(technical, "secondary_diagnostic_excerpt")) {
|
|
1712
|
+
delete technical.secondary_diagnostic_excerpt;
|
|
1713
|
+
}
|
|
1714
|
+
const yaml = fields.map(piece => {
|
|
1715
|
+
if (!piece.field) throw new Error("Completion frontmatter field is invalid");
|
|
1716
|
+
const [key, value] = piece.field;
|
|
1717
|
+
return `${key}: ${/^[a-z][a-z0-9_-]*$/u.test(value) && !/^(?:null|true|false|yes|no|on|off)$/iu.test(value) ? value : frontmatterJson(value)}`;
|
|
1718
|
+
});
|
|
1719
|
+
if (Object.keys(technical).length) yaml.push(`references: ${frontmatterJson(technical)}`);
|
|
1720
|
+
if (references.has(index) && location) {
|
|
1721
|
+
const navigation = safe.file_navigation!.find(item => safe.members[item.member_index] === ordered[index])!;
|
|
1722
|
+
yaml.push(`run_files: ${frontmatterJson({ run: ordered[index]!.run, run_instance_id: ordered[index]!.run_instance_id, locations: navigation.locations })}`);
|
|
1723
|
+
}
|
|
1724
|
+
const yamlIndex = displayed === "?" && safe.result_layout === "frontmatter_v2" ? frontmatterJson(displayed) : displayed;
|
|
1725
|
+
add("header", `---\nindex: ${yamlIndex}\n${yaml.join("\n")}\n---\n`);
|
|
1726
|
+
for (const piece of row.pieces) if (piece.block) add(piece.block, piece.text);
|
|
1727
|
+
if (safe.file_navigation !== undefined && !references.has(index)) add("marker", "Run-file reference omitted (packet byte limit).");
|
|
1728
|
+
}
|
|
1729
|
+
const omitted = safe.members.length - candidates.length;
|
|
1730
|
+
if (omitted) {
|
|
1731
|
+
add("service", `- omitted_results: ${omitted}`);
|
|
1732
|
+
const states = (["failed", "killed", "exited"] as const).flatMap(status => {
|
|
1733
|
+
const count = ordered.slice(candidates.length).filter(member => member.status === status).length;
|
|
1734
|
+
return count ? [` - ${status}: ${count}`] : [];
|
|
1735
|
+
});
|
|
1736
|
+
if (states.length) add("service", "- omitted_states:\n" + states.join("\n"));
|
|
1737
|
+
if (!safe.inventory_source) add("service", "- inventory_warning: No inventory reference is retained in this packet.");
|
|
1738
|
+
}
|
|
1739
|
+
if (safe.numbering?.kind === "unavailable") add("service", `- numbering_unavailable: ${safe.numbering.reason}`);
|
|
1740
|
+
if (safe.inventory_source) add("service", `- result_inventory: ${frontmatterJson(safe.inventory_source)}`);
|
|
1741
|
+
const frame = { blocks, prefix_sha256: createHash("sha256").update(message).digest("hex"), ...(safe.numbering ? { numbering: safe.numbering } : {}) };
|
|
1742
|
+
message += `\n${FRONTMATTER_FRAME}${frontmatterJson(frame)} -->\n${formatCompletionRecordMarker(safe.batch_id)}`;
|
|
1743
|
+
return { message, membersShown: candidates.length, blocks: Object.freeze(blocks), compact_ranges: blocks.map(([, a, b]) => [a, b] as [number, number]) };
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
type CompletionYamlProjectionReason = "invalid_snapshot" | "unsupported_source_class" | "unsupported_selected_data" | "overbudget" | "compatibility_refused";
|
|
1747
|
+
class CompletionYamlProjectionRefusal extends Error {
|
|
1748
|
+
readonly reason: CompletionYamlProjectionReason;
|
|
1749
|
+
constructor(reason: CompletionYamlProjectionReason) { super(reason); this.reason = reason; }
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// Only invoked at known metadata string slots; never on body text or arbitrary user objects.
|
|
1753
|
+
function completionYamlString(value: string): CompletionYamlData {
|
|
1754
|
+
if (value.isWellFormed()) return value;
|
|
1755
|
+
return { utf16_code_units: Array.from({ length: value.length }, (_, index) => value.charCodeAt(index).toString(16).padStart(4, "0")).join(" ") };
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
function completionYamlReferences(row: CompletionRow): Record<string, CompletionYamlData> {
|
|
1759
|
+
const technical = { ...row.technical_data } as Record<string, CompletionYamlData>;
|
|
1760
|
+
for (const key of ["primary", "diagnostic_metadata"]) {
|
|
1761
|
+
if (!technical[key]) continue;
|
|
1762
|
+
const detail = { ...technical[key] as Record<string, CompletionYamlData> };
|
|
1763
|
+
for (const field of ["state", "chars_shown", "chars_total", "utf8_range", "upstream_truncated"]) delete detail[field];
|
|
1764
|
+
technical[key] = detail;
|
|
1765
|
+
}
|
|
1766
|
+
delete technical.secondary_diagnostic_excerpt;
|
|
1767
|
+
for (const key of ["primary", "diagnostic_metadata", "artifact_inventory"]) {
|
|
1768
|
+
const detail = technical[key] as Record<string, CompletionYamlData> | undefined;
|
|
1769
|
+
if (!detail) continue;
|
|
1770
|
+
const source = { ...detail.source as Record<string, CompletionYamlData> };
|
|
1771
|
+
for (const slot of ["path", "run_instance_id", "sha256"]) {
|
|
1772
|
+
if (typeof source[slot] === "string") source[slot] = completionYamlString(source[slot]);
|
|
1773
|
+
}
|
|
1774
|
+
technical[key] = { ...detail, source };
|
|
1775
|
+
}
|
|
1776
|
+
const artifacts = technical.artifacts as { scope: string; declarations: { name: string; path: string }[] } | undefined;
|
|
1777
|
+
if (artifacts) technical.artifacts = { scope: artifacts.scope, declarations: artifacts.declarations.map(item => ({
|
|
1778
|
+
name: completionYamlString(item.name), path: completionYamlString(item.path),
|
|
1779
|
+
})) };
|
|
1780
|
+
return technical;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
type PreparedCompletionYamlProjection =
|
|
1784
|
+
| { state: "unsupported" }
|
|
1785
|
+
| { state: "overbudget" }
|
|
1786
|
+
| { state: "prepared"; bytes: number; chunks: string[]; packet: CompletionYamlPacket };
|
|
1787
|
+
|
|
1788
|
+
function prepareCompletionYamlProjection(safe: RunCompletionBatch, candidates: readonly { row: CompletionRow; location?: CompletionPiece }[],
|
|
1789
|
+
references: ReadonlySet<number>, ordered: readonly RunCompletionBatchMember[]): PreparedCompletionYamlProjection {
|
|
1790
|
+
const packet: CompletionYamlPacket = { batchId: safe.batch_id, records: [] };
|
|
1791
|
+
if (!safe.batch_id.isWellFormed()) return { state: "unsupported" };
|
|
1792
|
+
for (const [index, { row, location }] of candidates.entries()) {
|
|
1793
|
+
const member = ordered[index]!;
|
|
1794
|
+
const displayed = safe.numbering ? safe.numbering.kind === "stream" ? safe.numbering.first_index + index : "?" : index;
|
|
1795
|
+
const metadata: Record<string, CompletionYamlData> = { index: displayed };
|
|
1796
|
+
for (const piece of row.pieces) {
|
|
1797
|
+
if (piece.block) continue;
|
|
1798
|
+
if (!piece.field) return { state: "unsupported" };
|
|
1799
|
+
const [key, value] = piece.field;
|
|
1800
|
+
if (["run", "state", "kind"].includes(key)) {
|
|
1801
|
+
if (!value.isWellFormed() || !value.length || (key === "kind" && value !== "primary" && value !== "diagnostic")) return { state: "unsupported" };
|
|
1802
|
+
metadata[key] = value;
|
|
1803
|
+
} else if (key === "redacted") metadata[key] = true;
|
|
1804
|
+
else if (key === "artifact_declarations") metadata[key] = member.artifact_inventory?.declarations_total ?? Object.keys(member.artifacts ?? {}).length;
|
|
1805
|
+
else metadata[key] = completionYamlString(value);
|
|
1806
|
+
}
|
|
1807
|
+
const technical = completionYamlReferences(row);
|
|
1808
|
+
if (Object.keys(technical).length) metadata.references = technical;
|
|
1809
|
+
if (references.has(index) && location) {
|
|
1810
|
+
const navigation = safe.file_navigation!.find(item => safe.members[item.member_index] === member)!;
|
|
1811
|
+
// Navigation is already decoded native data, not the JSON display field.
|
|
1812
|
+
const locations = navigation.locations;
|
|
1813
|
+
metadata.run_files = { run: completionYamlString(member.run), run_instance_id: completionYamlString(member.run_instance_id),
|
|
1814
|
+
locations: locations.state === "available" ? { ...locations, run: completionYamlString(locations.run),
|
|
1815
|
+
run_instance_id: completionYamlString(locations.run_instance_id), directory: completionYamlString(locations.directory),
|
|
1816
|
+
files: locations.files.map(file => ({ ...file, ...(typeof file.path === "string" ? { path: completionYamlString(file.path) } : {}) })) } as CompletionYamlData
|
|
1817
|
+
: { ...locations } };
|
|
1818
|
+
}
|
|
1819
|
+
const parts: CompletionYamlPart[] = [];
|
|
1820
|
+
const add = (part: CompletionYamlPart) => { if (parts.length) parts.push({ marker: "\n" }); parts.push(part); };
|
|
1821
|
+
for (const piece of row.pieces) if (piece.block) {
|
|
1822
|
+
if (!piece.text.isWellFormed()) return { state: "unsupported" };
|
|
1823
|
+
add(piece.block === "body" ? { body: piece.text } : { marker: piece.text });
|
|
1824
|
+
}
|
|
1825
|
+
if (safe.file_navigation !== undefined && !references.has(index)) add({ marker: "Run-file reference omitted (packet byte limit)." });
|
|
1826
|
+
packet.records.push({ metadata, parts });
|
|
1827
|
+
}
|
|
1828
|
+
const service: Record<string, CompletionYamlData> = {};
|
|
1829
|
+
const omitted = safe.members.length - candidates.length;
|
|
1830
|
+
if (omitted) {
|
|
1831
|
+
service.omitted_results = omitted;
|
|
1832
|
+
const states: Record<string, CompletionYamlData> = {};
|
|
1833
|
+
for (const status of ["failed", "killed", "exited"] as const) {
|
|
1834
|
+
const count = ordered.slice(candidates.length).filter(member => member.status === status).length;
|
|
1835
|
+
if (count) states[status] = count;
|
|
1836
|
+
}
|
|
1837
|
+
if (Object.keys(states).length) service.omitted_states = states;
|
|
1838
|
+
if (!safe.inventory_source) service.inventory_warning = "No inventory reference is retained in this packet.";
|
|
1839
|
+
}
|
|
1840
|
+
if (safe.numbering) {
|
|
1841
|
+
service.numbering = { ...safe.numbering, session_id: completionYamlString(safe.numbering.session_id), member_order: [...safe.numbering.member_order] };
|
|
1842
|
+
if (safe.numbering.kind === "unavailable") service.numbering_unavailable = safe.numbering.reason;
|
|
1843
|
+
}
|
|
1844
|
+
if (safe.inventory_source) service.result_inventory = { ...safe.inventory_source, path: completionYamlString(safe.inventory_source.path) };
|
|
1845
|
+
if (Object.keys(service).length) packet.service = service;
|
|
1846
|
+
const chunks: string[] = [];
|
|
1847
|
+
let bytes = 0;
|
|
1848
|
+
for (let index = 0; index < Math.max(1, packet.records.length); index++) {
|
|
1849
|
+
const row = packet.records[index];
|
|
1850
|
+
const bookkeeping: Record<string, CompletionYamlData> = index === 0 ? { codec: completionYamlCodec, batch_id: packet.batchId,
|
|
1851
|
+
...(packet.service ? { service: packet.service } : {}) } : {};
|
|
1852
|
+
if (row?.parts.length) bookkeeping.parts = row.parts.map((part): CompletionYamlData => "body" in part ? { body: part.body.length } : { marker: part.marker.length });
|
|
1853
|
+
const header = { ...row?.metadata, ...(Object.keys(bookkeeping).length ? { _completion: bookkeeping } : {}) };
|
|
1854
|
+
const rendered = renderYamlMetadata(header, completionYamlMaxBytes);
|
|
1855
|
+
if (rendered.state !== "available") return { state: rendered.reason === "metadata_too_large" ? "overbudget" : "unsupported" };
|
|
1856
|
+
if (rendered.text.includes("\n\n")) return { state: "unsupported" };
|
|
1857
|
+
const framed = !!row?.parts.length;
|
|
1858
|
+
chunks.push(framed ? `---\n${rendered.text}---\n` : rendered.text);
|
|
1859
|
+
for (const part of row?.parts ?? []) chunks.push("body" in part ? part.body : part.marker);
|
|
1860
|
+
if (index + 1 < packet.records.length) chunks.push(framed ? "\n\n" : "\n");
|
|
1861
|
+
}
|
|
1862
|
+
for (const chunk of chunks) bytes += Buffer.byteLength(chunk, "utf8");
|
|
1863
|
+
return bytes > completionYamlMaxBytes ? { state: "overbudget" } : { state: "prepared", bytes, chunks, packet };
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
function materializeCompletionYamlProjection(prepared: PreparedCompletionYamlProjection) {
|
|
1867
|
+
if (prepared.state !== "prepared") throw new CompletionYamlProjectionRefusal(prepared.state === "overbudget" ? "overbudget" : "unsupported_selected_data");
|
|
1868
|
+
const message = prepared.chunks.join("");
|
|
1869
|
+
const encoded = encodeCompletionYamlCarrier(prepared.packet);
|
|
1870
|
+
if (encoded.state !== "available" || encoded.text !== message || readCompletionYamlCarrier(message).state !== "available") {
|
|
1871
|
+
throw new CompletionYamlProjectionRefusal("compatibility_refused");
|
|
1872
|
+
}
|
|
1873
|
+
return { message, membersShown: prepared.packet.records.length, compact_ranges: [] as [number, number][] };
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/** Nondefault pure projection; neither a newly admitted Batch nor a rendering/acceptance grant. */
|
|
1877
|
+
export function projectRunCompletionYamlPrototype(batch: unknown):
|
|
1878
|
+
| { state: "available"; content: string; presentation: { format: "completion_yaml_core_v1"; content_sha256: string }; membersShown: number; textBudget: number }
|
|
1879
|
+
| { state: "unavailable"; reason: CompletionYamlProjectionReason } {
|
|
1880
|
+
let safe: RunCompletionBatch;
|
|
1881
|
+
try { safe = parseBatch(batch); } catch { return { state: "unavailable", reason: "invalid_snapshot" }; }
|
|
1882
|
+
if (safe.presentation_format !== "ax_records" || safe.result_layout !== "frontmatter_v2") return { state: "unavailable", reason: "unsupported_source_class" };
|
|
1883
|
+
try {
|
|
1884
|
+
const projected = projectCompletionRecords(safe, "yaml");
|
|
1885
|
+
return { state: "available", content: projected.message, membersShown: projected.membersShown, textBudget: projected.textBudget!,
|
|
1886
|
+
presentation: { format: "completion_yaml_core_v1", content_sha256: createHash("sha256").update(projected.message).digest("hex") } };
|
|
1887
|
+
} catch (error) {
|
|
1888
|
+
if (error instanceof CompletionYamlProjectionRefusal) return { state: "unavailable", reason: error.reason };
|
|
1889
|
+
throw error;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
function projectCompletionRecords(safe: RunCompletionBatch, assembler: "legacy" | "yaml" = safe.result_layout === "completion_yaml_core_v1" ? "yaml" : "legacy"): { message: string; membersShown: number; compact_ranges: [number, number][]; textBudget?: number; blocks?: readonly CompletionBlock[] } {
|
|
1894
|
+
const frontmatter = isFrontmatterCompletionLayout(safe.result_layout) || assembler === "yaml";
|
|
1895
|
+
const shared = isSharedCompletionLayout(safe.result_layout);
|
|
1896
|
+
const maxBytes = shared ? Limits.RUN_COMPLETION_MODEL_MAX_BYTES : Limits.RUN_DELIVERY_MODEL_MAX_BYTES;
|
|
1897
|
+
const headers = shared || safe.result_layout === "headings_outcomes" || safe.result_layout === "headings_flat_omissions" || safe.result_layout === "run_headers" || safe.result_layout === "headings", single = safe.members.length === 1;
|
|
1898
|
+
const numbering = safe.numbering, ordered = numbering ? numbering.member_order.map(slot => safe.members[slot]!) : orderRunCompletionMembers(safe.members);
|
|
1899
|
+
const byIdentity = new Map(safe.members.map(member => [memberIdentity(member), member]));
|
|
1900
|
+
const makeCandidates = (quota?: number) => ordered.slice(0, Limits.RUN_DELIVERY_MODEL_MAX_MEMBERS).map((member, index) => {
|
|
1901
|
+
const row = completionRecordRow(member, index, byIdentity, true, safe.result_layout, quota);
|
|
1902
|
+
const navigation = safe.file_navigation?.find(item => safe.members[item.member_index] === member);
|
|
1903
|
+
if (navigation?.locations.state === "unavailable") row.pieces.push(completionPiece("run_files_unavailable", navigation.locations.reason));
|
|
1904
|
+
if (row.legacy) row.pieces.push(frontmatter
|
|
1905
|
+
? { text: "[omitted: unknown] (summary/fallback text: original completeness/privacy unproven)", compact: true, block: "marker" }
|
|
1906
|
+
: safe.result_layout === "headings_shared_kinds"
|
|
1907
|
+
? completionPiece("warning", "Summary/fallback text: original completeness/privacy unproven [omitted: unknown]")
|
|
1908
|
+
: completionPiece("legacy_basis", "Summary/legacy output: original completeness/privacy unproven [omitted: unknown]"));
|
|
1909
|
+
const technical = Object.keys(row.technical_data).length ? completionPiece("technical", formatLiteralBlock(JSON.stringify(row.technical_data, null, 2)), 0, false) : undefined;
|
|
1910
|
+
const location = navigation ? completionPiece("run_files", formatLiteralBlock(JSON.stringify({ run: member.run,
|
|
1911
|
+
run_instance_id: member.run_instance_id, locations: navigation.locations })), 0, false) : undefined;
|
|
1912
|
+
const baseBytes = assembler === "yaml" ? 0 : Buffer.byteLength([...row.pieces, ...(technical ? [technical] : [])].map(piece => piece.text).join("\n"), "utf8");
|
|
1913
|
+
return { row, navigation, technical, location, baseBytes };
|
|
1914
|
+
});
|
|
1915
|
+
let candidates = makeCandidates(shared ? safe.text_budget ?? 0 : undefined);
|
|
1916
|
+
const assemble = (shown: number, references: ReadonlySet<number>) => {
|
|
1917
|
+
if (frontmatter) return assembleFrontmatter(safe, candidates.slice(0, shown), references, ordered);
|
|
1918
|
+
const pieces: CompletionPiece[] = [];
|
|
1919
|
+
if (numbering?.kind === "stream" && numbering.first_index === 0) pieces.push({ text: completionStreamBoundary(numbering.stream_id), compact: true });
|
|
1920
|
+
for (let index = 0; index < shown; index++) {
|
|
1921
|
+
const { row, technical, location } = candidates[index]!;
|
|
1922
|
+
const fields = [...row.pieces];
|
|
1923
|
+
if (safe.file_navigation !== undefined && !references.has(index)) fields.push(completionPiece("run_files_omitted", "1"));
|
|
1924
|
+
if (technical) fields.push(technical);
|
|
1925
|
+
if (references.has(index)) fields.push(location!);
|
|
1926
|
+
const displayed = numbering ? numbering.kind === "stream" ? numbering.first_index + index : "?" : index;
|
|
1927
|
+
if (headers) pieces.push({ text: `${index ? "\n" : ""}${safe.result_layout === "run_headers" ? "Run " : ""}#${displayed}:`, compact: true });
|
|
1928
|
+
else if (!single) pieces.push({ text: `- #${index}:`, compact: true });
|
|
1929
|
+
pieces.push(...fields.map(piece => headers || single ? piece : { ...piece, text: " " + piece.text.replaceAll(/\r\n|\r|\n/gu, newline => `${newline} `) }));
|
|
1930
|
+
}
|
|
1931
|
+
const omitted = safe.members.length - shown;
|
|
1932
|
+
if (omitted) {
|
|
1933
|
+
pieces.push(single && !headers ? completionPiece("result", "omitted (model byte limit)") : completionPiece("omitted_results", String(omitted)));
|
|
1934
|
+
const states = (["failed", "killed", "exited"] as const).flatMap(status => {
|
|
1935
|
+
const count = ordered.slice(shown).filter(member => member.status === status).length;
|
|
1936
|
+
return count ? [formatMarkdownField(status, String(count))] : [];
|
|
1937
|
+
});
|
|
1938
|
+
if (states.length) pieces.push({ text: "- omitted_states:\n" + states.map(line => " " + line).join("\n"), compact: true });
|
|
1939
|
+
if (!safe.inventory_source) pieces.push(completionPiece("inventory_warning", "No inventory reference is retained in this packet."));
|
|
1940
|
+
}
|
|
1941
|
+
if (numbering?.kind === "unavailable") pieces.push(completionPiece("numbering_unavailable", numbering.reason));
|
|
1942
|
+
if (safe.inventory_source) pieces.push(completionPiece("result_inventory", formatLiteralBlock(JSON.stringify(safe.inventory_source, null, 2)), 0, false));
|
|
1943
|
+
if (numbering) pieces.push({ text: completionNumberingTechnical(numbering), compact: false });
|
|
1944
|
+
pieces.push({ text: formatCompletionRecordMarker(safe.batch_id), compact: false });
|
|
1945
|
+
let message = "";
|
|
1946
|
+
const compact_ranges: [number, number][] = [];
|
|
1947
|
+
for (const piece of pieces) {
|
|
1948
|
+
if (message) message += "\n";
|
|
1949
|
+
const start = message.length; message += piece.text;
|
|
1950
|
+
if (piece.compact) compact_ranges.push([start, message.length]);
|
|
1951
|
+
}
|
|
1952
|
+
return { message, membersShown: shown, compact_ranges };
|
|
1953
|
+
};
|
|
1954
|
+
// Closed second consumer: legacy assembly remains eager, with its original call order.
|
|
1955
|
+
const prepare = (count: number, refs: ReadonlySet<number>) => prepareCompletionYamlProjection(safe, candidates.slice(0, count), refs, ordered);
|
|
1956
|
+
const measure = (count: number, refs: ReadonlySet<number>): number => {
|
|
1957
|
+
if (assembler === "legacy") return Buffer.byteLength(assemble(count, refs).message, "utf8");
|
|
1958
|
+
const prepared = prepare(count, refs);
|
|
1959
|
+
if (prepared.state === "unsupported") throw new CompletionYamlProjectionRefusal("unsupported_selected_data");
|
|
1960
|
+
return prepared.state === "overbudget" ? Infinity : prepared.bytes;
|
|
1961
|
+
};
|
|
1962
|
+
if (assembler === "yaml") {
|
|
1963
|
+
// Validate selected facts even in rows that fitting may later omit.
|
|
1964
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
1965
|
+
if (prepareCompletionYamlProjection(safe, [candidate], new Set([0]), [ordered[index]!]).state === "unsupported") {
|
|
1966
|
+
throw new CompletionYamlProjectionRefusal("unsupported_selected_data");
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
let shown = 0, textBudget = safe.text_budget;
|
|
1971
|
+
let references = new Set<number>();
|
|
1972
|
+
const fitPrefix = (): void => {
|
|
1973
|
+
let minimumBytes = 0;
|
|
1974
|
+
for (let count = 1; count <= candidates.length; count++) {
|
|
1975
|
+
minimumBytes += candidates[count - 1]!.baseBytes + (count > 1 ? 1 : 0);
|
|
1976
|
+
if (!frontmatter && minimumBytes > maxBytes) break;
|
|
1977
|
+
if (count > shown && measure(count, references) <= maxBytes) shown = count;
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
1980
|
+
fitPrefix();
|
|
1981
|
+
if (shared && textBudget === undefined) {
|
|
1982
|
+
const maximum = Math.floor(maxBytes / Math.max(1, candidates.length));
|
|
1983
|
+
const boundaries = [...new Set(ordered.slice(0, candidates.length).flatMap(member => {
|
|
1984
|
+
const output = member.primary_text?.output;
|
|
1985
|
+
const diagnostic = member.diagnostic_excerpt?.field === "command" ? undefined : member.diagnostic_excerpt;
|
|
1986
|
+
const tracked = output ?? diagnostic;
|
|
1987
|
+
const text = member.completion_text?.text ?? output?.preview ?? diagnostic?.text ?? member.output;
|
|
1988
|
+
const size = tracked?.bytes_total ?? (text === undefined ? undefined : Buffer.byteLength(text));
|
|
1989
|
+
const points = size !== undefined && size <= maximum ? [size] : [];
|
|
1990
|
+
// Two displayed omitted counts can shrink together at decimal boundaries.
|
|
1991
|
+
// Their scalar positions need byte coordinates, including a possible CRLF retraction.
|
|
1992
|
+
if (tracked && text !== undefined) for (let power = 10; power <= tracked.chars_total; power *= 10) {
|
|
1993
|
+
const count = tracked.chars_total - power + 1;
|
|
1994
|
+
if (count <= 0 || count > text.length) continue;
|
|
1995
|
+
const scalars = [...text]; if (count > scalars.length) continue;
|
|
1996
|
+
const end = Buffer.byteLength(scalars.slice(0, count).join("")) + (scalars[count - 1] === "\r" && scalars[count] === "\n" ? 1 : 0);
|
|
1997
|
+
if (end <= maximum) points.push(end);
|
|
1998
|
+
}
|
|
1999
|
+
return points;
|
|
2000
|
+
}))].sort((a, b) => a - b);
|
|
2001
|
+
// Completing a value removes omission scaffolding: check those discontinuities before omitting rows.
|
|
2002
|
+
for (const point of boundaries) {
|
|
2003
|
+
if (shown === candidates.length) break;
|
|
2004
|
+
candidates = makeCandidates(point); fitPrefix();
|
|
2005
|
+
}
|
|
2006
|
+
const fits = (quota: number): boolean => {
|
|
2007
|
+
candidates = makeCandidates(quota);
|
|
2008
|
+
return measure(shown, references) <= maxBytes;
|
|
2009
|
+
};
|
|
2010
|
+
let low = 0, high = maximum, best = 0;
|
|
2011
|
+
while (low <= high) {
|
|
2012
|
+
const middle = Math.floor((low + high) / 2);
|
|
2013
|
+
if (fits(middle)) { best = middle; low = middle + 1; } else high = middle - 1;
|
|
2014
|
+
}
|
|
2015
|
+
// Cost is monotone between these count/scaffolding discontinuities, not across them.
|
|
2016
|
+
for (let index = 0; index < boundaries.length; index++) {
|
|
2017
|
+
const point = boundaries[index]!;
|
|
2018
|
+
if (point <= best || !fits(point)) continue;
|
|
2019
|
+
best = point; low = point + 1; high = (boundaries[index + 1] ?? maximum + 1) - 1;
|
|
2020
|
+
while (low <= high) {
|
|
2021
|
+
const middle = Math.floor((low + high) / 2);
|
|
2022
|
+
if (fits(middle)) { best = middle; low = middle + 1; } else high = middle - 1;
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
textBudget = best; candidates = makeCandidates(best);
|
|
2026
|
+
}
|
|
2027
|
+
// Outcome rows have priority. References of wholly omitted results remain covered by their result omission/inventory.
|
|
2028
|
+
for (const item of safe.file_navigation ?? []) {
|
|
2029
|
+
const index = ordered.indexOf(safe.members[item.member_index]!);
|
|
2030
|
+
if (index >= shown) continue;
|
|
2031
|
+
const next = new Set(references); next.add(index);
|
|
2032
|
+
if (measure(shown, next) > maxBytes) break;
|
|
2033
|
+
references = next;
|
|
2034
|
+
}
|
|
2035
|
+
const result = assembler === "legacy" ? assemble(shown, references) : materializeCompletionYamlProjection(prepare(shown, references));
|
|
2036
|
+
if (Buffer.byteLength(result.message, "utf8") > maxBytes) throw new Error("Run completion batch header exceeds the model byte limit");
|
|
2037
|
+
return { ...result, ...(shared ? { textBudget } : {}) };
|
|
2038
|
+
}
|
|
2039
|
+
/** Freeze the share and selected text, not 64 independent 48KiB copies in the journal. */
|
|
2040
|
+
function retainSharedCompletionTexts(batch: RunCompletionBatch): RunCompletionBatch {
|
|
2041
|
+
if (!isSharedCompletionLayout(batch.result_layout)) return batch;
|
|
2042
|
+
const projected = projectCompletionRecords({ ...batch, text_budget: undefined });
|
|
2043
|
+
const order = batch.numbering ? batch.numbering.member_order.map(slot => batch.members[slot]!) : orderRunCompletionMembers(batch.members);
|
|
2044
|
+
const selected = new Set(order.slice(0, Limits.RUN_DELIVERY_MODEL_MAX_MEMBERS));
|
|
2045
|
+
return parseBatch({ ...batch, text_budget: projected.textBudget, members: batch.members.map(member => {
|
|
2046
|
+
const { completion_text, ...rest } = member;
|
|
2047
|
+
return completion_text && selected.has(member)
|
|
2048
|
+
? { ...rest, completion_text: projectRunCompletionText(completion_text.text, projected.textBudget!) } : rest;
|
|
2049
|
+
}) });
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
|
|
2053
|
+
function projectRetainedRecordCompletionBatch(safe: RunCompletionBatch): { message: string; membersShown: number; compact_ranges: [number, number][] } {
|
|
2054
|
+
const single = safe.single_result === true;
|
|
2055
|
+
const ordered = orderRunCompletionMembers(safe.members), byIdentity = new Map(safe.members.map(member => [memberIdentity(member), member]));
|
|
2056
|
+
const times = safe.members.map(member => member.terminal_at).sort();
|
|
2057
|
+
const candidates = ordered.slice(0, Limits.RUN_DELIVERY_MODEL_MAX_MEMBERS).map((member, index) => {
|
|
2058
|
+
const row = completionRecordRow(member, index, byIdentity, single);
|
|
2059
|
+
const navigation = safe.file_navigation?.find(item => safe.members[item.member_index] === member);
|
|
2060
|
+
if (navigation?.locations.state === "unavailable") row.pieces.push(completionPiece("run_files_unavailable", navigation.locations.reason, single ? 0 : 2));
|
|
2061
|
+
return row;
|
|
2062
|
+
});
|
|
2063
|
+
const assemble = (rows: CompletionRow[], references: string[]) => {
|
|
2064
|
+
const pieces = single ? [completionPiece("completion", safe.batch_id, 0, false), completionPiece("presentation", "ax_records", 0, false)]
|
|
2065
|
+
: [completionPiece("completions", String(safe.members.length)), completionPiece("batch", safe.batch_id, 0, false),
|
|
2066
|
+
completionPiece("presentation", safe.presentation_format!, 0, false)];
|
|
2067
|
+
if (safe.members.length > 1) for (const status of ["done", "failed", "killed", "exited"] as const) {
|
|
2068
|
+
const count = safe.members.filter(member => member.status === status).length;
|
|
2069
|
+
if (count) pieces.push(completionPiece(`${status}_count`, String(count)));
|
|
2070
|
+
}
|
|
2071
|
+
if (!single && rows.some(row => row.legacy)) pieces.push(completionPiece("legacy_basis", "Summary/legacy output: original completeness/privacy unproven [omitted: unknown]"));
|
|
2072
|
+
if (rows.length) pieces.push(...(single ? [] : [{ text: "- members:", compact: true }]), ...rows.flatMap(row => row.pieces));
|
|
2073
|
+
if (single && rows.some(row => row.legacy)) pieces.push(completionPiece("legacy_basis", "Summary/legacy output: original completeness/privacy unproven [omitted: unknown]"));
|
|
2074
|
+
const omitted = safe.members.length - rows.length;
|
|
2075
|
+
if (omitted) {
|
|
2076
|
+
pieces.push(single ? completionPiece("result", "omitted (model byte limit)") : completionPiece("members_omitted", String(omitted)));
|
|
2077
|
+
if (!safe.inventory_source) pieces.push(completionPiece("inventory_warning", "No inventory reference is retained in this packet."));
|
|
2078
|
+
}
|
|
2079
|
+
const summaries = Object.fromEntries(ordered.slice(0, rows.length).filter(isRoutineCompletionSummary).map(member => [member.status, member.summary]));
|
|
2080
|
+
const technical = [formatMarkdownField("window_start", times[0]!), formatMarkdownField("window_end", times.at(-1)!),
|
|
2081
|
+
...(Object.keys(summaries).length ? [formatMarkdownField("runtime_summaries", formatLiteralBlock(JSON.stringify(summaries)))] : []),
|
|
2082
|
+
...(safe.inventory_source ? [formatMarkdownField("member_inventory", formatLiteralBlock(JSON.stringify(safe.inventory_source, null, 2)))] : []),
|
|
2083
|
+
...(rows.some(row => row.technical) ? [(single ? rows[0]!.technical : formatMarkdownField("member_details", rows.map(row => row.technical).filter(Boolean).join("\n")))] : [])];
|
|
2084
|
+
if (safe.navigation_handoff !== undefined) technical.push(formatMarkdownField("file_investigation", "optional; current session"), formatInspectNextAction("runtime", "runs", true));
|
|
2085
|
+
if (safe.file_navigation !== undefined) {
|
|
2086
|
+
pieces.push(completionPiece("run_files_shown", String(references.length)), completionPiece("run_files_omitted", String(safe.members.length - references.length)));
|
|
2087
|
+
technical.push(formatMarkdownField("run_files", [formatMarkdownField("total", String(safe.members.length)),
|
|
2088
|
+
formatMarkdownField("scope", "Captured completion members only; mutable locations, not body/privacy/terminal proof."), ...references].join("\n")));
|
|
2089
|
+
}
|
|
2090
|
+
pieces.push(completionPiece("technical", technical.join("\n"), 0, false));
|
|
2091
|
+
let message = "";
|
|
2092
|
+
const compact_ranges: [number, number][] = [];
|
|
2093
|
+
for (const piece of pieces) {
|
|
2094
|
+
if (message) message += "\n";
|
|
2095
|
+
const start = message.length; message += piece.text;
|
|
2096
|
+
if (piece.compact) compact_ranges.push([start, message.length]);
|
|
2097
|
+
}
|
|
2098
|
+
return { message, membersShown: rows.length, compact_ranges };
|
|
2099
|
+
};
|
|
2100
|
+
const rows: CompletionRow[] = [], references: string[] = [];
|
|
2101
|
+
for (const candidate of candidates) {
|
|
2102
|
+
if (Buffer.byteLength(assemble([...rows, candidate], references).message, "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) break;
|
|
2103
|
+
rows.push(candidate);
|
|
2104
|
+
}
|
|
2105
|
+
for (const item of safe.file_navigation ?? []) {
|
|
2106
|
+
const member = safe.members[item.member_index], index = ordered.indexOf(member);
|
|
2107
|
+
const row = formatMarkdownField(single ? "location" : `#${index}`, formatLiteralBlock(JSON.stringify({ run: member.run, run_instance_id: member.run_instance_id, locations: item.locations })));
|
|
2108
|
+
if (Buffer.byteLength(assemble(rows, [...references, row]).message, "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) break;
|
|
2109
|
+
references.push(row);
|
|
2110
|
+
}
|
|
2111
|
+
const result = assemble(rows, references);
|
|
2112
|
+
if (Buffer.byteLength(result.message, "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) throw new Error("Run completion batch header exceeds the model byte limit");
|
|
2113
|
+
return result;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
/** Single full model content plus compiler-owned UI offsets; no source reads or saved-packet mutation. */
|
|
2117
|
+
export function projectRunCompletionBatchPresentation(batch: RunCompletionBatch): { content: string; presentation?: RunCompletionPresentation | { format: "completion_yaml_core_v1"; content_sha256: string } } {
|
|
879
2118
|
const safe = parseBatch(batch);
|
|
2119
|
+
if (safe.presentation_format !== "ax_records" && safe.presentation_format !== "ax_records_v3") return { content: projectCompletionBatchMessage(safe).message };
|
|
2120
|
+
const result = safe.result_layout !== undefined ? projectCompletionRecords(safe) : projectRetainedRecordCompletionBatch(safe);
|
|
2121
|
+
if (safe.result_layout === "completion_yaml_core_v1") return { content: result.message, presentation: Object.freeze({
|
|
2122
|
+
format: "completion_yaml_core_v1", content_sha256: createHash("sha256").update(result.message).digest("hex"),
|
|
2123
|
+
}) };
|
|
2124
|
+
return { content: result.message, presentation: Object.freeze({ format: isFrontmatterCompletionLayout(safe.result_layout) ? "completion_frontmatter_v1" : safe.presentation_format, content_sha256: createHash("sha256").update(result.message).digest("hex"),
|
|
2125
|
+
...("blocks" in result ? { blocks: result.blocks as readonly CompletionBlock[] } : {}),
|
|
2126
|
+
compact_ranges: Object.freeze(result.compact_ranges.map(range => Object.freeze(range))) }) };
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
function completionRecordDetailsGap(value: string, single: boolean, terminal: boolean): boolean {
|
|
2130
|
+
let rest = value;
|
|
2131
|
+
while (rest !== (terminal ? "" : "\n")) {
|
|
2132
|
+
const opening = /^\n( *)- (technical|run_files|result_inventory):\n( *)(`{3,})text\n/u.exec(rest);
|
|
2133
|
+
if (!opening) return false;
|
|
2134
|
+
const [, indent, key, padding, fence] = opening;
|
|
2135
|
+
if (indent !== (key === "result_inventory" || single ? "" : " ") || padding !== indent + " ") return false;
|
|
2136
|
+
const ending = `\n${padding}${fence}`, end = rest.indexOf(ending, opening[0].length);
|
|
2137
|
+
if (end < 0) return false;
|
|
2138
|
+
const lines = rest.slice(opening[0].length, end).split("\n");
|
|
2139
|
+
if (lines.some(line => !line.startsWith(padding!))) return false;
|
|
2140
|
+
try {
|
|
2141
|
+
const data: unknown = JSON.parse(lines.map(line => line.slice(padding!.length)).join("\n"));
|
|
2142
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return false;
|
|
2143
|
+
} catch { return false; }
|
|
2144
|
+
rest = rest.slice(end + ending.length);
|
|
2145
|
+
}
|
|
2146
|
+
return true;
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
/** Display consistency only, never provenance authentication or a new read/action capability. */
|
|
2150
|
+
function readRunCompletionCompactText(content: string, value: unknown): string | undefined {
|
|
2151
|
+
try {
|
|
2152
|
+
if (Buffer.byteLength(content, "utf8") > Limits.RUN_COMPLETION_MODEL_MAX_BYTES) return undefined;
|
|
2153
|
+
const record = Object.getOwnPropertyDescriptors(value as object);
|
|
2154
|
+
if (Object.keys(record).sort().join() !== "compact_ranges,content_sha256,format" || Object.values(record).some(field => !Object.hasOwn(field, "value"))) return undefined;
|
|
2155
|
+
const header = completionRecordHeader(content), records = header ? undefined : readRunCompletionRecordMarker(content);
|
|
2156
|
+
if ((!header && !records) || record.format!.value !== (header?.format ?? "ax_records") || record.content_sha256!.value !== createHash("sha256").update(content).digest("hex")) return undefined;
|
|
2157
|
+
const flat = !!records && (records.single || !!records.headers);
|
|
2158
|
+
const indent = records ? (flat ? " " : " ") : header!.single ? " " : " ";
|
|
2159
|
+
const ranges = record.compact_ranges!.value;
|
|
2160
|
+
if (!Array.isArray(ranges) || ranges.length === 0 || ranges.length > content.length || Object.keys(ranges).length !== ranges.length) return undefined;
|
|
2161
|
+
const text: string[] = []; let previous = 0, fence: string | undefined;
|
|
2162
|
+
const boundary = (index: number) => !(index > 0 && index < content.length && /[\uD800-\uDBFF]/u.test(content[index - 1]!) && /[\uDC00-\uDFFF]/u.test(content[index]!))
|
|
2163
|
+
&& !(content[index - 1] === "\r" && content[index] === "\n");
|
|
2164
|
+
for (let index = 0; index < ranges.length; index++) {
|
|
2165
|
+
const descriptor = Object.getOwnPropertyDescriptor(ranges, String(index));
|
|
2166
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value")) return undefined;
|
|
2167
|
+
const range = descriptor.value;
|
|
2168
|
+
if (!Array.isArray(range) || range.length !== 2 || Object.keys(range).length !== 2) return undefined;
|
|
2169
|
+
const start = Object.getOwnPropertyDescriptor(range, "0"), end = Object.getOwnPropertyDescriptor(range, "1");
|
|
2170
|
+
if (!start || !end || !Object.hasOwn(start, "value") || !Object.hasOwn(end, "value")) return undefined;
|
|
2171
|
+
const a = start.value, b = end.value;
|
|
2172
|
+
if (!Number.isSafeInteger(a) || !Number.isSafeInteger(b) || a < previous || b <= a || b > content.length || !boundary(a) || !boundary(b)) return undefined;
|
|
2173
|
+
// Only compiler framing/fences and whole technical/reference blocks may disappear. Authored values stay padded inside ranges.
|
|
2174
|
+
const gap = content.slice(previous, a);
|
|
2175
|
+
if (index === 0) { if (a !== (header?.single ? header.length : 0)) return undefined; }
|
|
2176
|
+
else if (records) {
|
|
2177
|
+
let remaining = gap;
|
|
2178
|
+
if (fence) {
|
|
2179
|
+
const closing = `\n${indent}${fence}`;
|
|
2180
|
+
if (!remaining.startsWith(closing)) return undefined;
|
|
2181
|
+
remaining = remaining.slice(closing.length); fence = undefined;
|
|
2182
|
+
}
|
|
2183
|
+
const opening = (flat ? /^\n (`{3,})text\n$/u : /^\n (`{3,})text\n$/u).exec(remaining);
|
|
2184
|
+
if (opening) {
|
|
2185
|
+
const preview = flat ? /(?:^|\n)- (?:preview|result):(?:\n …)?$/u : /(?:^|\n) - preview:(?:\n …)?$/u;
|
|
2186
|
+
if (!preview.test(content.slice(0, previous))) return undefined;
|
|
2187
|
+
fence = opening[1]!;
|
|
2188
|
+
} else if (!completionRecordDetailsGap(remaining, flat, false)) return undefined;
|
|
2189
|
+
} else if (fence) {
|
|
2190
|
+
if (gap !== `\n${indent}${fence}\n`) return undefined;
|
|
2191
|
+
fence = undefined;
|
|
2192
|
+
} else if (gap !== "\n" && (header!.single || !/^\n- batch: [^\r\n\u2028\u2029]{1,128}\n- presentation: ax_records(?:_v3)?\n$/u.test(gap))) {
|
|
2193
|
+
const opening = (header!.single ? /^\n (`{3,})text\n$/u : /^\n (`{3,})text\n$/u).exec(gap);
|
|
2194
|
+
const preview = header!.single ? /(?:^|\n)- preview:(?:\n …)?$/u : /(?:^|\n) - preview:(?:\n …)?$/u;
|
|
2195
|
+
if (!opening || !preview.test(content.slice(0, previous))) return undefined;
|
|
2196
|
+
fence = opening[1]!;
|
|
2197
|
+
}
|
|
2198
|
+
const selected = content.slice(a, b);
|
|
2199
|
+
if (fence && [...selected.matchAll(/`+/gu)].some(run => run[0].length >= fence!.length)) return undefined;
|
|
2200
|
+
text.push(selected); previous = b;
|
|
2201
|
+
}
|
|
2202
|
+
if (records) {
|
|
2203
|
+
let remaining = content.slice(previous, records.start);
|
|
2204
|
+
if (fence) {
|
|
2205
|
+
const closing = `\n${indent}${fence}`;
|
|
2206
|
+
if (!remaining.startsWith(closing)) return undefined;
|
|
2207
|
+
remaining = remaining.slice(closing.length);
|
|
2208
|
+
}
|
|
2209
|
+
if (!completionRecordDetailsGap(remaining, flat, true)) return undefined;
|
|
2210
|
+
} else {
|
|
2211
|
+
const tail = content.slice(previous);
|
|
2212
|
+
if (fence ? !tail.startsWith(`\n${indent}${fence}\n- technical:\n`) : !tail.startsWith("\n- technical:\n")) return undefined;
|
|
2213
|
+
}
|
|
2214
|
+
return text.join("\n");
|
|
2215
|
+
} catch { return undefined; }
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
function ownCompletionData(value: unknown): unknown {
|
|
2219
|
+
if (value === null || typeof value !== "object") return value;
|
|
2220
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
2221
|
+
if (Object.values(descriptors).some(field => !Object.hasOwn(field, "value"))) throw new Error("Accessor presentation data");
|
|
2222
|
+
if (Array.isArray(value)) {
|
|
2223
|
+
if (Object.keys(descriptors).length !== value.length + 1) throw new Error("Sparse presentation data");
|
|
2224
|
+
return Array.from({ length: value.length }, (_, index) => {
|
|
2225
|
+
if (!descriptors[String(index)]) throw new Error("Sparse presentation data");
|
|
2226
|
+
return ownCompletionData(descriptors[String(index)]!.value);
|
|
2227
|
+
});
|
|
2228
|
+
}
|
|
2229
|
+
return Object.fromEntries(Object.entries(descriptors).map(([key, field]) => [key, ownCompletionData(field.value)]));
|
|
2230
|
+
}
|
|
2231
|
+
|
|
2232
|
+
export interface RunCompletionPresentationView {
|
|
2233
|
+
text: string;
|
|
2234
|
+
compact: boolean;
|
|
2235
|
+
single?: true;
|
|
2236
|
+
records?: true;
|
|
2237
|
+
blocks?: readonly CompletionBlock[];
|
|
2238
|
+
yaml?: true;
|
|
2239
|
+
segments?: readonly Readonly<{ kind: "header" | "body" | "marker" | "service" | "separator"; text: string }>[];
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2242
|
+
/** Invalid/missing hints reveal all content; old captures keep their existing host renderer. */
|
|
2243
|
+
export function readRunCompletionPresentation(content: string, value: unknown): RunCompletionPresentationView | undefined {
|
|
2244
|
+
const core = readCompletionYamlCarrier(content);
|
|
2245
|
+
let declaredCore = false;
|
|
2246
|
+
if (value !== null && typeof value === "object" && !utilTypes.isProxy(value)) {
|
|
2247
|
+
try { declaredCore = Object.getOwnPropertyDescriptor(value, "format")?.value === completionYamlCodec; } catch { /* Not own data. */ }
|
|
2248
|
+
}
|
|
2249
|
+
if (core.state === "available" || declaredCore) {
|
|
2250
|
+
const full: RunCompletionPresentationView = { text: content, compact: false, records: true, yaml: true };
|
|
2251
|
+
const hint = copyCompletionYamlData(value);
|
|
2252
|
+
if (core.state !== "available" || !completionYamlMapping(hint) ||
|
|
2253
|
+
!completionYamlKeys(hint, ["format", "content_sha256"]) || hint.format !== completionYamlCodec ||
|
|
2254
|
+
hint.content_sha256 !== createHash("sha256").update(content).digest("hex")) return full;
|
|
2255
|
+
const segments: NonNullable<RunCompletionPresentationView["segments"]>[number][] = [];
|
|
2256
|
+
for (const [index, record] of core.packet.records.entries()) {
|
|
2257
|
+
if (index) segments.push({ kind: "separator", text: "\n" });
|
|
2258
|
+
const metadata = Object.fromEntries(Object.entries(record.metadata).filter(([key]) => key !== "references" && key !== "run_files"));
|
|
2259
|
+
const header = renderYamlMetadata(metadata, completionYamlMaxBytes);
|
|
2260
|
+
if (header.state !== "available") return full;
|
|
2261
|
+
segments.push({ kind: "header", text: record.parts.length ? `---\n${header.text}---` : header.text.replace(/\n$/u, "") });
|
|
2262
|
+
for (const part of record.parts) segments.push("body" in part ? { kind: "body", text: part.body } : { kind: "marker", text: part.marker });
|
|
2263
|
+
}
|
|
2264
|
+
if (core.packet.service) {
|
|
2265
|
+
const service = renderYamlMetadata(core.packet.service, completionYamlMaxBytes);
|
|
2266
|
+
if (service.state !== "available") return full;
|
|
2267
|
+
if (segments.length) segments.push({ kind: "separator", text: "\n" });
|
|
2268
|
+
segments.push({ kind: "service", text: service.text.replace(/\n$/u, "") });
|
|
2269
|
+
}
|
|
2270
|
+
return { text: segments.map(segment => segment.text).join("\n"), compact: true, records: true, yaml: true,
|
|
2271
|
+
segments: Object.freeze(segments.map(segment => Object.freeze(segment))) };
|
|
2272
|
+
}
|
|
2273
|
+
const fresh = readFrontmatterFrame(content);
|
|
2274
|
+
let declaredFormat: unknown;
|
|
2275
|
+
try { declaredFormat = value && typeof value === "object" ? Object.getOwnPropertyDescriptor(value, "format")?.value : undefined; } catch { /* Unreadable hints fall back to full content. */ }
|
|
2276
|
+
if (fresh || declaredFormat === "completion_frontmatter_v1") {
|
|
2277
|
+
try {
|
|
2278
|
+
const hint = ownCompletionData(value) as RunCompletionPresentation;
|
|
2279
|
+
if (!fresh || !hint || Object.keys(hint).sort().join() !== "blocks,compact_ranges,content_sha256,format" || hint.format !== "completion_frontmatter_v1" || hint.content_sha256 !== createHash("sha256").update(content).digest("hex") || JSON.stringify(hint.blocks) !== JSON.stringify(fresh.blocks) || JSON.stringify(hint.compact_ranges) !== JSON.stringify(fresh.blocks.map(([, a, b]) => [a, b]))) throw new Error("Invalid completion presentation");
|
|
2280
|
+
return { text: fresh.blocks.map(([, a, b]) => content.slice(a, b)).join("\n"), compact: true, records: true, blocks: Object.freeze(fresh.blocks.map(block => Object.freeze(block))) };
|
|
2281
|
+
} catch { return { text: content, compact: false, records: true }; }
|
|
2282
|
+
}
|
|
2283
|
+
const header = completionRecordHeader(content), records = header ? undefined : readRunCompletionRecordMarker(content);
|
|
2284
|
+
let declared = false;
|
|
2285
|
+
try { const format = Object.getOwnPropertyDescriptor(value as object, "format")?.value; declared = format === "ax_records" || format === "ax_records_v3"; } catch { /* absent/unreadable UI hint */ }
|
|
2286
|
+
if (!header && !records && !declared) return undefined;
|
|
2287
|
+
const text = header || records ? readRunCompletionCompactText(content, value) : undefined;
|
|
2288
|
+
return { text: text ?? content, compact: text !== undefined, ...((header?.single || records?.single) ? { single: true as const } : {}),
|
|
2289
|
+
...(records ? { records: true as const } : {}) };
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
/** Retained draft codecs only. Journals store snapshots, not full text: replay must reproduce these exact bytes. */
|
|
2293
|
+
function projectRetainedCompletionBatchMessage(safe: RunCompletionBatch): { message: string; membersShown: number } {
|
|
2294
|
+
const batchLegacyBasis = safe.presentation_format === "ax_records_v2";
|
|
2295
|
+
const terminalTimes = safe.members.map(member => member.terminal_at).sort();
|
|
2296
|
+
const statuses = (["done", "failed", "killed", "exited"] as const).flatMap(status => {
|
|
2297
|
+
const count = safe.members.filter(member => member.status === status).length;
|
|
2298
|
+
return count ? [formatMarkdownField(batchLegacyBasis ? `${status}_count` : status, String(count))] : [];
|
|
2299
|
+
});
|
|
2300
|
+
const header = [formatMarkdownField("completions", String(safe.members.length)), formatMarkdownField("batch", safe.batch_id),
|
|
2301
|
+
formatMarkdownField("window_start", terminalTimes[0]), formatMarkdownField("window_end", terminalTimes.at(-1)!),
|
|
2302
|
+
...(batchLegacyBasis ? [...statuses, formatMarkdownField("legacy_basis", "All legacy summary/output fields: original completeness/privacy unproven."),
|
|
2303
|
+
formatMarkdownField("legacy_upstream", "[omitted: unknown]")] : [formatMarkdownField("statuses", statuses.join("\n"))]),
|
|
2304
|
+
...(safe.navigation_handoff ? [formatMarkdownField("file_investigation", "optional; current session"), formatInspectNextAction("runtime", "runs", true)] : []),
|
|
2305
|
+
...(safe.inventory_source ? [formatMarkdownField("member_inventory", formatLiteralBlock(JSON.stringify(safe.inventory_source, null, 2)))] : [])];
|
|
2306
|
+
const byIdentity = new Map(safe.members.map(member => [memberIdentity(member), member]));
|
|
2307
|
+
const assemble = (rows: string[], references: string[]): string => {
|
|
2308
|
+
const omitted = safe.members.length - rows.length;
|
|
2309
|
+
return [...header, ...(safe.members.length === 1 ? rows : rows.length ? [formatMarkdownField("members", rows.map(row => formatMarkdownField("member", row)).join("\n"))] : []),
|
|
2310
|
+
...(omitted ? [formatMarkdownField("members_omitted", String(omitted)),
|
|
2311
|
+
...(!safe.inventory_source ? [formatMarkdownField("inventory_warning", "No inventory reference is retained in this packet.")] : [])] : []),
|
|
2312
|
+
...(safe.file_navigation === undefined ? [] : [formatMarkdownField("run_files", [
|
|
2313
|
+
formatMarkdownField("shown", String(references.length)), formatMarkdownField("omitted", String(safe.members.length - references.length)),
|
|
2314
|
+
formatMarkdownField("total", String(safe.members.length)),
|
|
2315
|
+
formatMarkdownField("scope", "Captured completion members only; mutable locations, not body/privacy/terminal proof."),
|
|
2316
|
+
...references.map(row => formatMarkdownField("member", row)),
|
|
2317
|
+
].join("\n"))])].join("\n");
|
|
2318
|
+
};
|
|
2319
|
+
const rows: string[] = [];
|
|
2320
|
+
for (const member of orderRunCompletionMembers(safe.members).slice(0, Limits.RUN_DELIVERY_MODEL_MAX_MEMBERS)) {
|
|
2321
|
+
const row = formatRetainedCompletionMember(member, byIdentity, batchLegacyBasis);
|
|
2322
|
+
if (Buffer.byteLength(assemble([...rows, row], []), "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) break;
|
|
2323
|
+
rows.push(row);
|
|
2324
|
+
}
|
|
2325
|
+
const references: string[] = [];
|
|
2326
|
+
for (const item of safe.file_navigation ?? []) {
|
|
2327
|
+
const member = safe.members[item.member_index];
|
|
2328
|
+
const row = formatLiteralBlock(JSON.stringify({ run: member.run, run_instance_id: member.run_instance_id, locations: item.locations }));
|
|
2329
|
+
if (Buffer.byteLength(assemble(rows, [...references, row]), "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) break;
|
|
2330
|
+
references.push(row);
|
|
2331
|
+
}
|
|
2332
|
+
const message = assemble(rows, references);
|
|
2333
|
+
if (Buffer.byteLength(message, "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) throw new Error("Run completion batch header exceeds the model byte limit");
|
|
2334
|
+
return { message, membersShown: rows.length };
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
/** Format one immutable batch for a model-bound custom message, without source reads or phase changes. */
|
|
2338
|
+
export function formatRunCompletionBatchMessage(batch: RunCompletionBatch): string {
|
|
2339
|
+
return projectCompletionBatchMessage(parseBatch(batch)).message;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
function projectCompletionBatchMessage(safe: RunCompletionBatch): { message: string; membersShown: number } {
|
|
2343
|
+
if (safe.result_layout !== undefined) return projectCompletionRecords(safe);
|
|
2344
|
+
if (safe.presentation_format === "ax_records" || safe.presentation_format === "ax_records_v3") return projectRetainedRecordCompletionBatch(safe);
|
|
2345
|
+
if (safe.presentation_format !== undefined) return projectRetainedCompletionBatchMessage(safe);
|
|
880
2346
|
const terminalTimes = safe.members.map((member) => member.terminal_at).sort();
|
|
881
2347
|
const header = [
|
|
882
2348
|
`Actor completions: ${safe.members.length}`,
|
|
883
2349
|
`Batch: \`${compactModelText(safe.batch_id, 128)}\``,
|
|
884
2350
|
`Window: \`${terminalTimes[0]}\` → \`${terminalTimes.at(-1)}\``,
|
|
885
2351
|
`Statuses: \`${formatStatusCounts(safe.members)}\``,
|
|
2352
|
+
...(safe.navigation_handoff ? [formatRunFileNavigationHandoff()] : []),
|
|
2353
|
+
...(safe.inventory_source ? ["Complete captured member inventory retained at capture:", formatLiteralBlock(JSON.stringify(safe.inventory_source, null, 2))] : []),
|
|
886
2354
|
];
|
|
2355
|
+
const overflow = (omitted: number) => omitted > 0 ? [`… ${omitted} more completion(s) retained in batch.${safe.inventory_source ? "" : " No inventory reference is retained in this packet."}`] : [];
|
|
887
2356
|
const byIdentity = new Map(safe.members.map((member) => [
|
|
888
2357
|
memberIdentity(member),
|
|
889
2358
|
member,
|
|
890
2359
|
]));
|
|
891
|
-
const
|
|
2360
|
+
const navigation = safe.file_navigation;
|
|
2361
|
+
const navigationCounts = (shown: number) => navigation === undefined ? [] :
|
|
2362
|
+
[`Run files (captured locations; mutable): shown=${shown} omitted=${safe.members.length - shown} total=${safe.members.length}; captured completion members only; not body/privacy/terminal proof.`];
|
|
2363
|
+
const candidateRows = orderRunCompletionMembers(safe.members)
|
|
892
2364
|
.slice(0, Limits.RUN_DELIVERY_MODEL_MAX_MEMBERS)
|
|
893
|
-
.map((member) => formatCompletionMember(member, byIdentity));
|
|
2365
|
+
.map((member) => formatCompletionMember(member, byIdentity, safe.excerpt_format === "bracketed_omissions_v1"));
|
|
894
2366
|
const rows: string[] = [];
|
|
895
2367
|
for (const row of candidateRows) {
|
|
896
2368
|
const omitted = safe.members.length - rows.length - 1;
|
|
@@ -898,19 +2370,235 @@ export function formatRunCompletionBatchMessage(batch: RunCompletionBatch): stri
|
|
|
898
2370
|
...header,
|
|
899
2371
|
...rows,
|
|
900
2372
|
row,
|
|
901
|
-
...(omitted
|
|
2373
|
+
...overflow(omitted),
|
|
2374
|
+
...navigationCounts(0),
|
|
902
2375
|
].join("\n");
|
|
903
2376
|
if (Buffer.byteLength(candidate, "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) break;
|
|
904
2377
|
rows.push(row);
|
|
905
2378
|
}
|
|
906
2379
|
const omitted = safe.members.length - rows.length;
|
|
2380
|
+
const references: string[] = [];
|
|
2381
|
+
for (const item of navigation ?? []) {
|
|
2382
|
+
const member = safe.members[item.member_index];
|
|
2383
|
+
const row = formatLiteralBlock(JSON.stringify({ run: member.run, run_instance_id: member.run_instance_id, locations: item.locations }));
|
|
2384
|
+
const candidate = [...header, ...rows, ...overflow(omitted), ...navigationCounts(references.length + 1), ...references, row].join("\n");
|
|
2385
|
+
if (Buffer.byteLength(candidate, "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) break;
|
|
2386
|
+
references.push(row);
|
|
2387
|
+
}
|
|
907
2388
|
const message = [
|
|
908
2389
|
...header,
|
|
909
2390
|
...rows,
|
|
910
|
-
...(omitted
|
|
2391
|
+
...overflow(omitted),
|
|
2392
|
+
...navigationCounts(references.length),
|
|
2393
|
+
...references,
|
|
911
2394
|
].join("\n");
|
|
912
2395
|
if (Buffer.byteLength(message, "utf8") > Limits.RUN_DELIVERY_MODEL_MAX_BYTES) {
|
|
913
2396
|
throw new Error("Run completion batch header exceeds the model byte limit");
|
|
914
2397
|
}
|
|
915
|
-
return message;
|
|
2398
|
+
return { message, membersShown: rows.length };
|
|
2399
|
+
}
|
|
2400
|
+
|
|
2401
|
+
// --- Unadmitted pure YAML carrier prototype ---
|
|
2402
|
+
|
|
2403
|
+
export type CompletionYamlData = null | boolean | number | string | CompletionYamlData[] | { [key: string]: CompletionYamlData };
|
|
2404
|
+
export type CompletionYamlPart = { body: string } | { marker: string };
|
|
2405
|
+
export interface CompletionYamlPacket {
|
|
2406
|
+
batchId: string;
|
|
2407
|
+
records: { metadata: Record<string, CompletionYamlData>; parts: CompletionYamlPart[] }[];
|
|
2408
|
+
service?: Record<string, CompletionYamlData>;
|
|
2409
|
+
}
|
|
2410
|
+
export interface CompletionYamlBlock {
|
|
2411
|
+
kind: "header" | "body" | "marker" | "separator";
|
|
2412
|
+
start: number;
|
|
2413
|
+
end: number;
|
|
2414
|
+
record: number;
|
|
2415
|
+
part?: number;
|
|
2416
|
+
}
|
|
2417
|
+
type CompletionYamlUnavailable = { state: "unavailable" };
|
|
2418
|
+
const completionYamlCodec = "completion_yaml_core_v1";
|
|
2419
|
+
const completionYamlServiceKey = "_completion";
|
|
2420
|
+
const completionYamlMaxBytes = 49152;
|
|
2421
|
+
|
|
2422
|
+
// Snapshot only own data: never coerce or invoke caller accessors/serialization hooks.
|
|
2423
|
+
function copyCompletionYamlData(input: unknown): CompletionYamlData | undefined {
|
|
2424
|
+
let remaining = completionYamlMaxBytes;
|
|
2425
|
+
const active = new Set<object>();
|
|
2426
|
+
const copy = (value: unknown, depth: number): CompletionYamlData | undefined => {
|
|
2427
|
+
if (--remaining < 0 || depth > 64) return undefined;
|
|
2428
|
+
if (value === null || typeof value === "boolean") return value;
|
|
2429
|
+
if (typeof value === "number") return Number.isFinite(value) && !Object.is(value, -0) ? value : undefined;
|
|
2430
|
+
if (typeof value === "string") {
|
|
2431
|
+
remaining -= Buffer.byteLength(value, "utf8");
|
|
2432
|
+
return remaining >= 0 && value.isWellFormed() ? value : undefined;
|
|
2433
|
+
}
|
|
2434
|
+
if (!value || typeof value !== "object" || utilTypes.isProxy(value) || active.has(value)) return undefined;
|
|
2435
|
+
const array = Array.isArray(value), prototype = Object.getPrototypeOf(value);
|
|
2436
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) return undefined;
|
|
2437
|
+
const output: CompletionYamlData[] | Record<string, CompletionYamlData> = array ? [] : {};
|
|
2438
|
+
const length = array ? Object.getOwnPropertyDescriptor(value, "length")?.value : undefined;
|
|
2439
|
+
if (array && (!Number.isSafeInteger(length) || length < 0 || length > remaining)) return undefined;
|
|
2440
|
+
active.add(value);
|
|
2441
|
+
let count = 0;
|
|
2442
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
2443
|
+
if (array && key === "length") continue;
|
|
2444
|
+
if (typeof key !== "string" || !key.isWellFormed() || (array && key !== String(count))) return undefined;
|
|
2445
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
2446
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value") || !descriptor.enumerable) return undefined;
|
|
2447
|
+
remaining -= Buffer.byteLength(key, "utf8");
|
|
2448
|
+
const child = copy(descriptor.value, depth + 1);
|
|
2449
|
+
if (child === undefined) return undefined;
|
|
2450
|
+
Object.defineProperty(output, key, { value: child, enumerable: true, writable: true, configurable: true });
|
|
2451
|
+
count++;
|
|
2452
|
+
}
|
|
2453
|
+
active.delete(value);
|
|
2454
|
+
return array && count !== length ? undefined : output;
|
|
2455
|
+
};
|
|
2456
|
+
try { return copy(input, 0); } catch { return undefined; }
|
|
2457
|
+
}
|
|
2458
|
+
|
|
2459
|
+
function completionYamlMapping(value: unknown): value is Record<string, CompletionYamlData> {
|
|
2460
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
function completionYamlKeys(value: Record<string, CompletionYamlData>, required: string[], optional: string[] = []): boolean {
|
|
2464
|
+
return required.every(key => Object.hasOwn(value, key)) && Object.keys(value).every(key => required.includes(key) || optional.includes(key));
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
/**
|
|
2468
|
+
* Unadmitted data-only prototype, not source/privacy/identity/acceptance authority.
|
|
2469
|
+
* Grammar: canonical YAML headers, no physical blank lines. Bare headers end LF;
|
|
2470
|
+
* another record adds LF. Body headers are --- LF / YAML / --- LF then raw parts;
|
|
2471
|
+
* another record adds LF LF, independently of authored trailing CR/LF. Parts are
|
|
2472
|
+
* ordered single-key body/marker UTF-16 lengths, never offsets. Only the first
|
|
2473
|
+
* _completion carries codec/batch_id/service. Absent body is DTO parts:[] (no
|
|
2474
|
+
* wire parts); selected empty body is body:0 and has a closed header. Zero rows
|
|
2475
|
+
* is one bare service-only header requiring nonempty selected service facts.
|
|
2476
|
+
* No clipping, fair fitting, source reads, admission or production callers.
|
|
2477
|
+
*/
|
|
2478
|
+
export function encodeCompletionYamlCarrier(input: unknown): { state: "available"; text: string } | CompletionYamlUnavailable {
|
|
2479
|
+
const unavailable: CompletionYamlUnavailable = { state: "unavailable" };
|
|
2480
|
+
const packet = copyCompletionYamlData(input);
|
|
2481
|
+
if (!completionYamlMapping(packet) || !completionYamlKeys(packet, ["batchId", "records"], ["service"]) ||
|
|
2482
|
+
typeof packet.batchId !== "string" || !packet.batchId.length || !Array.isArray(packet.records) || packet.records.length > 64 ||
|
|
2483
|
+
(Object.hasOwn(packet, "service") && (!completionYamlMapping(packet.service) || !Object.keys(packet.service).length)) ||
|
|
2484
|
+
(!packet.records.length && !Object.hasOwn(packet, "service"))) return unavailable;
|
|
2485
|
+
const rows: string[] = [];
|
|
2486
|
+
for (let index = 0; index < Math.max(1, packet.records.length); index++) {
|
|
2487
|
+
const row: CompletionYamlData | undefined = packet.records[index];
|
|
2488
|
+
const header: Record<string, CompletionYamlData> = {};
|
|
2489
|
+
const service: Record<string, CompletionYamlData> = {};
|
|
2490
|
+
if (index === 0) {
|
|
2491
|
+
service.codec = completionYamlCodec;
|
|
2492
|
+
service.batch_id = packet.batchId;
|
|
2493
|
+
if (Object.hasOwn(packet, "service")) service.service = packet.service!;
|
|
2494
|
+
}
|
|
2495
|
+
const raw: string[] = [], lengths: CompletionYamlData[] = [];
|
|
2496
|
+
if (packet.records.length) {
|
|
2497
|
+
if (!completionYamlMapping(row) || !completionYamlKeys(row, ["metadata", "parts"]) ||
|
|
2498
|
+
!completionYamlMapping(row.metadata) || !Array.isArray(row.parts)) return unavailable;
|
|
2499
|
+
const meta = row.metadata;
|
|
2500
|
+
if (!(meta.index === "?" || (typeof meta.index === "number" && Number.isSafeInteger(meta.index) && meta.index >= 0)) ||
|
|
2501
|
+
typeof meta.run !== "string" || !meta.run.length || typeof meta.state !== "string" || !meta.state.length ||
|
|
2502
|
+
(Object.hasOwn(meta, "kind") && meta.kind !== "primary" && meta.kind !== "diagnostic") ||
|
|
2503
|
+
Object.hasOwn(meta, completionYamlServiceKey) ||
|
|
2504
|
+
// JS enumerates array-index keys before semantic core fields; do not silently reorder the header.
|
|
2505
|
+
Object.keys(meta).some(key => /^(0|[1-9]\d*)$/u.test(key) && Number(key) < 4294967295)) return unavailable;
|
|
2506
|
+
for (const key of ["index", "run", "state", ...(Object.hasOwn(meta, "kind") ? ["kind"] : []),
|
|
2507
|
+
...Object.keys(meta).filter(key => !["index", "run", "state", "kind"].includes(key))]) {
|
|
2508
|
+
Object.defineProperty(header, key, { value: meta[key]!, enumerable: true });
|
|
2509
|
+
}
|
|
2510
|
+
let hasBody = false;
|
|
2511
|
+
for (const part of row.parts) {
|
|
2512
|
+
if (!completionYamlMapping(part) || Object.keys(part).length !== 1) return unavailable;
|
|
2513
|
+
const key = Object.keys(part)[0]!;
|
|
2514
|
+
if ((key !== "body" && key !== "marker") || typeof part[key] !== "string") return unavailable;
|
|
2515
|
+
if (key === "body") hasBody = true;
|
|
2516
|
+
lengths.push({ [key]: part[key].length });
|
|
2517
|
+
raw.push(part[key]);
|
|
2518
|
+
}
|
|
2519
|
+
// An explicit selected empty body is real; empty markers alone cannot justify a wrapper.
|
|
2520
|
+
if (lengths.length && !hasBody && raw.every(text => text.length === 0)) return unavailable;
|
|
2521
|
+
if (lengths.length) service.parts = lengths;
|
|
2522
|
+
}
|
|
2523
|
+
if (Object.keys(service).length) header[completionYamlServiceKey] = service;
|
|
2524
|
+
const rendered = renderYamlMetadata(header, completionYamlMaxBytes);
|
|
2525
|
+
if (rendered.state !== "available" || rendered.text.includes("\n\n")) return unavailable;
|
|
2526
|
+
const text = lengths.length ? `---\n${rendered.text}---\n${raw.join("")}` : rendered.text;
|
|
2527
|
+
rows.push(text + (index + 1 < packet.records.length ? (lengths.length ? "\n\n" : "\n") : ""));
|
|
2528
|
+
}
|
|
2529
|
+
const text = rows.join("");
|
|
2530
|
+
return Buffer.byteLength(text, "utf8") <= completionYamlMaxBytes ? { state: "available", text } : unavailable;
|
|
2531
|
+
}
|
|
2532
|
+
|
|
2533
|
+
/** Strict whole-carrier decoder. Derived ranges are consistency data, never authenticity. */
|
|
2534
|
+
export function readCompletionYamlCarrier(input: unknown):
|
|
2535
|
+
{ state: "available"; packet: CompletionYamlPacket; blocks: CompletionYamlBlock[] } | CompletionYamlUnavailable {
|
|
2536
|
+
const unavailable: CompletionYamlUnavailable = { state: "unavailable" };
|
|
2537
|
+
if (typeof input !== "string" || !input.isWellFormed() || !input.length || Buffer.byteLength(input, "utf8") > completionYamlMaxBytes) return unavailable;
|
|
2538
|
+
try {
|
|
2539
|
+
let cursor = 0;
|
|
2540
|
+
const packet: CompletionYamlPacket = { batchId: "", records: [] };
|
|
2541
|
+
const blocks: CompletionYamlBlock[] = [];
|
|
2542
|
+
while (cursor < input.length) {
|
|
2543
|
+
if (packet.records.length >= 64) return unavailable;
|
|
2544
|
+
const record = packet.records.length, start = cursor, framed = input.startsWith("---\n", cursor);
|
|
2545
|
+
let yaml: string;
|
|
2546
|
+
if (framed) {
|
|
2547
|
+
const close = input.indexOf("\n---\n", cursor + 4);
|
|
2548
|
+
if (close < 0) return unavailable;
|
|
2549
|
+
yaml = input.slice(cursor + 4, close + 1);
|
|
2550
|
+
cursor = close + 5;
|
|
2551
|
+
} else {
|
|
2552
|
+
const close = input.indexOf("\n\n", cursor);
|
|
2553
|
+
cursor = close < 0 ? input.length : close + 1;
|
|
2554
|
+
yaml = input.slice(start, cursor);
|
|
2555
|
+
}
|
|
2556
|
+
const document = parseDocument(yaml, { version: "1.2", schema: "core", uniqueKeys: true });
|
|
2557
|
+
if (document.errors.length || document.warnings.length) return unavailable;
|
|
2558
|
+
const header: unknown = document.toJS({ maxAliasCount: 0 });
|
|
2559
|
+
if (!completionYamlMapping(header)) return unavailable;
|
|
2560
|
+
const canonical = renderYamlMetadata(header, completionYamlMaxBytes);
|
|
2561
|
+
if (canonical.state !== "available" || canonical.text !== yaml) return unavailable;
|
|
2562
|
+
const service = header[completionYamlServiceKey];
|
|
2563
|
+
if (record === 0) {
|
|
2564
|
+
if (!completionYamlMapping(service) || !completionYamlKeys(service, ["codec", "batch_id"], ["parts", "service"]) ||
|
|
2565
|
+
service.codec !== completionYamlCodec || typeof service.batch_id !== "string" || !service.batch_id.length) return unavailable;
|
|
2566
|
+
packet.batchId = service.batch_id;
|
|
2567
|
+
if (Object.hasOwn(service, "service")) {
|
|
2568
|
+
if (!completionYamlMapping(service.service) || !Object.keys(service.service).length) return unavailable;
|
|
2569
|
+
packet.service = service.service;
|
|
2570
|
+
}
|
|
2571
|
+
} else if (service !== undefined && (!completionYamlMapping(service) || !completionYamlKeys(service, ["parts"]))) return unavailable;
|
|
2572
|
+
blocks.push({ kind: "header", start, end: cursor, record });
|
|
2573
|
+
delete header[completionYamlServiceKey];
|
|
2574
|
+
const lengths = completionYamlMapping(service) ? service.parts : undefined;
|
|
2575
|
+
if (framed ? !Array.isArray(lengths) || !lengths.length : lengths !== undefined) return unavailable;
|
|
2576
|
+
const parts: CompletionYamlPart[] = [];
|
|
2577
|
+
if (Array.isArray(lengths)) {
|
|
2578
|
+
for (const descriptor of lengths) {
|
|
2579
|
+
if (!completionYamlMapping(descriptor) || Object.keys(descriptor).length !== 1) return unavailable;
|
|
2580
|
+
const key = Object.keys(descriptor)[0]!;
|
|
2581
|
+
const length = descriptor[key];
|
|
2582
|
+
if ((key !== "body" && key !== "marker") || typeof length !== "number" || !Number.isSafeInteger(length) || length < 0 || length > input.length - cursor) return unavailable;
|
|
2583
|
+
const text = input.slice(cursor, cursor + length);
|
|
2584
|
+
if (!text.isWellFormed()) return unavailable;
|
|
2585
|
+
parts.push(key === "body" ? { body: text } : { marker: text });
|
|
2586
|
+
blocks.push({ kind: key, start: cursor, end: cursor + length, record, part: parts.length - 1 });
|
|
2587
|
+
cursor += length;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
if (!Object.keys(header).length) {
|
|
2591
|
+
if (record !== 0 || framed || !packet.service || cursor !== input.length) return unavailable;
|
|
2592
|
+
} else packet.records.push({ metadata: header, parts });
|
|
2593
|
+
if (cursor < input.length) {
|
|
2594
|
+
const gap = framed ? "\n\n" : "\n";
|
|
2595
|
+
if (!input.startsWith(gap, cursor)) return unavailable;
|
|
2596
|
+
blocks.push({ kind: "separator", start: cursor, end: cursor + gap.length, record });
|
|
2597
|
+
cursor += gap.length;
|
|
2598
|
+
if (cursor === input.length) return unavailable;
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
const encoded = encodeCompletionYamlCarrier(packet);
|
|
2602
|
+
return encoded.state === "available" && encoded.text === input ? { state: "available", packet, blocks } : unavailable;
|
|
2603
|
+
} catch { return unavailable; }
|
|
916
2604
|
}
|