@llblab/pi-kit 0.38.4 → 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 +5 -0
- package/README.md +1 -1
- 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/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 +2 -2
- 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,139 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Owns:
|
|
2
|
+
* Run retirement owner.
|
|
3
|
+
* Owns: lifecycle-serialized manual archive/prune, age-based cleanup and shared ownership/scope fences.
|
|
4
|
+
* Excludes: native closure proof, process actuation and ordinary non-Run temp cleanup.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
7
|
-
import { cpSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
8
|
-
import {
|
|
8
|
+
import { cpSync, lstatSync, mkdirSync, renameSync, rmSync } from "node:fs";
|
|
9
|
+
import { readdir } from "node:fs/promises";
|
|
10
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
9
11
|
|
|
10
12
|
import { acquireFileMutationLock, writeJsonAtomic, writeTextAtomic } from "./file-state.ts";
|
|
11
13
|
import * as Limits from "./limits.ts";
|
|
12
|
-
import { readJsonlFileResilient } from "./state-readers.ts";
|
|
14
|
+
import { readJsonFileResilient, readJsonlFileResilient } from "./state-readers.ts";
|
|
15
|
+
import { readRunContextIdentity, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES } from "./runs-context-index.ts";
|
|
16
|
+
import { readProcessLiveness } from "./runs-process.ts";
|
|
17
|
+
import { acquireStateStartLock, assertNoActiveRunState } from "./runs-start.ts";
|
|
13
18
|
import {
|
|
14
19
|
resolveArtifactManifest,
|
|
15
20
|
type RunArtifactDeclaration,
|
|
16
21
|
} from "./runs-artifacts.ts";
|
|
22
|
+
import { assertNoRetainedDescendantLaunchScope } from "./runs-descendant-launches.ts";
|
|
17
23
|
import { safeRunId } from "./runs-identity.ts";
|
|
24
|
+
import { assertNoRetainedNativeLaunchScope } from "./runs-native-launches.ts";
|
|
18
25
|
import { assertOwnedRunStateDirectory } from "./runs-ownership.ts";
|
|
19
26
|
|
|
20
27
|
export type RunRetentionAction = "archive" | "prune";
|
|
21
28
|
export type RunRetentionOutcome = "queued" | "handled" | "failed";
|
|
22
29
|
|
|
30
|
+
export const DEFAULT_RUN_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
31
|
+
const RETIREMENT_FAILURE_MESSAGE = "Run retirement failed; effects may already exist.";
|
|
32
|
+
|
|
33
|
+
export interface RunRetentionExpectation {
|
|
34
|
+
onLocked?(): void;
|
|
35
|
+
ownerId?: string;
|
|
36
|
+
runInstanceId?: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function assertRetirableDirectory(status: Record<string, unknown>): void {
|
|
40
|
+
const stateDir = String(status.state_dir);
|
|
41
|
+
assertOwnedRunStateDirectory(stateDir, String(status.run ?? basename(stateDir)));
|
|
42
|
+
assertNoRetainedDescendantLaunchScope(stateDir);
|
|
43
|
+
assertNoRetainedNativeLaunchScope(stateDir);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Manual API preserves its existing status/expected contract, not a native closure claim. */
|
|
47
|
+
export function retainRun(
|
|
48
|
+
stateDir: string,
|
|
49
|
+
action: RunRetentionAction,
|
|
50
|
+
expected: RunRetentionExpectation,
|
|
51
|
+
readStatus: (stateDir: string) => Record<string, unknown>,
|
|
52
|
+
options: { preserveArtifacts?: boolean } = {},
|
|
53
|
+
): Record<string, unknown> {
|
|
54
|
+
const release = acquireStateStartLock(stateDir);
|
|
55
|
+
let status: Record<string, unknown> | undefined;
|
|
56
|
+
let evidenceId: string | undefined;
|
|
57
|
+
const failures: unknown[] = [];
|
|
58
|
+
try {
|
|
59
|
+
expected.onLocked?.();
|
|
60
|
+
status = readStatus(stateDir);
|
|
61
|
+
if (expected.ownerId !== undefined && status.ownerId !== expected.ownerId) {
|
|
62
|
+
return { [`${action}d`]: false, reason: "ownership changed", status };
|
|
63
|
+
}
|
|
64
|
+
if (expected.runInstanceId !== undefined && status.run_instance_id !== expected.runInstanceId) {
|
|
65
|
+
return { [`${action}d`]: false, reason: "run generation changed", status };
|
|
66
|
+
}
|
|
67
|
+
if (status.status === "running") throw new Error("Only terminal runs can be archived or pruned.");
|
|
68
|
+
if (resolve(String(status.state_dir)) !== resolve(stateDir)) throw new Error("Run retirement state directory changed.");
|
|
69
|
+
evidenceId = appendRunRetentionEvidence(status, action, "queued");
|
|
70
|
+
const result = action === "archive" ? archiveTerminalRun(status) : pruneTerminalRun(status, options);
|
|
71
|
+
appendRunRetentionEvidence(status, action, "handled", { id: evidenceId, result });
|
|
72
|
+
return { ...result, retention_id: evidenceId };
|
|
73
|
+
} catch (error) {
|
|
74
|
+
failures.push(error);
|
|
75
|
+
if (status && evidenceId) {
|
|
76
|
+
let text: string;
|
|
77
|
+
try { text = String(error instanceof Error ? error.message : error); }
|
|
78
|
+
catch (formattingError) { text = "failure text unavailable"; failures.push(formattingError); }
|
|
79
|
+
try { appendRunRetentionEvidence(status, action, "failed", { error: text, id: evidenceId }); }
|
|
80
|
+
catch (recordingError) { failures.push(recordingError); }
|
|
81
|
+
}
|
|
82
|
+
} finally {
|
|
83
|
+
try { release(); }
|
|
84
|
+
catch (releaseError) {
|
|
85
|
+
if (failures.length) throw new AggregateError([...failures, releaseError], RETIREMENT_FAILURE_MESSAGE, { cause: failures[0] });
|
|
86
|
+
throw releaseError;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (failures.length === 1) throw failures[0];
|
|
90
|
+
throw new AggregateError(failures, RETIREMENT_FAILURE_MESSAGE, { cause: failures[0] });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Filesystem age selects candidates; it grants neither retirement nor native scope authority. */
|
|
94
|
+
export async function cleanupStaleRunEntries(
|
|
95
|
+
runsDir: string,
|
|
96
|
+
maxAgeMs = DEFAULT_RUN_MAX_AGE_MS,
|
|
97
|
+
now = Date.now(),
|
|
98
|
+
): Promise<number> {
|
|
99
|
+
let entries;
|
|
100
|
+
const root = resolve(runsDir);
|
|
101
|
+
try { entries = await readdir(root, { withFileTypes: true }); }
|
|
102
|
+
catch { return 0; }
|
|
103
|
+
let removed = 0;
|
|
104
|
+
const readJson = (path: string) => {
|
|
105
|
+
const reading = readJsonFileResilient<Record<string, unknown> | undefined>(path, undefined,
|
|
106
|
+
{ maxBytes: RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES, strictUtf8: true });
|
|
107
|
+
if (reading.diagnostics.length) throw new Error("Run retirement metadata is unavailable.");
|
|
108
|
+
return reading.value;
|
|
109
|
+
};
|
|
110
|
+
for (const entry of entries) {
|
|
111
|
+
if (!entry.isDirectory()) continue;
|
|
112
|
+
const stateDir = join(root, entry.name);
|
|
113
|
+
try {
|
|
114
|
+
const selected = lstatSync(stateDir);
|
|
115
|
+
if (now - Math.min(selected.birthtimeMs || selected.mtimeMs, selected.mtimeMs) <= maxAgeMs) continue;
|
|
116
|
+
const candidate = readRunContextIdentity(stateDir, readJson(join(stateDir, "run.json")));
|
|
117
|
+
if (candidate.state !== "available") continue;
|
|
118
|
+
const release = acquireStateStartLock(stateDir);
|
|
119
|
+
try {
|
|
120
|
+
if (!candidate.unchanged()) continue;
|
|
121
|
+
const current = lstatSync(stateDir);
|
|
122
|
+
if (now - Math.min(current.birthtimeMs || current.mtimeMs, current.mtimeMs) <= maxAgeMs) continue;
|
|
123
|
+
const meta = readJson(join(stateDir, "run.json"));
|
|
124
|
+
if (!meta || typeof meta.pid !== "number" || !candidate.unchanged()) continue;
|
|
125
|
+
assertNoActiveRunState(stateDir, readJson, "");
|
|
126
|
+
if (readProcessLiveness(meta.pid) !== "dead" || !candidate.unchanged()) continue;
|
|
127
|
+
pruneTerminalRun(meta);
|
|
128
|
+
removed++;
|
|
129
|
+
} finally { release(); }
|
|
130
|
+
} catch {
|
|
131
|
+
// Best-effort startup cleanup retains unavailable, replaced or still-fenced candidates.
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return removed;
|
|
135
|
+
}
|
|
136
|
+
|
|
23
137
|
export function appendRunRetentionEvidence(
|
|
24
138
|
status: Record<string, unknown>,
|
|
25
139
|
action: RunRetentionAction,
|
|
@@ -40,6 +154,7 @@ export function appendRunRetentionEvidence(
|
|
|
40
154
|
ts: new Date().toISOString(),
|
|
41
155
|
};
|
|
42
156
|
const release = acquireFileMutationLock(path);
|
|
157
|
+
let writeFailed = false, writeFailure: unknown;
|
|
43
158
|
try {
|
|
44
159
|
const records = [...readJsonlFileResilient<Record<string, unknown>>(path).records, record]
|
|
45
160
|
.slice(-Limits.RUN_RETENTION_MAX_RECORDS);
|
|
@@ -50,7 +165,16 @@ export function appendRunRetentionEvidence(
|
|
|
50
165
|
}
|
|
51
166
|
if (!records.includes(record)) throw new Error("Run retention record exceeds journal byte limit");
|
|
52
167
|
writeTextAtomic(path, content);
|
|
53
|
-
}
|
|
168
|
+
} catch (error) {
|
|
169
|
+
writeFailed = true; writeFailure = error;
|
|
170
|
+
throw error;
|
|
171
|
+
} finally {
|
|
172
|
+
try { release(); }
|
|
173
|
+
catch (releaseError) {
|
|
174
|
+
if (writeFailed) throw new AggregateError([writeFailure, releaseError], "Run retention journal append and release failed", { cause: writeFailure });
|
|
175
|
+
throw releaseError;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
54
178
|
return id;
|
|
55
179
|
}
|
|
56
180
|
|
|
@@ -81,7 +205,7 @@ export function archiveTerminalRun(
|
|
|
81
205
|
): Record<string, unknown> {
|
|
82
206
|
const stateDir = String(status.state_dir);
|
|
83
207
|
const run = String(status.run ?? basename(stateDir));
|
|
84
|
-
|
|
208
|
+
assertRetirableDirectory(status);
|
|
85
209
|
const archiveDir = archivePathFor(run, stateDir);
|
|
86
210
|
renameSync(stateDir, archiveDir);
|
|
87
211
|
mkdirSync(stateDir, { recursive: true });
|
|
@@ -104,10 +228,10 @@ export function pruneTerminalRun(
|
|
|
104
228
|
): Record<string, unknown> {
|
|
105
229
|
const stateDir = String(status.state_dir);
|
|
106
230
|
const run = String(status.run ?? basename(stateDir));
|
|
107
|
-
|
|
108
|
-
const manifest = resolveArtifactManifest(
|
|
231
|
+
assertRetirableDirectory(status);
|
|
232
|
+
const manifest = options.preserveArtifacts ? resolveArtifactManifest(
|
|
109
233
|
status.artifacts as Record<string, RunArtifactDeclaration> | undefined,
|
|
110
|
-
);
|
|
234
|
+
) : undefined;
|
|
111
235
|
const preserved: Record<string, string> = {};
|
|
112
236
|
if (options.preserveArtifacts && manifest) {
|
|
113
237
|
const preserveRoot = join(
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run session context: fixed privacy previews and generation-bound publication/selection.
|
|
3
|
+
* Owns: assistant-only projection, recorded-session preview admission, producer-bound preview/catalog/index commits and selected published reads.
|
|
4
|
+
* Excludes: private-role exposure, raw session export/discovery, primary-source completeness, native authority and multi-file transactions.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { lstatSync, mkdirSync, type BigIntStats } from "node:fs";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
9
|
+
import { writeTextAtomic } from "./file-state.ts";
|
|
10
|
+
import { DEFAULT_INSPECT_LINES, SESSION_EVIDENCE_TEXT_CHARS } from "./limits.ts";
|
|
11
|
+
import { createRunRecipeEvidenceRedactor, createRunRecipeBodyRedactor, createContextIdentityPrivacyCheck } from "./recipe-projection.ts";
|
|
12
|
+
import { readRunContextIdentity, readRunContextIndex, runContextExportPath, writeRunContextIndexUnderLock, RUN_CONTEXT_INDEX_FILE,
|
|
13
|
+
RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES, type RunContextEntry, type RunContextIndexIdentity, type RunContextIndexPublication } from "./runs-context-index.ts";
|
|
14
|
+
import { readRunExecutionReference, type RunExecutionReferenceReading } from "./runs-execution-evidence.ts";
|
|
15
|
+
import { readExecutionSessionText } from "./execution-files.ts";
|
|
16
|
+
import { readRunProducerCapturedMetadata, withRunProducerEvidence, type RunResultProducer } from "./runs-results.ts";
|
|
17
|
+
import { selectLastRecordedSessionBranch } from "./session-evidence.ts";
|
|
18
|
+
import { readJsonFileResilient, type StateReadDiagnostic } from "./state-readers.ts";
|
|
19
|
+
|
|
20
|
+
// --- Fixed privacy projection ---
|
|
21
|
+
|
|
22
|
+
export const SESSION_ASSISTANT_PREVIEW_MAX_BYTES = 64 * 1024;
|
|
23
|
+
export interface SessionAssistantPreviewMessage {
|
|
24
|
+
readonly ordinal: number;
|
|
25
|
+
readonly assistant_text: string;
|
|
26
|
+
readonly text_chars_observed: number;
|
|
27
|
+
readonly text_chars_shown: number;
|
|
28
|
+
readonly text_chars_omitted: number;
|
|
29
|
+
readonly preview_clipped: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface SessionAssistantPreview {
|
|
32
|
+
readonly content_scope: "assistant_visible_text_preview";
|
|
33
|
+
readonly source_scope: "last_recorded_branch";
|
|
34
|
+
readonly primary_text: "not_exported";
|
|
35
|
+
readonly order: "oldest_first";
|
|
36
|
+
readonly coverage: { readonly truncated: true; readonly redacted: true; readonly history_complete: false };
|
|
37
|
+
readonly text_limit_chars: number;
|
|
38
|
+
readonly messages_total: number;
|
|
39
|
+
readonly messages_shown: number;
|
|
40
|
+
readonly messages_omitted: number;
|
|
41
|
+
readonly messages: readonly Readonly<SessionAssistantPreviewMessage>[];
|
|
42
|
+
}
|
|
43
|
+
export type SessionAssistantPreviewReading =
|
|
44
|
+
| { readonly state: "available"; readonly preview: Readonly<SessionAssistantPreview> }
|
|
45
|
+
| { readonly state: "unavailable"; readonly reason: "session_invalid" | "preview_too_large" };
|
|
46
|
+
|
|
47
|
+
/** Preview clipping happens after origin/secret projection, never by slicing raw/private session fields. */
|
|
48
|
+
export function projectSessionAssistantPreview(source: string, recipe: Record<string, unknown> = {},
|
|
49
|
+
redactEvidence?: (value: unknown) => unknown): Readonly<SessionAssistantPreviewReading> {
|
|
50
|
+
const invalid = (): Readonly<SessionAssistantPreviewReading> => Object.freeze({ state: "unavailable", reason: "session_invalid" });
|
|
51
|
+
let rows: Record<string, unknown>[];
|
|
52
|
+
try {
|
|
53
|
+
const parsed: unknown[] = source.split("\n").filter(line => line.trim()).map(line => JSON.parse(line));
|
|
54
|
+
if (!parsed.length || parsed.some(value => !record(value))) return invalid();
|
|
55
|
+
rows = parsed as Record<string, unknown>[];
|
|
56
|
+
} catch { return invalid(); }
|
|
57
|
+
const header = rows[0];
|
|
58
|
+
if (header.type !== "session" || header.version !== 3 || typeof header.id !== "string" || !header.id) return invalid();
|
|
59
|
+
const ids = new Set<string>();
|
|
60
|
+
for (const row of rows.slice(1)) {
|
|
61
|
+
if (row.type === "session" || typeof row.type !== "string" || !row.type || typeof row.id !== "string" || !row.id || ids.has(row.id) ||
|
|
62
|
+
!(row.parentId === null || typeof row.parentId === "string")) return invalid();
|
|
63
|
+
ids.add(row.id);
|
|
64
|
+
}
|
|
65
|
+
const diagnostics: StateReadDiagnostic[] = [];
|
|
66
|
+
const branch = selectLastRecordedSessionBranch(rows, "session", diagnostics);
|
|
67
|
+
if (diagnostics.length) return invalid();
|
|
68
|
+
const redact = redactEvidence ?? createRunRecipeEvidenceRedactor(recipe, Number.MAX_SAFE_INTEGER);
|
|
69
|
+
const messages: Readonly<SessionAssistantPreviewMessage>[] = [];
|
|
70
|
+
let total = 0;
|
|
71
|
+
for (const entry of branch) {
|
|
72
|
+
if (entry.type !== "message") continue;
|
|
73
|
+
const message = record(entry.message);
|
|
74
|
+
if (!message || message.role !== "assistant") continue;
|
|
75
|
+
if (!Array.isArray(message.content)) return invalid();
|
|
76
|
+
const texts: string[] = [];
|
|
77
|
+
for (const value of message.content) {
|
|
78
|
+
const block = record(value);
|
|
79
|
+
if (block?.type !== "text") continue;
|
|
80
|
+
if (typeof block.text !== "string") return invalid();
|
|
81
|
+
texts.push(block.text);
|
|
82
|
+
}
|
|
83
|
+
if (!texts.length || !texts.join("\n")) continue;
|
|
84
|
+
const authorized = redact(texts.join("\n")) as string;
|
|
85
|
+
const points = Array.from(authorized);
|
|
86
|
+
const shown = points.slice(0, SESSION_EVIDENCE_TEXT_CHARS);
|
|
87
|
+
total++;
|
|
88
|
+
messages.push(Object.freeze({ ordinal: total, assistant_text: shown.join(""), text_chars_observed: points.length,
|
|
89
|
+
text_chars_shown: shown.length, text_chars_omitted: points.length - shown.length, preview_clipped: shown.length < points.length }));
|
|
90
|
+
if (messages.length > DEFAULT_INSPECT_LINES) messages.shift();
|
|
91
|
+
}
|
|
92
|
+
const encode = (): Readonly<SessionAssistantPreview> => Object.freeze({ content_scope: "assistant_visible_text_preview", source_scope: "last_recorded_branch",
|
|
93
|
+
primary_text: "not_exported", order: "oldest_first", coverage: Object.freeze({ truncated: true, redacted: true, history_complete: false }),
|
|
94
|
+
text_limit_chars: SESSION_EVIDENCE_TEXT_CHARS, messages_total: total, messages_shown: messages.length, messages_omitted: total - messages.length,
|
|
95
|
+
messages: Object.freeze([...messages]) });
|
|
96
|
+
let preview = encode();
|
|
97
|
+
while (messages.length && Buffer.byteLength(JSON.stringify(preview), "utf8") > SESSION_ASSISTANT_PREVIEW_MAX_BYTES) { messages.shift(); preview = encode(); }
|
|
98
|
+
if (Buffer.byteLength(JSON.stringify(preview), "utf8") > SESSION_ASSISTANT_PREVIEW_MAX_BYTES) return Object.freeze({ state: "unavailable", reason: "preview_too_large" });
|
|
99
|
+
return Object.freeze({ state: "available", preview });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// --- Recorded-session preview admission ---
|
|
103
|
+
|
|
104
|
+
export type RunSessionPreviewReading =
|
|
105
|
+
| { readonly state: "available"; readonly identity: Readonly<RunContextIndexIdentity>; readonly command_id: string;
|
|
106
|
+
readonly source_updated_at: string; readonly preview: Readonly<SessionAssistantPreview>; readonly unchanged: () => boolean }
|
|
107
|
+
| { readonly state: "unavailable"; readonly reason: Extract<RunExecutionReferenceReading, { state: "unavailable" }>["reason"] |
|
|
108
|
+
"session_metadata_unavailable" | "session_metadata_redacted" | "session_unavailable" | "session_too_large" | "session_invalid" | "preview_too_large" };
|
|
109
|
+
type PreviewUnavailable = Extract<RunSessionPreviewReading, { state: "unavailable" }>;
|
|
110
|
+
function previewUnavailable(reason: PreviewUnavailable["reason"]): Readonly<PreviewUnavailable> { return Object.freeze({ state: "unavailable", reason }); }
|
|
111
|
+
|
|
112
|
+
/** This policy is fixed in the kernel; serialized reference DTOs or actor visibility flags cannot widen it. */
|
|
113
|
+
export function readRunSessionPreview(stateDir: string, expected: unknown, query: unknown): Readonly<RunSessionPreviewReading> {
|
|
114
|
+
return readSelectedSessionPreview(stateDir, expected, query);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Internal projection data, not a new input capability: only the admitted writer supplies original Recipe privacy. */
|
|
118
|
+
function readSelectedSessionPreview(stateDir: string, expected: unknown, query: unknown,
|
|
119
|
+
recipePrivacy?: Record<string, unknown>): Readonly<RunSessionPreviewReading> {
|
|
120
|
+
const request = record(query);
|
|
121
|
+
if (!request || Object.getOwnPropertyDescriptor(request, "kind")?.value !== "session") return previewUnavailable("request_invalid");
|
|
122
|
+
const admitted = readRunExecutionReference(stateDir, expected, query);
|
|
123
|
+
if (admitted.state === "unavailable") return admitted;
|
|
124
|
+
try {
|
|
125
|
+
const metaPath = join(stateDir, "run.json");
|
|
126
|
+
const before = lstatSync(metaPath, { bigint: true });
|
|
127
|
+
if (!before.isFile() || before.size > BigInt(RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES)) return previewUnavailable("session_metadata_unavailable");
|
|
128
|
+
const reading = readJsonFileResilient<unknown>(metaPath, undefined, { maxBytes: RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES, strictUtf8: true });
|
|
129
|
+
const meta = record(reading.value);
|
|
130
|
+
if (!admitted.unchanged() || !sameFile(before, lstatSync(metaPath, { bigint: true }))) return previewUnavailable("observation_changed");
|
|
131
|
+
if (reading.diagnostics.length || !meta || meta.run !== admitted.identity.run || meta.run_instance_id !== admitted.identity.run_instance_id ||
|
|
132
|
+
meta.ownerId !== admitted.identity.ownerId || meta.state_dir !== resolve(stateDir)) return previewUnavailable("session_metadata_unavailable");
|
|
133
|
+
if (recipePrivacy && !createContextIdentityPrivacyCheck(recipePrivacy)({ source_updated_at: admitted.source_updated_at })) {
|
|
134
|
+
return previewUnavailable("session_metadata_redacted");
|
|
135
|
+
}
|
|
136
|
+
const source = readExecutionSessionText(stateDir, admitted.reference.path);
|
|
137
|
+
if (!admitted.unchanged()) return previewUnavailable("observation_changed");
|
|
138
|
+
if (source.availability === "unavailable") return previewUnavailable(source.reason === "reference_too_large" ? "session_too_large"
|
|
139
|
+
: source.reason === "reference_invalid_text" ? "session_invalid" : "session_unavailable");
|
|
140
|
+
const unchanged = (): boolean => {
|
|
141
|
+
try { return admitted.unchanged() && source.reference.unchanged() && sameFile(before, lstatSync(metaPath, { bigint: true })); }
|
|
142
|
+
catch { return false; }
|
|
143
|
+
};
|
|
144
|
+
if (!unchanged()) return previewUnavailable("observation_changed");
|
|
145
|
+
const projected = recipePrivacy ? projectSessionAssistantPreview(source.text, recipePrivacy, createRunRecipeBodyRedactor(recipePrivacy, Number.MAX_SAFE_INTEGER))
|
|
146
|
+
: projectSessionAssistantPreview(source.text, meta);
|
|
147
|
+
if (projected.state === "unavailable") return projected;
|
|
148
|
+
if (!unchanged()) return previewUnavailable("observation_changed");
|
|
149
|
+
const result = Object.freeze({ state: "available" as const, identity: admitted.identity, command_id: admitted.reference.command_id,
|
|
150
|
+
source_updated_at: admitted.source_updated_at, preview: projected.preview, unchanged });
|
|
151
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > SESSION_ASSISTANT_PREVIEW_MAX_BYTES) return previewUnavailable("preview_too_large");
|
|
152
|
+
return result;
|
|
153
|
+
} catch { return previewUnavailable("session_unavailable"); }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// --- Publication and selected exported previews ---
|
|
157
|
+
|
|
158
|
+
export const RUN_CONTEXT_SESSIONS_MAX_BYTES = 64 * 1024;
|
|
159
|
+
const PREVIEW_SCHEMA = "run-context-session-preview-v1";
|
|
160
|
+
const CATALOG_SCHEMA = "run-context-sessions-v1";
|
|
161
|
+
const COVERAGE = Object.freeze({ truncated: true, redacted: true, history_complete: false });
|
|
162
|
+
type Unavailable = { readonly status: "unavailable"; readonly reason: Extract<RunSessionPreviewReading, { state: "unavailable" }>["reason"] | "session_view_too_large" | "session_admission_unavailable" | "session_identity_redacted" };
|
|
163
|
+
export type RunSessionContextExportResult = Unavailable | { readonly status: "written"; readonly captured_at: string; readonly entry: Readonly<RunContextEntry> };
|
|
164
|
+
interface Selection { readonly kind: "session"; readonly command_id: string; readonly path: string; }
|
|
165
|
+
interface BaseEntry { readonly export_id: string; readonly command_id: string; readonly captured_at: string; }
|
|
166
|
+
function previewMetadata(entry: BaseEntry & { readonly path?: string | null }) {
|
|
167
|
+
return { export_id: entry.export_id, command_id: entry.command_id, captured_at: entry.captured_at,
|
|
168
|
+
...(typeof entry.path === "string" ? { path: entry.path } : {}) };
|
|
169
|
+
}
|
|
170
|
+
type PreviewEntry = BaseEntry & (
|
|
171
|
+
| { readonly availability: "available"; readonly path: string; readonly media_type: "application/json"; readonly bytes_total: number; readonly mutable: true; readonly coverage: typeof COVERAGE }
|
|
172
|
+
| { readonly availability: "unavailable"; readonly path: null; readonly reason: "preview_export_unavailable" }
|
|
173
|
+
);
|
|
174
|
+
type SessionBinding = { readonly run: string; readonly runInstanceId: string };
|
|
175
|
+
interface ObservedPreview { entry: Extract<PreviewEntry, { availability: "available" }>; path: string; stamp: BigIntStats; preview: Readonly<SessionAssistantPreview>; }
|
|
176
|
+
function record(value: unknown): Record<string, unknown> | undefined { return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : undefined; }
|
|
177
|
+
function known(value: Record<string, unknown>, keys: readonly string[]): boolean { return Object.keys(value).every(key => keys.includes(key)); }
|
|
178
|
+
function count(value: unknown): value is number { return typeof value === "number" && Number.isSafeInteger(value) && value >= 0; }
|
|
179
|
+
function command(value: unknown): value is string { return typeof value === "string" && /^command-[0-9]{3,}$/u.test(value) && Number.isSafeInteger(Number(value.slice(8))) && Number(value.slice(8)) > 0; }
|
|
180
|
+
function timestamp(value: unknown): value is string { const time = typeof value === "string" ? Date.parse(value) : NaN; return Number.isSafeInteger(time) && new Date(time).toISOString() === value; }
|
|
181
|
+
function coverage(value: unknown): boolean { const item = record(value); return !!item && known(item, ["truncated", "redacted", "history_complete"]) && item.truncated === true && item.redacted === true && item.history_complete === false; }
|
|
182
|
+
function sameObject(a: BigIntStats, b: BigIntStats): boolean { return a.dev === b.dev && a.ino === b.ino; }
|
|
183
|
+
function sameFile(a: BigIntStats, b: BigIntStats): boolean { return b.isFile() && sameObject(a, b) && a.size === b.size && a.mtimeNs === b.mtimeNs && a.ctimeNs === b.ctimeNs; }
|
|
184
|
+
function stamp(path: string): BigIntStats | undefined { try { return lstatSync(path, { bigint: true }); } catch (error) { if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined; throw error; } }
|
|
185
|
+
function previewPath(id: string): string { return `evidence/sessions/session-${id}.json`; }
|
|
186
|
+
function selection(value: unknown): Readonly<Selection> | undefined {
|
|
187
|
+
const source = record(value); if (!source || !known(source, ["kind", "command_id", "path"])) return undefined;
|
|
188
|
+
const own = (key: string): unknown => Object.getOwnPropertyDescriptor(source, key)?.value;
|
|
189
|
+
const id = own("command_id"); const path = own("path");
|
|
190
|
+
return own("kind") === "session" && command(id) && typeof path === "string" && !!path ? Object.freeze({ kind: "session", command_id: id, path }) : undefined;
|
|
191
|
+
}
|
|
192
|
+
function bound(value: Record<string, unknown>, schema: string, binding: SessionBinding): boolean {
|
|
193
|
+
return value.schema === schema && value.run === binding.run && value.run_instance_id === binding.runInstanceId;
|
|
194
|
+
}
|
|
195
|
+
function readBounded(path: string): { value: Record<string, unknown>; stamp: BigIntStats } | undefined {
|
|
196
|
+
const before = stamp(path); if (!before?.isFile() || before.size > BigInt(RUN_CONTEXT_SESSIONS_MAX_BYTES)) return undefined;
|
|
197
|
+
const read = readJsonFileResilient<unknown>(path, undefined, { maxBytes: RUN_CONTEXT_SESSIONS_MAX_BYTES, strictUtf8: true });
|
|
198
|
+
const value = record(read.value); const after = stamp(path);
|
|
199
|
+
return !read.diagnostics.length && value && after && sameFile(before, after) ? { value, stamp: after } : undefined;
|
|
200
|
+
}
|
|
201
|
+
function validPreview(value: unknown): boolean {
|
|
202
|
+
const view = record(value);
|
|
203
|
+
if (!view || !known(view, ["content_scope", "source_scope", "primary_text", "order", "coverage", "text_limit_chars", "messages_total", "messages_shown", "messages_omitted", "messages"]) ||
|
|
204
|
+
view.content_scope !== "assistant_visible_text_preview" || view.source_scope !== "last_recorded_branch" || view.primary_text !== "not_exported" || view.order !== "oldest_first" ||
|
|
205
|
+
!coverage(view.coverage) || view.text_limit_chars !== SESSION_EVIDENCE_TEXT_CHARS || !count(view.messages_total) || !count(view.messages_shown) || !count(view.messages_omitted) ||
|
|
206
|
+
!Array.isArray(view.messages) || view.messages.length > DEFAULT_INSPECT_LINES || view.messages.length !== view.messages_shown || view.messages_shown + view.messages_omitted !== view.messages_total) return false;
|
|
207
|
+
let ordinal = 0;
|
|
208
|
+
for (const value of view.messages) {
|
|
209
|
+
const row = record(value);
|
|
210
|
+
if (!row || !known(row, ["ordinal", "assistant_text", "text_chars_observed", "text_chars_shown", "text_chars_omitted", "preview_clipped"]) ||
|
|
211
|
+
!count(row.ordinal) || row.ordinal <= ordinal || row.ordinal > view.messages_total || typeof row.assistant_text !== "string" || !count(row.text_chars_observed) ||
|
|
212
|
+
!count(row.text_chars_shown) || !count(row.text_chars_omitted) || row.text_chars_shown > SESSION_EVIDENCE_TEXT_CHARS || Array.from(row.assistant_text).length !== row.text_chars_shown ||
|
|
213
|
+
row.text_chars_shown + row.text_chars_omitted !== row.text_chars_observed || row.preview_clipped !== (row.text_chars_omitted > 0)) return false;
|
|
214
|
+
ordinal = row.ordinal;
|
|
215
|
+
}
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
function inspectPreview(root: string, base: BaseEntry, binding: SessionBinding): ObservedPreview | undefined {
|
|
219
|
+
try {
|
|
220
|
+
for (const directory of [join(root, "evidence"), join(root, "evidence", "sessions")]) {
|
|
221
|
+
const current = stamp(directory); if (!current?.isDirectory() || current.isSymbolicLink()) return undefined;
|
|
222
|
+
}
|
|
223
|
+
const relative = previewPath(base.export_id); const path = join(root, relative);
|
|
224
|
+
const read = readBounded(path); if (!read) return undefined;
|
|
225
|
+
const view = read.value;
|
|
226
|
+
if (!bound(view, PREVIEW_SCHEMA, binding) || !known(view, ["schema", "run", "run_instance_id", "command_id", "export_id", "captured_at", "source_updated_at", "coverage", "preview"]) ||
|
|
227
|
+
view.command_id !== base.command_id || view.export_id !== base.export_id || !timestamp(view.captured_at) || !timestamp(view.source_updated_at) || !coverage(view.coverage) || !validPreview(view.preview)) return undefined;
|
|
228
|
+
const parsed = view.preview as unknown as SessionAssistantPreview;
|
|
229
|
+
const preview = Object.freeze({ ...parsed, coverage: COVERAGE, messages: Object.freeze(parsed.messages.map(message => Object.freeze({ ...message }))) });
|
|
230
|
+
return { path, stamp: read.stamp, preview, entry: Object.freeze({ export_id: base.export_id, command_id: base.command_id, captured_at: view.captured_at,
|
|
231
|
+
availability: "available", path: relative, media_type: "application/json", bytes_total: Number(read.stamp.size), mutable: true, coverage: COVERAGE }) };
|
|
232
|
+
} catch { return undefined; }
|
|
233
|
+
}
|
|
234
|
+
function readCatalog(root: string, binding: SessionBinding): { entries: PreviewEntry[]; stamp?: BigIntStats } {
|
|
235
|
+
const evidence = stamp(join(root, "evidence"));
|
|
236
|
+
if (evidence && (!evidence.isDirectory() || evidence.isSymbolicLink())) throw new Error("Run session catalog directory is unavailable or invalid.");
|
|
237
|
+
const path = join(root, runContextExportPath("sessions")); const existing = stamp(path);
|
|
238
|
+
if (!existing) return { entries: [] };
|
|
239
|
+
const read = readBounded(path); const value = read?.value;
|
|
240
|
+
const invalid = () => new Error("Run session catalog is unavailable, invalid or belongs to another generation.");
|
|
241
|
+
if (!read || !value || !bound(value, CATALOG_SCHEMA, binding) || !known(value, ["schema", "run", "run_instance_id", "updated_at", "inventory_scope", "inventory_complete", "coverage", "previews_total", "previews"]) ||
|
|
242
|
+
!timestamp(value.updated_at) || value.inventory_scope !== "published_previews" || value.inventory_complete !== false || !coverage(value.coverage) ||
|
|
243
|
+
!Array.isArray(value.previews) || value.previews_total !== value.previews.length) throw invalid();
|
|
244
|
+
const ids = new Set<string>(); const entries: PreviewEntry[] = [];
|
|
245
|
+
for (const item of value.previews) {
|
|
246
|
+
const row = record(item);
|
|
247
|
+
if (!row || typeof row.export_id !== "string" || !/^[a-f0-9]{64}$/u.test(row.export_id) || ids.has(row.export_id) || !command(row.command_id) || !timestamp(row.captured_at)) throw invalid();
|
|
248
|
+
ids.add(row.export_id); const base = { export_id: row.export_id, command_id: row.command_id, captured_at: row.captured_at };
|
|
249
|
+
if (row.availability === "available" && known(row, ["export_id", "command_id", "captured_at", "availability", "path", "media_type", "bytes_total", "mutable", "coverage"]) &&
|
|
250
|
+
row.path === previewPath(row.export_id) && row.media_type === "application/json" && count(row.bytes_total) && row.bytes_total <= RUN_CONTEXT_SESSIONS_MAX_BYTES && row.mutable === true && coverage(row.coverage)) {
|
|
251
|
+
entries.push(Object.freeze({ ...base, availability: "available", path: row.path, media_type: "application/json", bytes_total: row.bytes_total, mutable: true, coverage: COVERAGE }));
|
|
252
|
+
} else if (row.availability === "unavailable" && known(row, ["export_id", "command_id", "captured_at", "availability", "path", "reason"]) && row.path === null && row.reason === "preview_export_unavailable") {
|
|
253
|
+
entries.push(Object.freeze({ ...base, availability: "unavailable", path: null, reason: "preview_export_unavailable" }));
|
|
254
|
+
} else throw invalid();
|
|
255
|
+
}
|
|
256
|
+
return { entries, stamp: read.stamp };
|
|
257
|
+
}
|
|
258
|
+
function directory(path: string): BigIntStats {
|
|
259
|
+
let value = stamp(path); if (!value) { mkdirSync(path); value = lstatSync(path, { bigint: true }); }
|
|
260
|
+
if (!value.isDirectory() || value.isSymbolicLink()) throw new Error("Run session export directory is unavailable or invalid.");
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
function targetUnchanged(path: string, expected: BigIntStats | undefined): boolean { const current = stamp(path); return expected ? !!current && sameFile(expected, current) : current === undefined; }
|
|
264
|
+
|
|
265
|
+
/** A real producer is required for both privacy capture and each subsequent evidence commit. */
|
|
266
|
+
export function writeRunSessionContextExport(stateDir: string, producer: RunResultProducer, query: unknown): Readonly<RunSessionContextExportResult> {
|
|
267
|
+
const captured = withRunProducerEvidence(stateDir, producer, () => {
|
|
268
|
+
const selected = selection(query); if (!selected) return { status: "unavailable", reason: "request_invalid" } as const;
|
|
269
|
+
const recipePrivacy = readRunProducerCapturedMetadata(producer, RUN_CONTEXT_SOURCE_METADATA_MAX_BYTES);
|
|
270
|
+
if (!recipePrivacy) return { status: "unavailable", reason: "session_admission_unavailable" } as const;
|
|
271
|
+
const identity = { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId !== undefined ? { ownerId: producer.ownerId } : {}) };
|
|
272
|
+
const metadataUnredacted = createContextIdentityPrivacyCheck(recipePrivacy);
|
|
273
|
+
if (!metadataUnredacted(identity)) return { status: "unavailable", reason: "session_identity_redacted" } as const;
|
|
274
|
+
if (!metadataUnredacted({ command_id: selected.command_id })) return { status: "unavailable", reason: "session_metadata_redacted" } as const;
|
|
275
|
+
const read = readSelectedSessionPreview(stateDir, identity, selected, recipePrivacy);
|
|
276
|
+
if (read.state === "unavailable") return { status: "unavailable", reason: read.reason } as const;
|
|
277
|
+
const id = createHash("sha256").update(JSON.stringify([selected.command_id, selected.path])).digest("hex");
|
|
278
|
+
const capturedAt = new Date().toISOString();
|
|
279
|
+
const base = { export_id: id, command_id: selected.command_id, captured_at: capturedAt };
|
|
280
|
+
if (!metadataUnredacted(previewMetadata({ ...base, path: previewPath(id) }))) return { status: "unavailable", reason: "session_metadata_redacted" } as const;
|
|
281
|
+
const content = JSON.stringify({ schema: PREVIEW_SCHEMA, run: producer.run, run_instance_id: producer.runInstanceId, command_id: selected.command_id,
|
|
282
|
+
export_id: id, captured_at: capturedAt, source_updated_at: read.source_updated_at, coverage: COVERAGE, preview: read.preview }, null, 2) + "\n";
|
|
283
|
+
if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_SESSIONS_MAX_BYTES) return { status: "unavailable", reason: "session_view_too_large" } as const;
|
|
284
|
+
return { status: "captured", content, base, unchanged: read.unchanged, metadataUnredacted } as const;
|
|
285
|
+
});
|
|
286
|
+
if (captured.status === "unavailable") return Object.freeze(captured);
|
|
287
|
+
return withRunProducerEvidence(stateDir, producer, () => {
|
|
288
|
+
const owner = readRunContextIdentity(stateDir, { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId !== undefined ? { ownerId: producer.ownerId } : {}) });
|
|
289
|
+
if (owner.state === "unavailable" || !captured.unchanged()) throw new Error("Run session export source or generation changed.");
|
|
290
|
+
const root = owner.local_path; const evidencePath = join(root, "evidence"); const evidence = directory(evidencePath);
|
|
291
|
+
const prior = readCatalog(root, producer);
|
|
292
|
+
const retained = prior.entries.find(entry => entry.export_id === captured.base.export_id);
|
|
293
|
+
if (retained && retained.command_id !== captured.base.command_id) throw new Error("Run session catalog binding contradicts the selected preview.");
|
|
294
|
+
// Only these bound peer fields survive every observation; old times/paths may legitimately refresh or disappear.
|
|
295
|
+
if (prior.entries.some(entry => entry.export_id !== captured.base.export_id &&
|
|
296
|
+
!captured.metadataUnredacted({ export_id: entry.export_id, command_id: entry.command_id }))) {
|
|
297
|
+
return Object.freeze({ status: "unavailable", reason: "session_metadata_redacted" } as const);
|
|
298
|
+
}
|
|
299
|
+
const previewDirectory = join(evidencePath, "sessions"); const previews = directory(previewDirectory);
|
|
300
|
+
const frameUnchanged = (): boolean => {
|
|
301
|
+
const currentEvidence = lstatSync(evidencePath, { bigint: true }); const currentPreviews = lstatSync(previewDirectory, { bigint: true });
|
|
302
|
+
return owner.unchanged() && currentEvidence.isDirectory() && sameObject(evidence, currentEvidence) && currentPreviews.isDirectory() && sameObject(previews, currentPreviews);
|
|
303
|
+
};
|
|
304
|
+
const path = join(root, previewPath(captured.base.export_id)); const target = stamp(path);
|
|
305
|
+
if (target && !inspectPreview(root, captured.base, producer)) throw new Error("Run session preview binding is unavailable or invalid.");
|
|
306
|
+
writeTextAtomic(path, captured.content, { onBeforeReplace() {
|
|
307
|
+
if (!frameUnchanged() || !captured.unchanged() || !targetUnchanged(path, target)) throw new Error("Run session export binding or source changed before publication.");
|
|
308
|
+
} });
|
|
309
|
+
const bases: BaseEntry[] = [...prior.entries.filter(entry => entry.export_id !== captured.base.export_id), captured.base];
|
|
310
|
+
const observations: ObservedPreview[] = [];
|
|
311
|
+
const entries = bases.map(base => {
|
|
312
|
+
const observed = inspectPreview(root, base, producer);
|
|
313
|
+
if (observed) { observations.push(observed); return observed.entry; }
|
|
314
|
+
return Object.freeze({ export_id: base.export_id, command_id: base.command_id, captured_at: base.captured_at, availability: "unavailable" as const, path: null, reason: "preview_export_unavailable" as const });
|
|
315
|
+
});
|
|
316
|
+
if (!observations.some(observed => observed.entry.export_id === captured.base.export_id)) throw new Error("Run session preview binding changed after publication.");
|
|
317
|
+
// The selected preview may already be committed; preserve it and the prior catalog on this distinct refusal.
|
|
318
|
+
if (entries.some(entry => !captured.metadataUnredacted(previewMetadata(entry)))) {
|
|
319
|
+
return Object.freeze({ status: "unavailable", reason: "session_metadata_redacted" } as const);
|
|
320
|
+
}
|
|
321
|
+
const content = JSON.stringify({ schema: CATALOG_SCHEMA, run: producer.run, run_instance_id: producer.runInstanceId, updated_at: captured.base.captured_at,
|
|
322
|
+
inventory_scope: "published_previews", inventory_complete: false, coverage: COVERAGE, previews_total: entries.length, previews: entries }, null, 2) + "\n";
|
|
323
|
+
if (Buffer.byteLength(content, "utf8") > RUN_CONTEXT_SESSIONS_MAX_BYTES) throw new Error("Run session catalog exceeds its bounded metadata limit.");
|
|
324
|
+
const catalogPath = join(root, runContextExportPath("sessions"));
|
|
325
|
+
writeTextAtomic(catalogPath, content, { onBeforeReplace() {
|
|
326
|
+
if (!frameUnchanged() || !captured.unchanged() || !targetUnchanged(catalogPath, prior.stamp) || observations.some(view => !sameFile(view.stamp, lstatSync(view.path, { bigint: true })))) {
|
|
327
|
+
throw new Error("Run session catalog references or source changed before publication.");
|
|
328
|
+
}
|
|
329
|
+
} });
|
|
330
|
+
const entry: RunContextEntry = Object.freeze({ availability: "available", path: runContextExportPath("sessions"), media_type: "application/json", bytes_total: Buffer.byteLength(content, "utf8"), mutable: true, coverage: COVERAGE });
|
|
331
|
+
return Object.freeze({ status: "written", captured_at: captured.base.captured_at, entry });
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** Preview, catalog and context index are separate effects; no failed phase rolls earlier commits back. */
|
|
336
|
+
export function writeRunSessionContextIndex(stateDir: string, producer: RunResultProducer, query: unknown): Readonly<RunContextIndexPublication & { status: "published" }> {
|
|
337
|
+
const view = writeRunSessionContextExport(stateDir, producer, query);
|
|
338
|
+
return withRunProducerEvidence(stateDir, producer, () => {
|
|
339
|
+
if (view.status === "written") {
|
|
340
|
+
const catalog = readCatalog(stateDir, producer);
|
|
341
|
+
for (const entry of catalog.entries) {
|
|
342
|
+
if (entry.availability !== "available") continue;
|
|
343
|
+
const observed = inspectPreview(stateDir, entry, producer);
|
|
344
|
+
if (!observed || observed.entry.bytes_total !== entry.bytes_total || observed.entry.captured_at !== entry.captured_at) {
|
|
345
|
+
throw new Error("Run session catalog references changed before index publication.");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const entry: RunContextEntry = view.status === "written" ? view.entry : view.reason === "manifest_absent" ? { availability: "pending", path: null } : { availability: "unavailable", path: null, reason: view.reason };
|
|
350
|
+
const publication = writeRunContextIndexUnderLock(stateDir, { run: producer.run, run_instance_id: producer.runInstanceId, ...(producer.ownerId !== undefined ? { ownerId: producer.ownerId } : {}) }, { sessions: entry });
|
|
351
|
+
return Object.freeze({ status: "published", ...publication });
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export type RunSessionContextPreviewReading =
|
|
356
|
+
| { readonly state: "available"; readonly identity: Readonly<RunContextIndexIdentity>; readonly export_id: string; readonly command_id: string;
|
|
357
|
+
readonly captured_at: string; readonly path: string; readonly media_type: "application/json"; readonly bytes_total: number; readonly mutable: true;
|
|
358
|
+
readonly coverage: typeof COVERAGE; readonly preview: Readonly<SessionAssistantPreview>; readonly unchanged: () => boolean }
|
|
359
|
+
| { readonly state: "unavailable"; readonly reason: "selection_invalid" | "identity_unavailable" | "identity_mismatch" | "observation_changed" |
|
|
360
|
+
"index_unavailable" | "sessions_unavailable" | "catalog_unavailable" | "preview_not_listed" | "preview_unavailable" | "preview_invalid" | "preview_too_large" };
|
|
361
|
+
type ReadUnavailable = Extract<RunSessionContextPreviewReading, { state: "unavailable" }>;
|
|
362
|
+
function readUnavailable(reason: ReadUnavailable["reason"]): Readonly<ReadUnavailable> { return Object.freeze({ state: "unavailable", reason }); }
|
|
363
|
+
|
|
364
|
+
/** Read only the selected published body: index metadata never substitutes for nested format/generation admission. */
|
|
365
|
+
export function readRunSessionContextPreview(stateDir: string, expected: unknown, exportId: unknown): Readonly<RunSessionContextPreviewReading> {
|
|
366
|
+
if (typeof exportId !== "string" || !/^[a-f0-9]{64}$/u.test(exportId)) return readUnavailable("selection_invalid");
|
|
367
|
+
const owner = readRunContextIdentity(stateDir, expected);
|
|
368
|
+
if (owner.state === "unavailable") return readUnavailable(owner.reason);
|
|
369
|
+
try {
|
|
370
|
+
const root = owner.local_path; const indexPath = join(root, RUN_CONTEXT_INDEX_FILE); const indexBefore = stamp(indexPath);
|
|
371
|
+
if (!indexBefore?.isFile()) return readUnavailable("index_unavailable");
|
|
372
|
+
const evidencePath = join(root, "evidence"); const evidence = stamp(evidencePath);
|
|
373
|
+
if (!evidence?.isDirectory() || evidence.isSymbolicLink()) return readUnavailable("sessions_unavailable");
|
|
374
|
+
const catalogPath = join(root, runContextExportPath("sessions")); const catalogBefore = stamp(catalogPath);
|
|
375
|
+
const indexed = readRunContextIndex(root, owner.identity);
|
|
376
|
+
if (!owner.unchanged() || !targetUnchanged(indexPath, indexBefore)) return readUnavailable("observation_changed");
|
|
377
|
+
if (indexed.state === "unavailable") return readUnavailable("index_unavailable");
|
|
378
|
+
const role = indexed.index.entries.sessions;
|
|
379
|
+
if (role.availability !== "available" || role.mutable !== true || !coverage(role.coverage)) return readUnavailable("sessions_unavailable");
|
|
380
|
+
if (!catalogBefore?.isFile() || catalogBefore.size !== BigInt(role.bytes_total)) return readUnavailable("catalog_unavailable");
|
|
381
|
+
const binding: SessionBinding = { run: owner.identity.run, runInstanceId: owner.identity.run_instance_id };
|
|
382
|
+
const catalog = readCatalog(root, binding);
|
|
383
|
+
if (!catalog.stamp || !sameFile(catalogBefore, catalog.stamp)) return readUnavailable("observation_changed");
|
|
384
|
+
const selected = catalog.entries.find(entry => entry.export_id === exportId);
|
|
385
|
+
if (!selected) return readUnavailable("preview_not_listed");
|
|
386
|
+
if (selected.availability !== "available") return readUnavailable("preview_unavailable");
|
|
387
|
+
const previewDirectory = join(evidencePath, "sessions"); const previews = stamp(previewDirectory);
|
|
388
|
+
if (!previews?.isDirectory() || previews.isSymbolicLink()) return readUnavailable("preview_invalid");
|
|
389
|
+
const observed = inspectPreview(root, selected, binding);
|
|
390
|
+
if (!observed || observed.entry.bytes_total !== selected.bytes_total || observed.entry.captured_at !== selected.captured_at) return readUnavailable("preview_invalid");
|
|
391
|
+
const unchanged = (): boolean => {
|
|
392
|
+
try {
|
|
393
|
+
const currentEvidence = lstatSync(evidencePath, { bigint: true }); const currentPreviews = lstatSync(previewDirectory, { bigint: true });
|
|
394
|
+
return owner.unchanged() && targetUnchanged(indexPath, indexBefore) && targetUnchanged(catalogPath, catalog.stamp) &&
|
|
395
|
+
currentEvidence.isDirectory() && sameObject(evidence, currentEvidence) && currentPreviews.isDirectory() && sameObject(previews, currentPreviews) &&
|
|
396
|
+
sameFile(observed.stamp, lstatSync(observed.path, { bigint: true }));
|
|
397
|
+
} catch { return false; }
|
|
398
|
+
};
|
|
399
|
+
if (!unchanged()) return readUnavailable("observation_changed");
|
|
400
|
+
const result = Object.freeze({ state: "available" as const, identity: owner.identity, export_id: selected.export_id, command_id: selected.command_id,
|
|
401
|
+
captured_at: selected.captured_at, path: observed.entry.path, media_type: observed.entry.media_type, bytes_total: observed.entry.bytes_total,
|
|
402
|
+
mutable: observed.entry.mutable, coverage: COVERAGE, preview: observed.preview, unchanged });
|
|
403
|
+
if (Buffer.byteLength(JSON.stringify(result), "utf8") > RUN_CONTEXT_SESSIONS_MAX_BYTES) return readUnavailable("preview_too_large");
|
|
404
|
+
return result;
|
|
405
|
+
} catch { return readUnavailable("catalog_unavailable"); }
|
|
406
|
+
}
|