@piagent/platform 1.5.5 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +166 -0
- package/README.md +25 -21
- package/README.vi.md +34 -5
- package/adapters/web-frontend/profile.json +2 -4
- package/architecture/layers.json +18 -3
- package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
- package/benchmarks/deep-logic-v1/grade.mjs +272 -10
- package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
- package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
- package/benchmarks/deep-logic-v1/project/package.json +1 -1
- package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
- package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
- package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
- package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
- package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
- package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
- package/benchmarks/deep-logic-v1/suite.json +51 -3
- package/benchmarks/deep-logic-v1/variant.mjs +22 -1
- package/benchmarks/production-v1/spend-control.v1.json +92 -0
- package/benchmarks/production-v1/suite.json +46 -3
- package/catalog/capabilities.json +18 -18
- package/docs/authority-manifest.md +1 -1
- package/docs/auto-delegation-policy.md +38 -73
- package/docs/command-reference-vietnamese.md +27 -35
- package/docs/context-engine.md +12 -4
- package/docs/context-window-policy.md +37 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +27 -13
- package/docs/languages.json +4 -4
- package/docs/model-options.md +8 -5
- package/docs/operator-manual-vietnamese.md +27 -45
- package/docs/project-adapters.md +2 -2
- package/docs/publishing-for-teams.md +10 -0
- package/docs/quality-benchmark.md +427 -62
- package/docs/release-install-policy.md +10 -9
- package/docs/runtime-harness-standard.md +3 -3
- package/docs/runtime-policy-design.md +4 -4
- package/docs/runtime-quality-baseline.md +13 -6
- package/docs/security-threat-model.md +2 -2
- package/docs/subagent-orchestration-capabilities.md +19 -51
- package/docs/subagents-and-multiagent.md +21 -47
- package/docs/task-implementation-contract.md +3 -3
- package/docs/task-lifecycle-vietnamese.md +3 -3
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +7 -2
- package/docs/vi/architecture.md +27 -13
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +89 -95
- package/docs-site/commands.html +1 -1
- package/docs-site/content/benchmark.html +88 -94
- package/docs-site/content/en/benchmark.html +134 -90
- package/docs-site/content/en/index.html +19 -19
- package/docs-site/content/en/runtime.html +4 -4
- package/docs-site/content/en/whats-new.html +12 -1
- package/docs-site/content/en/workflows.html +2 -2
- package/docs-site/content/index.html +19 -19
- package/docs-site/content/runtime.html +4 -4
- package/docs-site/content/whats-new.html +12 -1
- package/docs-site/content/workflows.html +2 -2
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +135 -91
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +20 -20
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +5 -5
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +13 -2
- package/docs-site/en/workflows.html +3 -3
- package/docs-site/index.html +20 -20
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +5 -5
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +13 -2
- package/docs-site/workflows.html +3 -3
- package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
- package/evals/fixtures/codex-relative-efficiency-v1.invalid.json +4 -0
- package/evals/fixtures/codex-relative-efficiency-v1.valid.json +94 -0
- package/evals/fixtures/helper-request.invalid.json +1 -1
- package/evals/fixtures/helper-request.valid.json +1 -1
- package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
- package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
- package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +7 -0
- package/evals/fixtures/task-authority-snapshot.valid.json +4 -4
- package/evals/fs4-readiness-matrix.v1.json +2 -2
- package/evals/fs5-causal-arm.v1.json +3 -3
- package/evals/fs5-pilot-protocol.v1.json +3 -3
- package/evals/fs5-pilot-protocol.v2.json +8 -8
- package/evals/fs5-pilot-protocol.v3.json +8 -8
- package/evals/fs5-pilot-protocol.v4.json +7 -6
- package/evals/fs5-pilot-protocol.v5.json +9 -7
- package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
- package/evals/runtime-conformance-v1/README.md +43 -0
- package/evals/runtime-conformance-v1/evaluate-paid-canary.mjs +198 -0
- package/evals/runtime-conformance-v1/lane.json +57 -0
- package/evals/runtime-conformance-v1/paid-canary.json +31 -0
- package/evals/runtime-conformance-v1/runner.mjs +456 -0
- package/package.json +3 -1
- package/packages/piagent-core/README.md +13 -4
- package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
- package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
- package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
- package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
- package/packages/piagent-core/benchmark/benchmark-codex-relative-efficiency.js +698 -0
- package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
- package/packages/piagent-core/benchmark/benchmark-core.js +736 -664
- package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
- package/packages/piagent-core/benchmark/benchmark-forensics.js +441 -4
- package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
- package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
- package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
- package/packages/piagent-core/benchmark/benchmark-preflight.js +5 -1
- package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
- package/packages/piagent-core/benchmark/benchmark-provider-free-evidence.js +345 -0
- package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +252 -0
- package/packages/piagent-core/benchmark/benchmark-report.js +130 -12
- package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
- package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
- package/packages/piagent-core/benchmark/benchmark-runtime-causal.js +244 -0
- package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +979 -0
- package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
- package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
- package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
- package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
- package/packages/piagent-core/benchmark/benchmark-suite.js +149 -2
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +380 -0
- package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +322 -0
- package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
- package/packages/piagent-core/capabilities/runtime-integrity.js +3 -0
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
- package/packages/piagent-core/extensions/acceptance-behavior-proof.js +806 -0
- package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
- package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
- package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
- package/packages/piagent-core/extensions/acceptance-receipt.js +123 -59
- package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
- package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
- package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
- package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
- package/packages/piagent-core/extensions/context-engine.js +149 -156
- package/packages/piagent-core/extensions/context-evidence.js +48 -0
- package/packages/piagent-core/extensions/context-import-links.js +296 -0
- package/packages/piagent-core/extensions/context-index-policy.js +4 -0
- package/packages/piagent-core/extensions/context-query-paths.js +57 -0
- package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +435 -7
- package/packages/piagent-core/extensions/criterion-graph.js +82 -9
- package/packages/piagent-core/extensions/fallback-base-policy.ts +101 -0
- package/packages/piagent-core/extensions/guard-types.ts +6 -0
- package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +420 -260
- package/packages/piagent-core/extensions/task-contract-view.js +17 -3
- package/packages/piagent-core/extensions/task-lifecycle.js +39 -2
- package/packages/piagent-core/extensions/task-state.js +109 -142
- package/packages/piagent-core/extensions/verification-intelligence.js +52 -13
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +193 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +44 -2
- package/packages/piagent-core/policy/authority-manifest.v1.json +3 -3
- package/packages/piagent-core/prompts/be-to-fe.md +2 -8
- package/packages/piagent-core/prompts/plan.md +1 -1
- package/packages/piagent-core/prompts/platform-improve.md +2 -6
- package/packages/piagent-core/prompts/review.md +2 -7
- package/packages/piagent-core/prompts/scout.md +1 -1
- package/packages/piagent-core/prompts/task.md +4 -3
- package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
- package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
- package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
- package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
- package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
- package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
- package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
- package/packages/piagent-core/runtime/hooks/completion-hook.ts +12 -12
- package/packages/piagent-core/runtime/hooks/input-hook.ts +17 -1
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +107 -3
- package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +134 -166
- package/packages/piagent-core/runtime/hooks/tool-result-value-helpers.ts +66 -0
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
- package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
- package/packages/piagent-core/runtime/inspection/bounded-emission-guard.ts +286 -0
- package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +37 -7
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +27 -4
- package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +70 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +73 -9
- package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
- package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
- package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
- package/packages/piagent-core/runtime/orchestration/helper-lifecycle.ts +16 -12
- package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +71 -0
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +28 -20
- package/packages/piagent-core/runtime/orchestration/role-policy.ts +27 -9
- package/packages/piagent-core/runtime/orchestration/subagent-tool-policy.ts +329 -0
- package/packages/piagent-core/runtime/policy/block-remedy.ts +0 -4
- package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +7 -2
- package/packages/piagent-core/runtime/quality/edit-freshness-guard.ts +102 -0
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +22 -11
- package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +9 -5
- package/packages/piagent-core/runtime/recovery/continuation-budget.ts +1 -1
- package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
- package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
- package/packages/piagent-core/runtime/recovery/handoff-projection.ts +4 -1
- package/packages/piagent-core/runtime/recovery/pristine-task-policy-refresh.ts +91 -0
- package/packages/piagent-core/runtime/recovery/recovery-policy.ts +7 -4
- package/packages/piagent-core/runtime/recovery/resume-state.ts +112 -14
- package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +60 -13
- package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
- package/packages/piagent-core/runtime/recovery/task-evidence-coverage-refresh.ts +217 -0
- package/packages/piagent-core/runtime/registration/context-commands.ts +82 -7
- package/packages/piagent-core/runtime/registration/knowledge-tools.ts +9 -5
- package/packages/piagent-core/runtime/registration/operator-request-intake.ts +49 -0
- package/packages/piagent-core/runtime/registration/policy-tools.ts +50 -15
- package/packages/piagent-core/runtime/registration/session-commands.ts +1 -1
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +61 -25
- package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +21 -12
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +169 -0
- package/packages/piagent-core/runtime/registration/task-start-manifest.ts +54 -0
- package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +108 -103
- package/packages/piagent-core/runtime/runtime-limits.ts +16 -2
- package/packages/piagent-core/runtime/session/adaptive-context-analysis.ts +461 -0
- package/packages/piagent-core/runtime/session/adaptive-context-governor.ts +464 -0
- package/packages/piagent-core/runtime/session/adaptive-context-ledger.ts +265 -0
- package/packages/piagent-core/runtime/session/message-signals.ts +7 -3
- package/packages/piagent-core/runtime/session/operator-request-carry.ts +44 -0
- package/packages/piagent-core/runtime/session/runtime-state.ts +161 -5
- package/packages/piagent-core/runtime/session/source-checkout-read-grants.ts +35 -0
- package/packages/piagent-core/runtime/session/system-prompt.ts +76 -14
- package/packages/piagent-core/runtime/session/task-contract-presentation.ts +39 -0
- package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
- package/packages/piagent-core/runtime/tools/phase-tools.ts +8 -8
- package/packages/piagent-core/runtime/tools/tool-groups.ts +12 -2
- package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
- package/packages/piagent-core/runtime/verification/exact-verifier-reuse.ts +101 -0
- package/packages/piagent-core/runtime/workflows/runtime-intake-compaction.ts +164 -0
- package/packages/piagent-core/runtime/workflows/task-intake.ts +207 -38
- package/packages/piagent-core/security/sensitive-data.js +13 -218
- package/packages/piagent-core/security/sensitive-project-file.js +42 -0
- package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
- package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
- package/packages/piagent-core/security/sensitive-text.js +234 -0
- package/packages/piagent-core/subagents/piagent-oracle.md +5 -3
- package/packages/piagent-core/subagents/piagent-planner.md +6 -4
- package/packages/piagent-core/subagents/piagent-reviewer.md +5 -3
- package/packages/piagent-core/subagents/piagent-scout.md +4 -2
- package/packages/piagent-core/subagents/piagent-worker.md +7 -5
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-B4XehTCU.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-CgOEyRPG.js +176 -0
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/extension/piagent-webui.ts +3 -1
- package/packages/piagent-webui/extension/same-session-bridge.ts +1 -1
- package/packages/piagent-webui/extension/session-stream-adapter.ts +33 -10
- package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
- package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +122 -20
- package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
- package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
- package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
- package/packages/piagent-webui/gateway/session-operation-lifecycle.ts +237 -0
- package/packages/piagent-webui/gateway/session-operation-retry-control.ts +38 -0
- package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +165 -0
- package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +141 -125
- package/packages/piagent-webui/ownership/session-lease-store.ts +5 -2
- package/packages/piagent-webui/server/core-inspection-provider.ts +2 -1
- package/packages/piagent-webui/server/loopback-server.ts +8 -0
- package/packages/piagent-webui/server/transcript-projection.ts +113 -9
- package/packages/piagent-webui/shared/text-visibility.ts +10 -0
- package/schemas/codex-relative-efficiency-v1.schema.json +155 -0
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
- package/schemas/piagent-webui/runtime-event-v2.schema.json +15 -5
- package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
- package/schemas/piagent-webui/subagent-tree-v1.schema.json +11 -1
- package/schemas/project-profile.schema.json +1 -1
- package/schemas/task-contract.schema.json +27 -1
- package/scripts/benchmark-runner-core.mjs +367 -363
- package/scripts/benchmark-runner-finalization.mjs +313 -0
- package/scripts/benchmark-runner-host-readiness.mjs +94 -0
- package/scripts/benchmark-runner-support.mjs +200 -0
- package/scripts/benchmark-session.mjs +95 -41
- package/scripts/configure-model-scope.sh +4 -3
- package/scripts/configure-subagents.sh +42 -34
- package/scripts/helper-evaluation.mjs +2 -2
- package/scripts/install-global.sh +2 -2
- package/scripts/pi-usage-history.mjs +37 -10
- package/scripts/setup.sh +2 -2
- package/scripts/team-doctor.sh +1 -1
- package/scripts/verify-local.sh +10 -1
- package/templates/global/settings.json +2 -1
- package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<meta name="csp-nonce" content="__PIAGENT_CSP_NONCE__" />
|
|
8
8
|
<meta name="piagent-webui-mode" content="__PIAGENT_WEBUI_MODE__" />
|
|
9
9
|
<title>Piagent WebUI</title>
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-CgOEyRPG.js"></script>
|
|
11
11
|
<link rel="stylesheet" crossorigin href="/assets/index-BV-_03yl.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
@@ -446,7 +446,9 @@ export default function piagentWebUiExtension(pi: ExtensionAPI): void {
|
|
|
446
446
|
pi.on("tool_execution_end", (event, ctx) => { publishDrafts(ctx, stream.toolEnded(event, bridge.snapshot())); bridgeSoft(() => lifecycle.observeToolEnd(ctx)); });
|
|
447
447
|
pi.on("turn_end", (event, ctx) => publishDrafts(ctx, stream.turnEnded(event, bridge.snapshot())));
|
|
448
448
|
pi.on("agent_settled", (_event, ctx) => {
|
|
449
|
-
|
|
449
|
+
const hasPendingMessages = typeof ctx.hasPendingMessages === "function" ? ctx.hasPendingMessages() : null;
|
|
450
|
+
publishDrafts(ctx, stream.agentSettled(bridge.snapshot(), hasPendingMessages));
|
|
451
|
+
if (hasPendingMessages === true) { publishPersisted(ctx); return; }
|
|
450
452
|
const operationId = bridge.snapshot().identity?.agentOperationId ?? null;
|
|
451
453
|
bridgeSoft(() => bridge.observeAgentSettled(ctx)); bridgeSoft(() => lifecycle.observeAgentSettled(ctx, operationId)); publishPersisted(ctx);
|
|
452
454
|
});
|
|
@@ -317,7 +317,7 @@ export class SameSessionPiBridge {
|
|
|
317
317
|
}
|
|
318
318
|
observeAgentStart(ctx: ExtensionContext): void {
|
|
319
319
|
if (!this.refresh(ctx) || !this.#binding || this.#binding.state !== "ready") return;
|
|
320
|
-
const pending = this.#pending;
|
|
320
|
+
const pending = this.#pending; if (this.#binding.identity.agentOperationId && !pending) return;
|
|
321
321
|
const operationId = pending?.operationId ?? this.#revision("operation", ++this.#operationCounter, "external");
|
|
322
322
|
this.#binding.identity.agentOperationId = operationId; this.#refresh(); this.#event("operation.started", { operationRef: operationId, commandId: pending?.command.commandId });
|
|
323
323
|
const observedEntry = pending ? dispatchEntry(pending, ctx) : null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import { redactSensitiveText } from "../../piagent-core/extensions/redaction-core.js";
|
|
4
|
+
import { classifyToolFailure, handledToolFailure } from "../../piagent-core/runtime/inspection/tool-failure-classification.ts";
|
|
4
5
|
import type { RuntimeEventDraft, RuntimeEventRevision } from "../../piagent-core/runtime/inspection/runtime-event-store.ts";
|
|
5
6
|
import type { BridgeIdentity, BridgeSnapshot } from "./same-session-bridge.ts";
|
|
6
7
|
|
|
@@ -35,6 +36,13 @@ function safeToolName(value: unknown): string {
|
|
|
35
36
|
if (projected.redacted) return "redacted-tool";
|
|
36
37
|
return projected.text.replace(/[^A-Za-z0-9._:-]/g, "-").replace(/^[^A-Za-z0-9]+/, "").slice(0, 128) || "tool";
|
|
37
38
|
}
|
|
39
|
+
function toolClassificationInput(value: unknown): { command: string } | undefined {
|
|
40
|
+
if (!value || typeof value !== "object") return undefined;
|
|
41
|
+
const direct = (value as { command?: unknown }).command;
|
|
42
|
+
const nested = (value as { args?: { command?: unknown } }).args?.command;
|
|
43
|
+
const command = typeof direct === "string" ? direct : typeof nested === "string" ? nested : null;
|
|
44
|
+
return command === null ? undefined : { command: command.slice(0, 8_192) };
|
|
45
|
+
}
|
|
38
46
|
function role(message: any): MessageRole | null {
|
|
39
47
|
return message?.role === "user" ? "user" : message?.role === "assistant" ? "assistant" : message?.role === "toolResult" ? "tool-result" : null;
|
|
40
48
|
}
|
|
@@ -68,10 +76,11 @@ function redaction(applied = false, truncated = false) { return { applied, value
|
|
|
68
76
|
function identityReady(identity: BridgeIdentity | null): identity is BridgeIdentity & { agentOperationId: string } {
|
|
69
77
|
return Boolean(identity?.agentOperationId);
|
|
70
78
|
}
|
|
71
|
-
function activityPayload(state: "started" | "progress" | "finished" | "failed", activityRef: string, toolName: string
|
|
79
|
+
function activityPayload(state: "started" | "progress" | "finished" | "failed", activityRef: string, toolName: string,
|
|
80
|
+
reasonCode: string | null = null) {
|
|
72
81
|
return { state, activityType: "tool", activityRef, toolName, inputDigest: null, outputDigest: null, preview: null, previewKind: "none",
|
|
73
82
|
outputBytes: null, outputLines: null, exitCode: null, isError: state === "finished" ? false : state === "failed" ? true : null,
|
|
74
|
-
affectedFileRefs: [], criterionIds: [], verifierAttemptIds: [], reasonCode
|
|
83
|
+
affectedFileRefs: [], criterionIds: [], verifierAttemptIds: [], reasonCode };
|
|
75
84
|
}
|
|
76
85
|
|
|
77
86
|
export class PiSessionStreamAdapter {
|
|
@@ -81,12 +90,13 @@ export class PiSessionStreamAdapter {
|
|
|
81
90
|
#activeByRole = new Map<MessageRole, ActiveMessage>();
|
|
82
91
|
#lastMessageRef: string | null = null;
|
|
83
92
|
#toolRefs = new Map<string, string>();
|
|
93
|
+
#toolInputs = new Map<string, { command: string } | undefined>();
|
|
84
94
|
#lastToolProgress = new Map<string, number>();
|
|
85
95
|
|
|
86
96
|
constructor(options: { now?: () => Date } = {}) { this.#now = options.now ?? (() => new Date()); }
|
|
87
97
|
reset(): void {
|
|
88
98
|
this.#turnIndex = null; this.#messageCounter = 0; this.#activeByRole.clear(); this.#lastMessageRef = null;
|
|
89
|
-
this.#toolRefs.clear(); this.#lastToolProgress.clear();
|
|
99
|
+
this.#toolRefs.clear(); this.#toolInputs.clear(); this.#lastToolProgress.clear();
|
|
90
100
|
}
|
|
91
101
|
|
|
92
102
|
#base(snapshot: BridgeSnapshot, sourceObservedAt?: unknown): Omit<RuntimeEventDraft, "kind" | "payload" | "evidence" | "redaction"> | null {
|
|
@@ -210,13 +220,14 @@ export class PiSessionStreamAdapter {
|
|
|
210
220
|
const draft = this.#draft(snapshot, "turn.ended", { phase: "ended", finalMessageRef: this.#lastMessageRef, toolResultCount: count, stopReason: reason });
|
|
211
221
|
return draft ? [draft] : [];
|
|
212
222
|
}
|
|
213
|
-
toolStarted(event: { toolCallId?: unknown; toolName?: unknown }, snapshot: BridgeSnapshot): RuntimeEventDraft[] {
|
|
223
|
+
toolStarted(event: { toolCallId?: unknown; toolName?: unknown; args?: unknown }, snapshot: BridgeSnapshot): RuntimeEventDraft[] {
|
|
214
224
|
if (typeof event.toolCallId !== "string" || !event.toolCallId) return [];
|
|
215
225
|
const drafts = this.#activeByRole.get("assistant")
|
|
216
226
|
? this.#drainLines(snapshot, this.#activeByRole.get("assistant") as ActiveMessage, false) : [];
|
|
217
227
|
const name = safeToolName(event.toolName);
|
|
218
228
|
const ref = opaque("tool", [snapshot.identity?.sessionRef, event.toolCallId]), activityRef = opaque("activity", [snapshot.identity?.sessionRef, event.toolCallId]);
|
|
219
|
-
this.#toolRefs.set(event.toolCallId, ref); this.#
|
|
229
|
+
this.#toolRefs.set(event.toolCallId, ref); this.#toolInputs.set(event.toolCallId, toolClassificationInput(event.args));
|
|
230
|
+
this.#lastToolProgress.set(event.toolCallId, 0);
|
|
220
231
|
const draft = this.#draft(snapshot, "activity.started", activityPayload("started", activityRef, name), { toolCallId: ref });
|
|
221
232
|
if (draft) drafts.push(draft);
|
|
222
233
|
return drafts;
|
|
@@ -231,19 +242,31 @@ export class PiSessionStreamAdapter {
|
|
|
231
242
|
const draft = this.#draft(snapshot, "activity.progress", activityPayload("progress", activityRef, name), { toolCallId: ref });
|
|
232
243
|
return draft ? [draft] : [];
|
|
233
244
|
}
|
|
234
|
-
toolEnded(event: { toolCallId?: unknown; toolName?: unknown; isError?: unknown }, snapshot: BridgeSnapshot): RuntimeEventDraft[] {
|
|
245
|
+
toolEnded(event: { toolCallId?: unknown; toolName?: unknown; result?: unknown; isError?: unknown }, snapshot: BridgeSnapshot): RuntimeEventDraft[] {
|
|
235
246
|
if (typeof event.toolCallId !== "string") return [];
|
|
236
247
|
const ref = this.#toolRefs.get(event.toolCallId); if (!ref) return [];
|
|
237
248
|
const name = safeToolName(event.toolName);
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
const
|
|
249
|
+
const reasonCode = classifyToolFailure(name, event.isError === true, event.result, this.#toolInputs.get(event.toolCallId));
|
|
250
|
+
const state = (event.isError === true || reasonCode !== null) && !handledToolFailure(reasonCode) ? "failed" : "finished";
|
|
251
|
+
const activityRef = opaque("activity", [snapshot.identity?.sessionRef, event.toolCallId]);
|
|
252
|
+
this.#toolRefs.delete(event.toolCallId); this.#toolInputs.delete(event.toolCallId); this.#lastToolProgress.delete(event.toolCallId);
|
|
253
|
+
const draft = this.#draft(snapshot, `activity.${state}`, activityPayload(state, activityRef, name, reasonCode), { toolCallId: ref });
|
|
241
254
|
return draft ? [draft] : [];
|
|
242
255
|
}
|
|
243
256
|
agentSettled(snapshot: BridgeSnapshot, hasPendingMessages: boolean | null): RuntimeEventDraft[] {
|
|
257
|
+
// A queued follow-up belongs to the same logical operation. Do not emit a
|
|
258
|
+
// terminal edge or clear stream identity between recovery turns.
|
|
259
|
+
if (hasPendingMessages === true) return [];
|
|
244
260
|
const value = hasPendingMessages === null ? { state: "unknown", value: null, reasonCode: "queue-fact-unavailable" }
|
|
245
261
|
: { state: "known", value: hasPendingMessages, reasonCode: null };
|
|
246
|
-
|
|
262
|
+
// Host-idle only proves that this agent operation stopped producing turns.
|
|
263
|
+
// A governed task is successful only after its durable contract is terminal.
|
|
264
|
+
// Fail closed here so a missed/late completion hook cannot paint an open
|
|
265
|
+
// task as completed in the WebUI.
|
|
266
|
+
const governedTaskOpen = Boolean(snapshot.identity?.taskRunId) && snapshot.taskState !== "terminal";
|
|
267
|
+
const draft = this.#draft(snapshot, "agent-operation.settled", {
|
|
268
|
+
settlement: governedTaskOpen ? "unknown" : "completed", lastStopReason: null, hasPendingMessages: value
|
|
269
|
+
});
|
|
247
270
|
this.reset(); return draft ? [draft] : [];
|
|
248
271
|
}
|
|
249
272
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { redactSensitiveText } from "../../piagent-core/security/sensitive-data.js";
|
|
2
|
+
|
|
1
3
|
export type GatewayEventKind = "catalog.changed" | "session.changed" | "runtime.changed" | "message.delta"
|
|
2
|
-
| "message.completed" | "tool.started" | "tool.completed";
|
|
4
|
+
| "message.completed" | "operation.settled" | "tool.started" | "tool.completed";
|
|
3
5
|
|
|
4
6
|
export type GatewayProtocolEvent = {
|
|
5
7
|
schemaVersion: 1;
|
|
@@ -12,16 +14,47 @@ export type GatewayProtocolEvent = {
|
|
|
12
14
|
payload: Record<string, unknown>;
|
|
13
15
|
};
|
|
14
16
|
|
|
17
|
+
export type GatewayTerminalSettlement = {
|
|
18
|
+
sessionRef: string;
|
|
19
|
+
operationRef: string;
|
|
20
|
+
settlement: "blocked" | "aborted" | "error" | "unknown";
|
|
21
|
+
reasonCode: string;
|
|
22
|
+
settledAt: string;
|
|
23
|
+
sequence: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const OPAQUE_REF_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._~-]{0,159}$/;
|
|
27
|
+
const REASON_CODE_PATTERN = /^[a-z0-9][a-z0-9.-]{0,95}$/;
|
|
28
|
+
const TERMINAL_SETTLEMENTS = new Set(["completed", "blocked", "aborted", "error", "unknown"]);
|
|
29
|
+
const SETTLEMENT_RETENTION_COUNT = 100;
|
|
30
|
+
const SETTLEMENT_RETENTION_MS = 60 * 60_000;
|
|
31
|
+
const MAX_SETTLEMENT_DECISION_COUNT = 10_000;
|
|
32
|
+
type GatewaySettlementDecision = { settledAt: string; settlement: "completed" | GatewayTerminalSettlement["settlement"];
|
|
33
|
+
reasonCode: string | null };
|
|
34
|
+
|
|
35
|
+
function settlementReason(settlement: GatewayTerminalSettlement["settlement"], value: unknown): string {
|
|
36
|
+
if (typeof value === "string" && REASON_CODE_PATTERN.test(value) && !redactSensitiveText(value).redacted) return value;
|
|
37
|
+
if (settlement === "aborted") return "operation-aborted";
|
|
38
|
+
if (settlement === "blocked") return "operation-blocked";
|
|
39
|
+
if (settlement === "error") return "operation-failed";
|
|
40
|
+
return "operation-settlement-unknown";
|
|
41
|
+
}
|
|
42
|
+
|
|
15
43
|
export class GatewayEventStore {
|
|
16
44
|
readonly #maximumCount: number;
|
|
17
45
|
readonly #maximumAgeMs: number;
|
|
46
|
+
readonly #settlementDecisionCount: number;
|
|
18
47
|
#events: GatewayProtocolEvent[] = [];
|
|
48
|
+
#settlements: GatewayTerminalSettlement[] = [];
|
|
49
|
+
#settlementDecisions = new Map<string, GatewaySettlementDecision>();
|
|
19
50
|
#sequence = 0;
|
|
20
51
|
#listeners = new Set<(event: GatewayProtocolEvent) => void>();
|
|
21
52
|
|
|
22
|
-
constructor(options: { maximumCount?: number; maximumAgeMs?: number } = {}) {
|
|
53
|
+
constructor(options: { maximumCount?: number; maximumAgeMs?: number; settlementDecisionCount?: number } = {}) {
|
|
23
54
|
this.#maximumCount = Math.max(1, Math.min(10_000, options.maximumCount ?? 1_000));
|
|
24
55
|
this.#maximumAgeMs = Math.max(1_000, Math.min(24 * 60 * 60_000, options.maximumAgeMs ?? 60 * 60_000));
|
|
56
|
+
this.#settlementDecisionCount = Math.max(1,
|
|
57
|
+
Math.min(MAX_SETTLEMENT_DECISION_COUNT, Math.floor(options.settlementDecisionCount ?? MAX_SETTLEMENT_DECISION_COUNT)));
|
|
25
58
|
}
|
|
26
59
|
|
|
27
60
|
get stateVersion(): number { return this.#sequence; }
|
|
@@ -31,14 +64,74 @@ export class GatewayEventStore {
|
|
|
31
64
|
while (this.#events.length > this.#maximumCount || this.#events[0] && Date.parse(this.#events[0].generatedAt) < cutoff) this.#events.shift();
|
|
32
65
|
}
|
|
33
66
|
|
|
67
|
+
#pruneSettlements(now: number): void {
|
|
68
|
+
const cutoff = now - SETTLEMENT_RETENTION_MS;
|
|
69
|
+
this.#settlements = this.#settlements.filter((item) => Date.parse(item.settledAt) >= cutoff);
|
|
70
|
+
if (this.#settlements.length > SETTLEMENT_RETENTION_COUNT) {
|
|
71
|
+
this.#settlements.splice(0, this.#settlements.length - SETTLEMENT_RETENTION_COUNT);
|
|
72
|
+
}
|
|
73
|
+
for (const [key, decision] of this.#settlementDecisions) {
|
|
74
|
+
if (Date.parse(decision.settledAt) < cutoff) this.#settlementDecisions.delete(key);
|
|
75
|
+
}
|
|
76
|
+
while (this.#settlementDecisions.size > this.#settlementDecisionCount) {
|
|
77
|
+
this.#settlementDecisions.delete(this.#settlementDecisions.keys().next().value as string);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
#settlementDecision(sessionRef: string, operationRef: string): GatewaySettlementDecision | undefined {
|
|
82
|
+
const key = `${sessionRef}\u0000${operationRef}`;
|
|
83
|
+
const indexed = this.#settlementDecisions.get(key);
|
|
84
|
+
if (indexed) return indexed;
|
|
85
|
+
const retained = this.#settlements.find((item) => item.sessionRef === sessionRef && item.operationRef === operationRef);
|
|
86
|
+
if (!retained) return undefined;
|
|
87
|
+
const restored = { settledAt: retained.settledAt, settlement: retained.settlement, reasonCode: retained.reasonCode };
|
|
88
|
+
this.#settlementDecisions.set(key, restored);
|
|
89
|
+
return restored;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#recordSettlement(event: GatewayProtocolEvent, now: number): void {
|
|
93
|
+
if (event.kind !== "operation.settled") return;
|
|
94
|
+
const sessionRef = event.payload.sessionRef, operationRef = event.payload.operationRef, settlement = event.payload.settlement;
|
|
95
|
+
if (typeof sessionRef !== "string" || !OPAQUE_REF_PATTERN.test(sessionRef)
|
|
96
|
+
|| typeof operationRef !== "string" || !OPAQUE_REF_PATTERN.test(operationRef)
|
|
97
|
+
|| typeof settlement !== "string" || !TERMINAL_SETTLEMENTS.has(settlement)) return;
|
|
98
|
+
this.#pruneSettlements(now);
|
|
99
|
+
const key = `${sessionRef}\u0000${operationRef}`;
|
|
100
|
+
if (this.#settlementDecision(sessionRef, operationRef)) return;
|
|
101
|
+
this.#settlementDecisions.set(key, { settledAt: event.generatedAt,
|
|
102
|
+
settlement: settlement as GatewaySettlementDecision["settlement"],
|
|
103
|
+
reasonCode: settlement === "completed" ? null : String(event.payload.reasonCode) });
|
|
104
|
+
const retained = { sessionRef, operationRef, settledAt: event.generatedAt, sequence: event.sequence };
|
|
105
|
+
if (settlement !== "completed") {
|
|
106
|
+
const outcome = settlement as GatewayTerminalSettlement["settlement"];
|
|
107
|
+
this.#settlements.push({ ...retained, settlement: outcome, reasonCode: settlementReason(outcome, event.payload.reasonCode) });
|
|
108
|
+
}
|
|
109
|
+
this.#pruneSettlements(now);
|
|
110
|
+
}
|
|
111
|
+
|
|
34
112
|
publish(kind: GatewayEventKind, payload: Record<string, unknown>, now = new Date()): GatewayProtocolEvent {
|
|
35
113
|
if (!Number.isFinite(now.getTime())) throw new Error("gateway-event-time-invalid");
|
|
114
|
+
if (kind === "operation.settled") {
|
|
115
|
+
if (payload.settlement === "completed") payload = { ...payload, reasonCode: null };
|
|
116
|
+
else {
|
|
117
|
+
const settlement = ["blocked", "aborted", "error", "unknown"].includes(String(payload.settlement))
|
|
118
|
+
? payload.settlement as GatewayTerminalSettlement["settlement"] : "unknown";
|
|
119
|
+
payload = { ...payload, settlement, reasonCode: settlementReason(settlement, payload.reasonCode) };
|
|
120
|
+
}
|
|
121
|
+
this.#pruneSettlements(now.getTime());
|
|
122
|
+
const sessionRef = payload.sessionRef, operationRef = payload.operationRef;
|
|
123
|
+
if (typeof sessionRef === "string" && OPAQUE_REF_PATTERN.test(sessionRef)
|
|
124
|
+
&& typeof operationRef === "string" && OPAQUE_REF_PATTERN.test(operationRef)) {
|
|
125
|
+
const canonical = this.#settlementDecision(sessionRef, operationRef);
|
|
126
|
+
if (canonical) payload = { ...payload, settlement: canonical.settlement, reasonCode: canonical.reasonCode };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
36
129
|
this.#sequence += 1;
|
|
37
130
|
const event: GatewayProtocolEvent = {
|
|
38
131
|
schemaVersion: 1, version: "piagent-gateway-protocol-v1", messageType: "event",
|
|
39
132
|
sequence: this.#sequence, stateVersion: this.#sequence, generatedAt: now.toISOString(), kind, payload
|
|
40
133
|
};
|
|
41
|
-
this.#events.push(event); this.#prune(now.getTime());
|
|
134
|
+
this.#events.push(event); this.#prune(now.getTime()); this.#recordSettlement(event, now.getTime());
|
|
42
135
|
for (const listener of this.#listeners) { try { listener(event); } catch { /* observers cannot block truth */ } }
|
|
43
136
|
return event;
|
|
44
137
|
}
|
|
@@ -48,10 +141,21 @@ export class GatewayEventStore {
|
|
|
48
141
|
return () => { this.#listeners.delete(listener); };
|
|
49
142
|
}
|
|
50
143
|
|
|
144
|
+
recentOperationSettlements(maximum = 100, now = new Date()): GatewayTerminalSettlement[] {
|
|
145
|
+
if (!Number.isFinite(now.getTime())) throw new Error("gateway-settlement-time-invalid");
|
|
146
|
+
const limit = Math.max(0, Math.min(100, Math.floor(maximum)));
|
|
147
|
+
if (limit === 0) return [];
|
|
148
|
+
this.#pruneSettlements(now.getTime());
|
|
149
|
+
return [...this.#settlements].sort((left, right) => right.sequence - left.sequence).slice(0, limit).map((item) => ({ ...item }));
|
|
150
|
+
}
|
|
151
|
+
|
|
51
152
|
replay(after: number | null): { state: "current" | "resync-required"; events: GatewayProtocolEvent[]; earliestSequence: number; currentSequence: number } {
|
|
52
153
|
this.#prune(Date.now());
|
|
53
|
-
|
|
54
|
-
|
|
154
|
+
// An empty retained ring after pruning contains no event at `#sequence`.
|
|
155
|
+
// Treat the next sequence as the retention boundary so a client one event
|
|
156
|
+
// behind must resync instead of silently receiving an empty replay.
|
|
157
|
+
const earliestSequence = this.#events[0]?.sequence ?? Math.min(Number.MAX_SAFE_INTEGER, this.#sequence + 1);
|
|
158
|
+
if (after !== null && (after > this.#sequence || after < Math.max(0, earliestSequence - 1))) {
|
|
55
159
|
return { state: "resync-required", events: [], earliestSequence, currentSequence: this.#sequence };
|
|
56
160
|
}
|
|
57
161
|
return { state: "current", events: after === null ? [] : this.#events.filter((event) => event.sequence > after), earliestSequence,
|
|
@@ -21,6 +21,7 @@ import { SessionMetadataStore } from "./session-metadata-store.ts";
|
|
|
21
21
|
import { GatewayProtocolService } from "./gateway-protocol-service.ts";
|
|
22
22
|
import { SessionLeaseStore } from "./session-lease-store.ts";
|
|
23
23
|
import { SessionRuntimeSupervisor } from "./session-runtime-supervisor.ts";
|
|
24
|
+
import { buildSessionLiveState } from "./session-live-state.ts";
|
|
24
25
|
import { SessionCommandStore } from "./session-command-store.ts";
|
|
25
26
|
import { SessionCommandController } from "./session-command-controller.ts";
|
|
26
27
|
import { RuntimeCommandController } from "./runtime-command-controller.ts";
|
|
@@ -192,6 +193,8 @@ export async function startPiagentGateway(options: {
|
|
|
192
193
|
mode: "gateway",
|
|
193
194
|
readCapabilities: () => capabilities(gatewayInstanceRef, true),
|
|
194
195
|
readSessionCatalog: readCatalog,
|
|
196
|
+
readSessionLiveState: () => buildSessionLiveState({ gatewayInstanceRef, eventSequence: events.stateVersion,
|
|
197
|
+
operations: runtimes!.currentOperations(), settlements: events.recentOperationSettlements() }),
|
|
195
198
|
readSessionCreationOptions: () => inspections.creationOptions(),
|
|
196
199
|
readSessionModel: (sessionRef) => inspections.provider(sessionRef),
|
|
197
200
|
readSessionConnections: (sessionRef) => inspections.connections(sessionRef),
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
import { redactSensitiveText } from "../../piagent-core/security/sensitive-data.js";
|
|
4
|
+
import { hasVisibleText } from "../shared/text-visibility.ts";
|
|
4
5
|
import { GatewayEventStore } from "./gateway-events.ts";
|
|
6
|
+
import { SessionOperationLifecycle, type SessionOperationObservation,
|
|
7
|
+
type SessionOperationPhase, type SessionOperationRetryPolicyOptions } from "./session-operation-lifecycle.ts";
|
|
5
8
|
|
|
6
9
|
const MAX_BUFFER = 65_536;
|
|
7
10
|
const MAX_EVENTS = 128;
|
|
8
11
|
const MAX_DELTA = 16_384;
|
|
9
12
|
const PRIVATE_BEGIN = /-----BEGIN [A-Z ]*PRIVATE KEY-----/;
|
|
10
13
|
const PRIVATE_END = /-----END [A-Z ]*PRIVATE KEY-----/;
|
|
14
|
+
const RUNTIME_DRIFT = /Installed Piagent runtime changed (?:during this session|while the capability lock was being re-pinned)/i;
|
|
15
|
+
const COMPLETION_GATE = /^\[Piagent completion gate: (CONTINUING|NOT APPROVED)\]/i;
|
|
16
|
+
|
|
17
|
+
export type GatewayOperationSettlement = "completed" | "blocked" | "aborted" | "error" | "unknown";
|
|
18
|
+
type Settlement = { outcome: GatewayOperationSettlement; reasonCode: string | null };
|
|
11
19
|
|
|
12
20
|
function clean(value: unknown): string {
|
|
13
|
-
return String(value ?? "").replace(/\u001b(
|
|
21
|
+
return String(value ?? "").replace(/\u001b(?:\[[0-?]*[ -/]*[@-~]|[@-_])/g, "")
|
|
14
22
|
.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, " ");
|
|
15
23
|
}
|
|
16
24
|
function safeTool(value: unknown): string {
|
|
@@ -19,6 +27,44 @@ function safeTool(value: unknown): string {
|
|
|
19
27
|
return safe.text.replace(/[^A-Za-z0-9._:-]/g, "-").replace(/^[^A-Za-z0-9]+/, "").slice(0, 160) || "tool";
|
|
20
28
|
}
|
|
21
29
|
function ref(prefix: string): string { return `${prefix}_${randomBytes(18).toString("base64url")}`; }
|
|
30
|
+
function messageText(message: any): string {
|
|
31
|
+
if (typeof message?.content === "string") return message.content;
|
|
32
|
+
if (!Array.isArray(message?.content)) return "";
|
|
33
|
+
return message.content.filter((item: any) => item?.type === "text" && typeof item.text === "string")
|
|
34
|
+
.map((item: any) => item.text).join("\n");
|
|
35
|
+
}
|
|
36
|
+
function assistantSettlement(message: any): Settlement {
|
|
37
|
+
const stopReason = String(message?.stopReason ?? "");
|
|
38
|
+
if (stopReason === "aborted") return { outcome: "aborted", reasonCode: "operation-aborted" };
|
|
39
|
+
if (stopReason === "error") return { outcome: "error", reasonCode: "assistant-response-failed" };
|
|
40
|
+
if (stopReason === "length") return { outcome: "error", reasonCode: "assistant-output-incomplete" };
|
|
41
|
+
const gate = COMPLETION_GATE.exec(messageText(message).trimStart());
|
|
42
|
+
if (gate) return { outcome: "blocked", reasonCode: gate[1]?.toUpperCase() === "CONTINUING"
|
|
43
|
+
? "completion-gate-continuing" : "completion-gate-not-approved" };
|
|
44
|
+
if (stopReason === "stop") return hasVisibleText(redactSensitiveText(clean(messageText(message))).text)
|
|
45
|
+
? { outcome: "completed", reasonCode: null }
|
|
46
|
+
: { outcome: "unknown", reasonCode: "assistant-message-empty" };
|
|
47
|
+
if (["toolUse", "pending", "deferred"].includes(stopReason)) return { outcome: "unknown", reasonCode: "assistant-message-intermediate" };
|
|
48
|
+
return { outcome: "unknown", reasonCode: "operation-settlement-unknown" };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function containsRuntimeDrift(value: unknown, depth = 0, remaining = { value: 16_384 }): boolean {
|
|
52
|
+
if (depth > 5 || remaining.value <= 0 || value === null || value === undefined) return false;
|
|
53
|
+
if (typeof value === "string") {
|
|
54
|
+
const text = value.slice(0, remaining.value); remaining.value -= text.length;
|
|
55
|
+
return RUNTIME_DRIFT.test(text);
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(value)) return value.some((item) => containsRuntimeDrift(item, depth + 1, remaining));
|
|
58
|
+
if (typeof value !== "object") return false;
|
|
59
|
+
const record = value as Record<string, unknown>;
|
|
60
|
+
return ["reason", "message", "text", "content", "result", "details"]
|
|
61
|
+
.some((key) => containsRuntimeDrift(record[key], depth + 1, remaining));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function runtimeRestartReasonCode(event: unknown): "runtime-restart-required" | null {
|
|
65
|
+
if (!event || typeof event !== "object" || (event as Record<string, unknown>).isError !== true) return null;
|
|
66
|
+
return containsRuntimeDrift((event as Record<string, unknown>).result) ? "runtime-restart-required" : null;
|
|
67
|
+
}
|
|
22
68
|
|
|
23
69
|
export class GatewaySessionStream {
|
|
24
70
|
readonly sessionRef: string;
|
|
@@ -29,57 +75,91 @@ export class GatewaySessionStream {
|
|
|
29
75
|
#eventCount = 0;
|
|
30
76
|
#buffer = "";
|
|
31
77
|
#truncated = false;
|
|
32
|
-
#started = false;
|
|
33
78
|
#startListeners: Array<() => void> = [];
|
|
34
|
-
#
|
|
79
|
+
#runtimeRestartRequired = false;
|
|
80
|
+
#lastMessageRef: string | null = null;
|
|
81
|
+
#settlement: Settlement = { outcome: "unknown", reasonCode: "operation-settlement-unknown" };
|
|
82
|
+
#forcedSettlement: Settlement | null = null;
|
|
83
|
+
#lifecycleTerminationReasonCode: string | null = null;
|
|
35
84
|
#settleListeners: Array<() => void> = [];
|
|
36
85
|
readonly #toolRefs = new Map<string, string>();
|
|
86
|
+
readonly #lifecycle: SessionOperationLifecycle;
|
|
37
87
|
|
|
38
|
-
|
|
88
|
+
get runtimeRestartRequired(): boolean { return this.#runtimeRestartRequired; }
|
|
89
|
+
get lifecycleTerminationReasonCode(): string | null { return this.#lifecycleTerminationReasonCode; }
|
|
90
|
+
get operationPhase(): SessionOperationPhase { return this.#lifecycle.phase; }
|
|
91
|
+
get retryAbortRequired(): boolean { return this.#lifecycle.retryAbortRequired; }
|
|
92
|
+
|
|
93
|
+
markAborted(reasonCode = "operation-aborted"): void { this.#forcedSettlement = { outcome: "aborted", reasonCode }; }
|
|
94
|
+
markError(reasonCode = "operation-failed"): void {
|
|
95
|
+
if (!this.#forcedSettlement) this.#forcedSettlement = { outcome: "error", reasonCode };
|
|
96
|
+
}
|
|
97
|
+
forceLifecycleTermination(reasonCode: string): void {
|
|
98
|
+
this.#lifecycleTerminationReasonCode ??= reasonCode;
|
|
99
|
+
const wasStarted = this.#lifecycle.started, wasSettled = this.#lifecycle.hostSettled;
|
|
100
|
+
this.#lifecycle.forceHostSettlement();
|
|
101
|
+
if (!wasStarted) for (const resolve of this.#startListeners.splice(0)) resolve();
|
|
102
|
+
if (!wasSettled) for (const resolve of this.#settleListeners.splice(0)) resolve();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
constructor(options: { sessionRef: string; operationRef: string; events: GatewayEventStore;
|
|
106
|
+
retryPolicy?: SessionOperationRetryPolicyOptions }) {
|
|
39
107
|
this.sessionRef = options.sessionRef; this.operationRef = options.operationRef; this.#events = options.events;
|
|
108
|
+
this.#lifecycle = new SessionOperationLifecycle({ operationRef: options.operationRef, retryPolicy: options.retryPolicy });
|
|
40
109
|
}
|
|
41
110
|
|
|
42
111
|
started(): Promise<void> {
|
|
43
|
-
if (this.#started) return Promise.resolve();
|
|
112
|
+
if (this.#lifecycle.started) return Promise.resolve();
|
|
44
113
|
return new Promise((resolve) => this.#startListeners.push(resolve));
|
|
45
114
|
}
|
|
46
115
|
|
|
47
116
|
settled(): Promise<void> {
|
|
48
|
-
if (this.#
|
|
117
|
+
if (this.#lifecycle.hostSettled) return Promise.resolve();
|
|
49
118
|
return new Promise((resolve) => this.#settleListeners.push(resolve));
|
|
50
119
|
}
|
|
51
120
|
|
|
52
|
-
observe(event: any):
|
|
121
|
+
observe(event: any): SessionOperationObservation {
|
|
122
|
+
const wasStarted = this.#lifecycle.started, wasSettled = this.#lifecycle.hostSettled;
|
|
123
|
+
const observation = this.#lifecycle.observe(event);
|
|
124
|
+
if (!observation.accepted) return observation;
|
|
125
|
+
if (!wasStarted && this.#lifecycle.started) for (const resolve of this.#startListeners.splice(0)) resolve();
|
|
126
|
+
if (!wasSettled && this.#lifecycle.hostSettled) for (const resolve of this.#settleListeners.splice(0)) resolve();
|
|
127
|
+
if (observation.retry === "abort") this.markError(observation.reasonCode ?? "automatic-retry-blocked");
|
|
53
128
|
if (event?.type === "agent_start") {
|
|
54
|
-
|
|
129
|
+
return observation;
|
|
55
130
|
}
|
|
56
131
|
if (event?.type === "agent_settled") {
|
|
57
|
-
|
|
132
|
+
return observation;
|
|
58
133
|
}
|
|
59
134
|
if (event?.type === "message_start" && event.message?.role === "assistant") {
|
|
60
|
-
this.#messageRef = ref("message"); this.#buffer = ""; return;
|
|
135
|
+
this.#messageRef = ref("message"); this.#buffer = ""; return observation;
|
|
61
136
|
}
|
|
62
137
|
if (event?.type === "message_update" && event.assistantMessageEvent?.type === "text_delta"
|
|
63
138
|
&& typeof event.assistantMessageEvent.delta === "string") {
|
|
64
139
|
const remaining = MAX_BUFFER - this.#buffer.length;
|
|
65
|
-
if (remaining <= 0) { this.#truncated = true; return; }
|
|
140
|
+
if (remaining <= 0) { this.#truncated = true; return observation; }
|
|
66
141
|
this.#buffer += event.assistantMessageEvent.delta.slice(0, remaining);
|
|
67
142
|
if (event.assistantMessageEvent.delta.length > remaining) this.#truncated = true;
|
|
68
|
-
this.#flush(false); return;
|
|
143
|
+
this.#flush(false); return observation;
|
|
144
|
+
}
|
|
145
|
+
if (event?.type === "message_end" && event.message?.role === "assistant") {
|
|
146
|
+
this.#flush(true); this.#lastMessageRef = this.#messageRef; this.#settlement = assistantSettlement(event.message); return observation;
|
|
69
147
|
}
|
|
70
|
-
if (event?.type === "message_end" && event.message?.role === "assistant") { this.#flush(true); return; }
|
|
71
148
|
if (event?.type === "tool_execution_start" && typeof event.toolCallId === "string") {
|
|
72
149
|
const toolCallRef = ref("tool"); this.#toolRefs.set(event.toolCallId, toolCallRef);
|
|
73
150
|
this.#events.publish("tool.started", { sessionRef: this.sessionRef, operationRef: this.operationRef, toolCallRef,
|
|
74
|
-
toolLabel: safeTool(event.toolName), isError: null });
|
|
75
|
-
return;
|
|
151
|
+
toolLabel: safeTool(event.toolName), isError: null, reasonCode: null });
|
|
152
|
+
return observation;
|
|
76
153
|
}
|
|
77
154
|
if (event?.type === "tool_execution_end" && typeof event.toolCallId === "string") {
|
|
78
|
-
const toolCallRef = this.#toolRefs.get(event.toolCallId); if (!toolCallRef) return;
|
|
155
|
+
const toolCallRef = this.#toolRefs.get(event.toolCallId); if (!toolCallRef) return observation;
|
|
156
|
+
const reasonCode = runtimeRestartReasonCode(event);
|
|
157
|
+
if (reasonCode) this.#runtimeRestartRequired = true;
|
|
79
158
|
this.#events.publish("tool.completed", { sessionRef: this.sessionRef, operationRef: this.operationRef, toolCallRef,
|
|
80
|
-
toolLabel: safeTool(event.toolName), isError: event.isError === true });
|
|
159
|
+
toolLabel: safeTool(event.toolName), isError: event.isError === true, reasonCode });
|
|
81
160
|
this.#toolRefs.delete(event.toolCallId);
|
|
82
161
|
}
|
|
162
|
+
return observation;
|
|
83
163
|
}
|
|
84
164
|
|
|
85
165
|
#flush(final: boolean): void {
|
|
@@ -112,9 +192,31 @@ export class GatewaySessionStream {
|
|
|
112
192
|
}
|
|
113
193
|
}
|
|
114
194
|
|
|
115
|
-
complete(sessionRevision: string): void {
|
|
195
|
+
complete(sessionRevision: string | null, taskOutcome: string | null = null): void {
|
|
196
|
+
if (!this.#lifecycle.markTerminal()) return;
|
|
116
197
|
this.#flush(true);
|
|
117
|
-
|
|
118
|
-
|
|
198
|
+
let settlement = this.#forcedSettlement ?? (this.#runtimeRestartRequired
|
|
199
|
+
? { outcome: "unknown" as const, reasonCode: "runtime-restart-required" }
|
|
200
|
+
: this.#settlement);
|
|
201
|
+
const messageRef = this.#lastMessageRef ?? this.#messageRef;
|
|
202
|
+
if (settlement.outcome === "completed" && !messageRef) {
|
|
203
|
+
settlement = { outcome: "unknown", reasonCode: "assistant-message-unavailable" };
|
|
204
|
+
} else if (settlement.outcome === "completed" && taskOutcome === "pending") {
|
|
205
|
+
// Provider completion is not governed-task completion. The durable task
|
|
206
|
+
// contract is authoritative, so withhold both success settlement and the
|
|
207
|
+
// durable assistant-success event until that contract becomes terminal.
|
|
208
|
+
settlement = { outcome: "unknown", reasonCode: "task-completion-pending" };
|
|
209
|
+
} else if (settlement.outcome === "completed" && sessionRevision === null) {
|
|
210
|
+
// Streaming text is not durable success until the canonical session
|
|
211
|
+
// projection confirms it. Still terminate the UI, but do not show draft.
|
|
212
|
+
settlement = { outcome: "unknown", reasonCode: "session-projection-unavailable" };
|
|
213
|
+
}
|
|
214
|
+
if (settlement.outcome === "completed" && messageRef && sessionRevision) {
|
|
215
|
+
this.#events.publish("message.completed", { sessionRef: this.sessionRef, operationRef: this.operationRef,
|
|
216
|
+
messageRef, sessionRevision, truncated: this.#truncated });
|
|
217
|
+
}
|
|
218
|
+
this.#events.publish("operation.settled", { sessionRef: this.sessionRef, operationRef: this.operationRef,
|
|
219
|
+
messageRef, sessionRevision, settlement: settlement.outcome,
|
|
220
|
+
reasonCode: settlement.outcome === "completed" ? null : settlement.reasonCode ?? "operation-settlement-unknown" });
|
|
119
221
|
}
|
|
120
222
|
}
|
|
@@ -154,8 +154,18 @@ export class SessionCommandController {
|
|
|
154
154
|
| "renamed" | "pinned" | "unpinned" | "archived" | "unarchived" | "forked" | "no-change",
|
|
155
155
|
operationRef: string | null = null;
|
|
156
156
|
if (command.action === "session.create") {
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
const createWithReadback = (this.#runtimes as any).createWithReadback;
|
|
158
|
+
if (typeof createWithReadback === "function") {
|
|
159
|
+
const created = await createWithReadback.call(this.#runtimes, String(command.payload.projectRef), String(command.payload.placeRef),
|
|
160
|
+
command.payload.modelRef === null ? null : String(command.payload.modelRef), String(command.payload.thinkingLevel));
|
|
161
|
+
targetSessionRef = String(created.sessionRef);
|
|
162
|
+
if (created.effectiveOptions?.state !== "confirmed") throw new Error(
|
|
163
|
+
typeof created.effectiveOptions?.reasonCode === "string" ? created.effectiveOptions.reasonCode : "session-create-options-effect-unknown");
|
|
164
|
+
} else {
|
|
165
|
+
// Compatibility with older embedded supervisors and test doubles.
|
|
166
|
+
targetSessionRef = await this.#runtimes.create(String(command.payload.projectRef), String(command.payload.placeRef),
|
|
167
|
+
command.payload.modelRef === null ? null : String(command.payload.modelRef), String(command.payload.thinkingLevel));
|
|
168
|
+
}
|
|
159
169
|
if (command.payload.permissionMode !== undefined) {
|
|
160
170
|
await this.#runtimes.setPermission(targetSessionRef, String(command.payload.permissionMode) as
|
|
161
171
|
"read-only" | "workspace-write" | "trusted-full-access");
|
|
@@ -246,9 +256,14 @@ export class SessionCommandController {
|
|
|
246
256
|
const code = /recovery/.test(message) ? "recovery-required"
|
|
247
257
|
: /(owner-conflict|operation-conflict|runtime-busy)/.test(message) ? "owner-conflict"
|
|
248
258
|
: noDispatchEffect || command.action === "session.send" && !sendAttempted ? "unavailable" : "effect-unknown";
|
|
249
|
-
const
|
|
259
|
+
const specificCreateUncertainty = command.action === "session.create" && /^[a-z0-9][a-z0-9.-]{0,95}$/.test(message)
|
|
260
|
+
? message : "session-command-effect-unknown";
|
|
261
|
+
const receipt = code === "effect-unknown" ? this.#uncertain(command, after, afterRow, specificCreateUncertainty)
|
|
250
262
|
: this.#rejected(command, after, afterRow, code, message);
|
|
251
263
|
try { this.#store.settle(command, receipt, this.#now()); } catch { /* intent remains uncertain */ }
|
|
264
|
+
if (command.action === "session.create" && afterRow) {
|
|
265
|
+
this.#events.publish("session.changed", { catalogRevision: after.catalogRevision, session: afterRow });
|
|
266
|
+
}
|
|
252
267
|
return receipt;
|
|
253
268
|
}
|
|
254
269
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { webUiModelRef } from "../../piagent-core/runtime/inspection/webui-snapshot.ts";
|
|
2
|
+
|
|
3
|
+
export type EffectiveSessionOptions = {
|
|
4
|
+
state: "confirmed" | "mismatch" | "unknown";
|
|
5
|
+
modelRef: string | null;
|
|
6
|
+
thinkingLevel: string | null;
|
|
7
|
+
reasonCode: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
11
|
+
|
|
12
|
+
export function effectiveModelRef(session: any): string | null {
|
|
13
|
+
const model = session?.model;
|
|
14
|
+
if (!model || typeof model !== "object") return null;
|
|
15
|
+
const provider = String(model.provider ?? ""), id = String(model.id ?? model.modelId ?? "");
|
|
16
|
+
return provider && id ? webUiModelRef(provider, id) : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function effectiveThinkingLevel(session: any): string | null {
|
|
20
|
+
let value: unknown;
|
|
21
|
+
try { value = typeof session?.getThinkingLevel === "function" ? session.getThinkingLevel() : undefined; }
|
|
22
|
+
catch { return null; }
|
|
23
|
+
if (value === undefined || value === null || value === "") value = session?.thinkingLevel;
|
|
24
|
+
const level = String(value ?? "");
|
|
25
|
+
return THINKING_LEVELS.has(level) ? level : null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function configureSessionOptions(session: any, modelRef: string | null,
|
|
29
|
+
thinkingLevel: string): Promise<EffectiveSessionOptions> {
|
|
30
|
+
if (modelRef) {
|
|
31
|
+
const models = session.modelRuntime?.getAvailableSnapshot?.() ?? [];
|
|
32
|
+
const model = models.find((value: any) => webUiModelRef(String(value.provider ?? ""),
|
|
33
|
+
String(value.id ?? value.modelId ?? "")) === modelRef);
|
|
34
|
+
try { if (model) await session.setModel(model); } catch { /* Canonical read-back below decides the effect. */ }
|
|
35
|
+
}
|
|
36
|
+
try { await session.setThinkingLevel(thinkingLevel); } catch { /* Canonical read-back below decides the effect. */ }
|
|
37
|
+
const effectiveModel = effectiveModelRef(session), effectiveThinking = effectiveThinkingLevel(session);
|
|
38
|
+
if (!effectiveModel) return { state: "unknown", modelRef: null,
|
|
39
|
+
thinkingLevel: effectiveThinking, reasonCode: "session-model-effect-unknown" };
|
|
40
|
+
if (!effectiveThinking) return { state: "unknown", modelRef: effectiveModel,
|
|
41
|
+
thinkingLevel: null, reasonCode: "session-thinking-effect-unknown" };
|
|
42
|
+
if (modelRef && effectiveModel !== modelRef) return { state: "mismatch", modelRef: effectiveModel,
|
|
43
|
+
thinkingLevel: effectiveThinking, reasonCode: "session-model-mismatch" };
|
|
44
|
+
if (effectiveThinking !== thinkingLevel) return { state: "mismatch", modelRef: effectiveModel,
|
|
45
|
+
thinkingLevel: effectiveThinking, reasonCode: "session-thinking-mismatch" };
|
|
46
|
+
return { state: "confirmed", modelRef: effectiveModel, thinkingLevel: effectiveThinking, reasonCode: null };
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PiagentWebUICanonicalVolatileSessionOperationStateV1 } from "../contracts/generated/session-live-state-v1.ts";
|
|
2
|
+
import type { GatewayTerminalSettlement } from "./gateway-events.ts";
|
|
3
|
+
import type { CurrentOperationProjection } from "./session-runtime-supervisor.ts";
|
|
4
|
+
|
|
5
|
+
export function buildSessionLiveState(options: { gatewayInstanceRef: string; eventSequence: number;
|
|
6
|
+
operations: Array<CurrentOperationProjection & { sessionRef: string }>; settlements: GatewayTerminalSettlement[];
|
|
7
|
+
now?: Date }): PiagentWebUICanonicalVolatileSessionOperationStateV1 {
|
|
8
|
+
const now = options.now ?? new Date();
|
|
9
|
+
if (!Number.isFinite(now.getTime())) throw new Error("session-live-state-time-invalid");
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: 1,
|
|
12
|
+
version: "piagent-session-live-state-v1",
|
|
13
|
+
generatedAt: now.toISOString(),
|
|
14
|
+
gatewayInstanceRef: options.gatewayInstanceRef,
|
|
15
|
+
eventSequence: options.eventSequence,
|
|
16
|
+
state: "ready",
|
|
17
|
+
operations: [...options.operations].sort((left, right) => left.sessionRef.localeCompare(right.sessionRef)),
|
|
18
|
+
settlements: [...options.settlements].sort((left, right) => right.sequence - left.sequence).slice(0, 100),
|
|
19
|
+
reasonCode: null
|
|
20
|
+
};
|
|
21
|
+
}
|