@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
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
import { createHmac, randomBytes } from "node:crypto";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import { webUiModelRef } from "../../piagent-core/runtime/inspection/webui-snapshot.ts";
|
|
5
|
-
import { webUiTaskRevision } from "../../piagent-core/runtime/inspection/webui-snapshot.ts";
|
|
2
|
+
import { webUiModelRef, webUiTaskRevision } from "../../piagent-core/runtime/inspection/webui-snapshot.ts";
|
|
6
3
|
import { activeSessionTask } from "../../piagent-core/extensions/task-state.js";
|
|
7
4
|
import { inspectTaskControlState } from "../../piagent-core/runtime/inspection/task-control-journal.ts";
|
|
8
5
|
import { piApprovalBroker, type ApprovalAuthority, type ApprovalBrokerEvent } from "../../piagent-core/runtime/inspection/approval-broker.ts";
|
|
@@ -11,52 +8,23 @@ import { isUserConversationSession, projectRefForCwd, sessionRefForPath } from "
|
|
|
11
8
|
import { SessionLeaseStore, type SessionLeaseSnapshot } from "./session-lease-store.ts";
|
|
12
9
|
import { GatewayEventStore } from "./gateway-events.ts";
|
|
13
10
|
import { GatewaySessionStream } from "./gateway-session-stream.ts";
|
|
14
|
-
import { preferAuthoritativePiagentGuard } from "./extension-authority.ts";
|
|
15
11
|
import { executePermissionCommand, executeRuntimeCommand } from "./runtime-session-controls.ts";
|
|
16
|
-
import {
|
|
12
|
+
import { configureSessionOptions, effectiveModelRef, effectiveThinkingLevel, type EffectiveSessionOptions } from "./session-effective-options.ts";
|
|
17
13
|
import { waitForOperationStart } from "./session-operation-start.ts";
|
|
18
|
-
|
|
14
|
+
import { armSessionOperationWatchdog, bestEffortUnsubscribe, boundedResult, sessionOperationDeadlinePolicy, SessionOperationWatchdog, terminateWatchedSessionOperation,
|
|
15
|
+
type SessionOperationDeadlinePolicy, type SessionOperationWatchdogOptions } from "./session-operation-watchdog.ts";
|
|
16
|
+
import { createProductionRuntimeFactory, type RuntimeFactory, type RuntimeHandle } from "./session-runtime-factory.ts";
|
|
19
17
|
const MAX_WARM_RUNTIMES = 10;
|
|
20
|
-
|
|
21
|
-
type RuntimeHandle = { dispose(): Promise<void>; session?: any };
|
|
22
|
-
type RuntimeFactory = (info: PiSessionInfo, runtimeInstanceRef: string, sessionManager?: any) => Promise<RuntimeHandle>;
|
|
23
18
|
type ActiveRuntime = { runtime: RuntimeHandle; lease: SessionLeaseSnapshot; info: PiSessionInfo; operationRef: string | null;
|
|
24
|
-
unsubscribe: (() => void) | null; completion: Promise<void> | null; settling: boolean; approvalWaiting: boolean;
|
|
25
|
-
unbindApproval: (() => void) | null; unsubscribeApproval: (() => void) | null; sessionManager: any | null
|
|
19
|
+
stream: GatewaySessionStream | null; unsubscribe: (() => void) | null; completion: Promise<void> | null; settling: boolean; approvalWaiting: boolean;
|
|
20
|
+
unbindApproval: (() => void) | null; unsubscribeApproval: (() => void) | null; sessionManager: any | null;
|
|
21
|
+
watchdog: SessionOperationWatchdog | null; lastSessionRevision: string | null };
|
|
26
22
|
type Projection = { sessionRevision: string; liveState: "offline" | "idle" | "running" | "paused" | "waiting-approval" | "uncertain" };
|
|
27
23
|
type RuntimeCommandResult = Awaited<ReturnType<typeof executeRuntimeCommand>>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const createRuntime = async ({ cwd, agentDir, sessionManager, sessionStartEvent }: any) => {
|
|
33
|
-
const services = await options.host.createAgentSessionServices({
|
|
34
|
-
cwd, agentDir, modelRuntime: options.modelRuntime,
|
|
35
|
-
resourceLoaderOptions: {
|
|
36
|
-
additionalExtensionPaths: [guard],
|
|
37
|
-
extensionsOverride: preferAuthoritativePiagentGuard(guard)
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
const extensionErrors = services.resourceLoader.getExtensions().errors;
|
|
41
|
-
if (extensionErrors.length) throw new Error("session-runtime-extension-load-failed");
|
|
42
|
-
const created = await options.host.createAgentSessionFromServices({ services, sessionManager, sessionStartEvent });
|
|
43
|
-
return { ...created, services, diagnostics: services.diagnostics };
|
|
44
|
-
};
|
|
45
|
-
const manager = sessionManager ?? options.host.SessionManager.open(info.path);
|
|
46
|
-
const runtime = await options.host.createAgentSessionRuntime(createRuntime, {
|
|
47
|
-
cwd: info.cwd,
|
|
48
|
-
agentDir: options.agentDir,
|
|
49
|
-
sessionManager: manager,
|
|
50
|
-
sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile: info.path }
|
|
51
|
-
});
|
|
52
|
-
const bind = async (session: any) => session.bindExtensions({ mode: "rpc", uiContext: rpcUiContext() });
|
|
53
|
-
runtime.setRebindSession(bind);
|
|
54
|
-
try { await bind(runtime.session); }
|
|
55
|
-
catch (error) { await runtime.dispose().catch(() => undefined); throw error; }
|
|
56
|
-
return runtime;
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
24
|
+
export type { EffectiveSessionOptions } from "./session-effective-options.ts";
|
|
25
|
+
export type SessionCreateResult = { sessionRef: string; effectiveOptions: EffectiveSessionOptions };
|
|
26
|
+
export type CurrentOperationProjection = { operationRef: string; state: "running" | "waiting-approval" | "settling";
|
|
27
|
+
abortable: boolean };
|
|
60
28
|
export class SessionRuntimeSupervisor {
|
|
61
29
|
readonly #gatewayInstanceRef: string;
|
|
62
30
|
readonly #key: Buffer;
|
|
@@ -65,13 +33,13 @@ export class SessionRuntimeSupervisor {
|
|
|
65
33
|
readonly #runtimeFactory: RuntimeFactory;
|
|
66
34
|
readonly #events: GatewayEventStore;
|
|
67
35
|
readonly #host: any | null;
|
|
36
|
+
readonly #operationDeadlinePolicy: SessionOperationDeadlinePolicy;
|
|
68
37
|
readonly #resolveProject: ((projectRef: string) => string | null) | null;
|
|
69
38
|
readonly #active = new Map<string, ActiveRuntime>();
|
|
70
39
|
readonly #opening = new Map<string, Promise<SessionLeaseSnapshot>>();
|
|
71
40
|
readonly #created = new Map<string, PiSessionInfo>();
|
|
72
41
|
#closed = false;
|
|
73
42
|
#readProjection: ((sessionRef: string) => Promise<Projection>) | null = null;
|
|
74
|
-
|
|
75
43
|
constructor(options: {
|
|
76
44
|
gatewayInstanceRef: string;
|
|
77
45
|
key: Buffer;
|
|
@@ -83,6 +51,7 @@ export class SessionRuntimeSupervisor {
|
|
|
83
51
|
packageRoot?: string;
|
|
84
52
|
modelRuntime?: any;
|
|
85
53
|
events?: GatewayEventStore;
|
|
54
|
+
operationWatchdog?: SessionOperationWatchdogOptions;
|
|
86
55
|
resolveProject?(projectRef: string): string | null;
|
|
87
56
|
}) {
|
|
88
57
|
this.#gatewayInstanceRef = options.gatewayInstanceRef;
|
|
@@ -91,19 +60,17 @@ export class SessionRuntimeSupervisor {
|
|
|
91
60
|
this.#listSessions = options.listSessions;
|
|
92
61
|
this.#events = options.events ?? new GatewayEventStore();
|
|
93
62
|
this.#host = options.host ?? null;
|
|
63
|
+
this.#operationDeadlinePolicy = sessionOperationDeadlinePolicy(options.operationWatchdog);
|
|
94
64
|
this.#resolveProject = options.resolveProject ?? null;
|
|
95
65
|
if (options.runtimeFactory) this.#runtimeFactory = options.runtimeFactory;
|
|
96
66
|
else {
|
|
97
67
|
if (!options.host || !options.agentDir || !options.packageRoot) throw new Error("session-runtime-factory-unavailable");
|
|
98
|
-
this.#runtimeFactory =
|
|
68
|
+
this.#runtimeFactory = createProductionRuntimeFactory({ host: options.host, agentDir: options.agentDir,
|
|
99
69
|
packageRoot: options.packageRoot, modelRuntime: options.modelRuntime });
|
|
100
70
|
}
|
|
101
71
|
}
|
|
102
|
-
|
|
103
72
|
get activeCount(): number { return this.#active.size; }
|
|
104
|
-
|
|
105
73
|
setProjectionReader(reader: (sessionRef: string) => Promise<Projection>): void { this.#readProjection = reader; }
|
|
106
|
-
|
|
107
74
|
async listSessions(): Promise<PiSessionInfo[]> {
|
|
108
75
|
const persisted = (await this.#listSessions()).filter(isUserConversationSession);
|
|
109
76
|
const paths = new Set(persisted.map((info) => info.path));
|
|
@@ -112,8 +79,11 @@ export class SessionRuntimeSupervisor {
|
|
|
112
79
|
}
|
|
113
80
|
return [...persisted, ...[...this.#created.values()].filter((info) => !paths.has(info.path))];
|
|
114
81
|
}
|
|
115
|
-
|
|
116
82
|
async create(projectRef: string, placeRef: string, modelRef: string | null, thinkingLevel: string): Promise<string> {
|
|
83
|
+
return (await this.createWithReadback(projectRef, placeRef, modelRef, thinkingLevel)).sessionRef;
|
|
84
|
+
}
|
|
85
|
+
async createWithReadback(projectRef: string, placeRef: string, modelRef: string | null,
|
|
86
|
+
thinkingLevel: string): Promise<SessionCreateResult> {
|
|
117
87
|
if (this.#closed || !this.#host) throw new Error("session-create-unavailable");
|
|
118
88
|
if (placeRef !== projectRef) throw new Error("session-place-mismatch");
|
|
119
89
|
const sessions = (await this.#listSessions()).filter(isUserConversationSession);
|
|
@@ -128,22 +98,19 @@ export class SessionRuntimeSupervisor {
|
|
|
128
98
|
const created: PiSessionInfo = { path: sessionFile, id: manager.getSessionId(), cwd: candidates[0], created: createdAt,
|
|
129
99
|
modified: createdAt, messageCount: 0, firstMessage: "(no messages)", allMessagesText: "" };
|
|
130
100
|
const sessionRef = sessionRefForPath(this.#key, sessionFile);
|
|
131
|
-
|
|
132
|
-
const active = this.#active.get(sessionRef), session = active?.runtime.session;
|
|
133
|
-
if (!session) throw new Error("session-runtime-unavailable");
|
|
134
|
-
if (modelRef) {
|
|
135
|
-
const models = session.modelRuntime?.getAvailableSnapshot?.() ?? [];
|
|
136
|
-
const model = models.find((value: any) => webUiModelRef(String(value.provider ?? ""), String(value.id ?? "")) === modelRef);
|
|
137
|
-
if (!model) throw new Error("session-model-unavailable");
|
|
138
|
-
await session.setModel(model);
|
|
139
|
-
}
|
|
140
|
-
session.setThinkingLevel(thinkingLevel);
|
|
101
|
+
// Retain identity before fallible runtime work so create receipts never point at an orphan.
|
|
141
102
|
this.#created.set(sessionRef, created);
|
|
142
|
-
|
|
103
|
+
try { await this.#activate(sessionRef, created, manager); }
|
|
104
|
+
catch {
|
|
105
|
+
return { sessionRef, effectiveOptions: { state: "unknown", modelRef: null, thinkingLevel: null,
|
|
106
|
+
reasonCode: "session-runtime-open-failed" } };
|
|
107
|
+
}
|
|
108
|
+
const active = this.#active.get(sessionRef), session = active?.runtime.session;
|
|
109
|
+
if (!session) return { sessionRef, effectiveOptions: { state: "unknown", modelRef: null, thinkingLevel: null,
|
|
110
|
+
reasonCode: "session-runtime-unavailable" } };
|
|
111
|
+
return { sessionRef, effectiveOptions: await configureSessionOptions(session, modelRef, thinkingLevel) };
|
|
143
112
|
}
|
|
144
|
-
|
|
145
113
|
liveSessionManager(sessionRef: string): any | null { return this.#active.get(sessionRef)?.sessionManager ?? null; }
|
|
146
|
-
|
|
147
114
|
async acquire(sessionRef: string): Promise<SessionLeaseSnapshot> {
|
|
148
115
|
if (this.#closed) throw new Error("session-runtime-supervisor-closed");
|
|
149
116
|
const active = this.#active.get(sessionRef);
|
|
@@ -154,7 +121,6 @@ export class SessionRuntimeSupervisor {
|
|
|
154
121
|
this.#opening.set(sessionRef, opening);
|
|
155
122
|
return await opening;
|
|
156
123
|
}
|
|
157
|
-
|
|
158
124
|
async #acquire(sessionRef: string): Promise<SessionLeaseSnapshot> {
|
|
159
125
|
if (this.#active.size + this.#opening.size > MAX_WARM_RUNTIMES) throw new Error("session-runtime-capacity");
|
|
160
126
|
const found = (await this.#listSessions()).filter(isUserConversationSession)
|
|
@@ -162,12 +128,11 @@ export class SessionRuntimeSupervisor {
|
|
|
162
128
|
if (found.length !== 1) throw new Error(found.length ? "session-ref-ambiguous" : "session-not-found");
|
|
163
129
|
return await this.#activate(sessionRef, found[0]!);
|
|
164
130
|
}
|
|
165
|
-
|
|
166
131
|
async #activate(sessionRef: string, info: PiSessionInfo, sessionManager?: any): Promise<SessionLeaseSnapshot> {
|
|
167
132
|
if (this.#active.has(sessionRef)) throw new Error("session-owner-conflict");
|
|
168
133
|
const runtimeInstanceRef = `runtime_${randomBytes(24).toString("base64url")}`;
|
|
169
134
|
const prior = this.#leases.inspect(sessionRef);
|
|
170
|
-
if (prior.state === "gateway-owned" || prior.state === "terminal-owned") {
|
|
135
|
+
if (prior.state === "gateway-owned" || prior.state === "terminal-owned" || prior.state === "recovery-required") {
|
|
171
136
|
try { this.#leases.releaseDeadOwnerForExplicitRecovery(sessionRef); } catch { /* live or unprovable owners stay authoritative */ }
|
|
172
137
|
}
|
|
173
138
|
const lease = this.#leases.acquire(sessionRef, this.#gatewayInstanceRef, runtimeInstanceRef);
|
|
@@ -179,9 +144,9 @@ export class SessionRuntimeSupervisor {
|
|
|
179
144
|
await runtime.dispose().catch(() => undefined);
|
|
180
145
|
throw new Error("session-owner-continuity-lost");
|
|
181
146
|
}
|
|
182
|
-
const active: ActiveRuntime = { runtime, lease: current, info, operationRef: null, unsubscribe: null, completion: null,
|
|
147
|
+
const active: ActiveRuntime = { runtime, lease: current, info, operationRef: null, stream: null, unsubscribe: null, completion: null,
|
|
183
148
|
settling: false, approvalWaiting: false, unbindApproval: null, unsubscribeApproval: null,
|
|
184
|
-
sessionManager: sessionManager ?? runtime.session?.sessionManager ?? null };
|
|
149
|
+
sessionManager: sessionManager ?? runtime.session?.sessionManager ?? null, watchdog: null, lastSessionRevision: null };
|
|
185
150
|
this.#active.set(sessionRef, active);
|
|
186
151
|
this.#bindApproval(sessionRef, active);
|
|
187
152
|
return current;
|
|
@@ -191,7 +156,6 @@ export class SessionRuntimeSupervisor {
|
|
|
191
156
|
throw error;
|
|
192
157
|
}
|
|
193
158
|
}
|
|
194
|
-
|
|
195
159
|
async release(sessionRef: string): Promise<SessionLeaseSnapshot> {
|
|
196
160
|
const active = this.#active.get(sessionRef);
|
|
197
161
|
if (!active) {
|
|
@@ -203,7 +167,7 @@ export class SessionRuntimeSupervisor {
|
|
|
203
167
|
if (active.settling && active.completion) await active.completion;
|
|
204
168
|
if (active.operationRef || active.settling) throw new Error("session-runtime-busy");
|
|
205
169
|
this.#active.delete(sessionRef);
|
|
206
|
-
active.unsubscribeApproval
|
|
170
|
+
bestEffortUnsubscribe(active.unsubscribeApproval); bestEffortUnsubscribe(active.unbindApproval);
|
|
207
171
|
try { await active.runtime.dispose(); }
|
|
208
172
|
catch (error) {
|
|
209
173
|
try { this.#leases.requireRecovery(sessionRef, active.lease.ownerEpoch!, this.#gatewayInstanceRef,
|
|
@@ -215,7 +179,28 @@ export class SessionRuntimeSupervisor {
|
|
|
215
179
|
if (transient && !(await this.#listSessions()).some((info) => info.path === transient.path)) this.#created.delete(sessionRef);
|
|
216
180
|
return released;
|
|
217
181
|
}
|
|
218
|
-
|
|
182
|
+
async restart(sessionRef: string): Promise<SessionLeaseSnapshot> {
|
|
183
|
+
const active = this.#active.get(sessionRef);
|
|
184
|
+
if (!active) return await this.acquire(sessionRef);
|
|
185
|
+
if (active.operationRef || active.settling || !active.runtime.session?.isIdle) throw new Error("session-runtime-busy");
|
|
186
|
+
const info = active.info, manager = active.sessionManager; await this.release(sessionRef);
|
|
187
|
+
return await this.#activate(sessionRef, info, manager ?? undefined);
|
|
188
|
+
}
|
|
189
|
+
currentOperation(sessionRef: string): CurrentOperationProjection | null {
|
|
190
|
+
const active = this.#active.get(sessionRef);
|
|
191
|
+
if (!active?.operationRef) return null;
|
|
192
|
+
return { operationRef: active.operationRef,
|
|
193
|
+
state: active.settling ? "settling" : active.approvalWaiting ? "waiting-approval" : "running",
|
|
194
|
+
abortable: !active.settling };
|
|
195
|
+
}
|
|
196
|
+
currentOperations(): Array<CurrentOperationProjection & { sessionRef: string }> {
|
|
197
|
+
const result: Array<CurrentOperationProjection & { sessionRef: string }> = [];
|
|
198
|
+
for (const sessionRef of this.#active.keys()) {
|
|
199
|
+
const operation = this.currentOperation(sessionRef);
|
|
200
|
+
if (operation) result.push({ sessionRef, ...operation });
|
|
201
|
+
}
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
219
204
|
ownership(sessionRef: string): SessionOwnerProjection {
|
|
220
205
|
const lease = this.#leases.inspect(sessionRef);
|
|
221
206
|
const active = this.#active.get(sessionRef);
|
|
@@ -225,7 +210,7 @@ export class SessionRuntimeSupervisor {
|
|
|
225
210
|
reasonCode: null
|
|
226
211
|
};
|
|
227
212
|
if (lease.state === "gateway-owned" && active && active.lease.ownerEpoch === lease.ownerEpoch
|
|
228
|
-
&& lease.gatewayInstanceRef === this.#gatewayInstanceRef) return {
|
|
213
|
+
&& lease.gatewayInstanceRef === this.#gatewayInstanceRef && active.lease.runtimeInstanceRef === lease.runtimeInstanceRef) return {
|
|
229
214
|
state: "gateway-owned", liveState: active.approvalWaiting ? "waiting-approval" : active.operationRef ? "running" : "idle",
|
|
230
215
|
composerAvailable: true, needsAttention: active.approvalWaiting,
|
|
231
216
|
owner: { kind: "gateway", ownerEpoch: lease.ownerEpoch!, gatewayInstanceRef: lease.gatewayInstanceRef!,
|
|
@@ -250,10 +235,6 @@ export class SessionRuntimeSupervisor {
|
|
|
250
235
|
reasonCode: lease.reasonCode ?? "session-owner-continuity-unknown"
|
|
251
236
|
};
|
|
252
237
|
}
|
|
253
|
-
|
|
254
|
-
// `message` arrives already carrying any attached document text, and `images`
|
|
255
|
-
// already split out: the command controller claims staged refs before it calls
|
|
256
|
-
// here, so this layer only forwards what the host prompt takes.
|
|
257
238
|
async send(sessionRef: string, payload: { delivery: "new-operation" | "follow-up" | "steer"; message: string;
|
|
258
239
|
expectedOperationRef: string | null; images?: unknown[] }, sessionRevision: string):
|
|
259
240
|
Promise<{ resultCode: "started" | "queued" | "steered"; operationRef: string }> {
|
|
@@ -261,7 +242,7 @@ export class SessionRuntimeSupervisor {
|
|
|
261
242
|
const active = this.#active.get(sessionRef), session = active?.runtime.session;
|
|
262
243
|
if (!active || !session) throw new Error("session-runtime-unavailable");
|
|
263
244
|
if (payload.delivery !== "new-operation") {
|
|
264
|
-
if (!active.operationRef || active.operationRef !== payload.expectedOperationRef || !session.isStreaming) throw new Error("session-operation-conflict");
|
|
245
|
+
if (!active.operationRef || active.operationRef !== payload.expectedOperationRef || active.settling || !session.isStreaming) throw new Error("session-operation-conflict");
|
|
265
246
|
const images = payload.images?.length ? payload.images : undefined;
|
|
266
247
|
if (payload.delivery === "follow-up") { await session.followUp(payload.message, images); return { resultCode: "queued", operationRef: active.operationRef }; }
|
|
267
248
|
await session.steer(payload.message, images); return { resultCode: "steered", operationRef: active.operationRef };
|
|
@@ -269,8 +250,14 @@ export class SessionRuntimeSupervisor {
|
|
|
269
250
|
if (payload.expectedOperationRef !== null || active.operationRef || active.settling || !session.isIdle) throw new Error("session-operation-conflict");
|
|
270
251
|
const operationRef = `operation_${randomBytes(24).toString("base64url")}`;
|
|
271
252
|
const stream = new GatewaySessionStream({ sessionRef, operationRef, events: this.#events });
|
|
272
|
-
|
|
273
|
-
active.
|
|
253
|
+
const watchdog = new SessionOperationWatchdog(this.#operationDeadlinePolicy);
|
|
254
|
+
active.operationRef = operationRef; active.stream = stream; active.watchdog = watchdog; active.lastSessionRevision = sessionRevision;
|
|
255
|
+
try {
|
|
256
|
+
active.unsubscribe = armSessionOperationWatchdog({ watchdog, subscribe: (listener) => session.subscribe(listener), retrySession: session,
|
|
257
|
+
observe: (event) => stream.observe(event), expire: (reasonCode) => { void this.#terminateOperation(sessionRef,
|
|
258
|
+
operationRef, "error", reasonCode, reasonCode, true).catch(() => undefined); } });
|
|
259
|
+
} catch (error) { stream.markError("session-operation-start-failed");
|
|
260
|
+
await this.#quarantineRuntime(sessionRef, operationRef, active, stream, "session-operation-start-failed"); throw error; }
|
|
274
261
|
this.#events.publish("runtime.changed", { sessionRef, sessionRevision, liveState: "running", operationRef, reasonCode: null });
|
|
275
262
|
const created = this.#created.get(sessionRef);
|
|
276
263
|
if (created && created.firstMessage === "(no messages)") {
|
|
@@ -278,35 +265,51 @@ export class SessionRuntimeSupervisor {
|
|
|
278
265
|
}
|
|
279
266
|
let prompt: Promise<void>;
|
|
280
267
|
try { prompt = session.prompt(payload.message, payload.images?.length ? { images: payload.images } : undefined); }
|
|
281
|
-
catch (error) { await this.#finishOperation(sessionRef, operationRef, stream); throw error; }
|
|
268
|
+
catch (error) { stream.markError(); await this.#finishOperation(sessionRef, operationRef, stream); throw error; }
|
|
282
269
|
const started = waitForOperationStart(stream, prompt);
|
|
283
|
-
//
|
|
284
|
-
// command resolves before its nested agent turn starts. In both cases the
|
|
285
|
-
// canonical completion boundary is `agent_settled`, not the outer promise.
|
|
270
|
+
// `agent_settled` is canonical even when a workflow's outer prompt resolves first.
|
|
286
271
|
const lifecycle = started.then(async (mode) => {
|
|
287
272
|
if (mode === "deferred") await stream.settled();
|
|
288
273
|
else await Promise.race([stream.settled(), prompt]);
|
|
289
274
|
});
|
|
290
275
|
active.completion = lifecycle.then(() => this.#finishOperation(sessionRef, operationRef, stream),
|
|
291
|
-
() => this.#finishOperation(sessionRef, operationRef, stream));
|
|
276
|
+
() => { stream.markError(); return this.#finishOperation(sessionRef, operationRef, stream); });
|
|
292
277
|
try { await started; }
|
|
293
278
|
catch (error) { await active.completion; throw error; }
|
|
294
279
|
return { resultCode: "started", operationRef };
|
|
295
280
|
}
|
|
296
|
-
|
|
297
281
|
async abort(sessionRef: string, operationRef: string, clearQueued: boolean): Promise<void> {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
282
|
+
await this.#terminateOperation(sessionRef, operationRef, "aborted", "operation-aborted", "operation-abort-timeout", clearQueued);
|
|
283
|
+
}
|
|
284
|
+
async #terminateOperation(sessionRef: string, operationRef: string, settlement: "aborted" | "error",
|
|
285
|
+
reasonCode: string, forcedReasonCode: string, clearQueued: boolean): Promise<void> {
|
|
286
|
+
const active = this.#active.get(sessionRef), session = active?.runtime.session, stream = active?.stream, watchdog = active?.watchdog;
|
|
287
|
+
if (!active || !session || !stream || !watchdog || active.operationRef !== operationRef) throw new Error("session-operation-conflict");
|
|
288
|
+
if (active.settling && !watchdog.terminating) throw new Error("session-operation-conflict");
|
|
289
|
+
active.settling = true; active.approvalWaiting = false;
|
|
290
|
+
const result = await terminateWatchedSessionOperation({ watchdog, settlement, reasonCode, forcedReasonCode, stream,
|
|
291
|
+
completion: () => active.completion,
|
|
292
|
+
settledCleanly: () => this.#active.get(sessionRef) === active && active.operationRef !== operationRef
|
|
293
|
+
&& session.isIdle === true && session.isStreaming !== true,
|
|
294
|
+
cancelApproval: (reason) => { piApprovalBroker.cancelForOperation(active.info.cwd, active.info.id, operationRef, reason); },
|
|
295
|
+
abortHost: () => session.abort(), clearQueue: clearQueued ? () => session.clearQueue() : undefined
|
|
296
|
+
});
|
|
297
|
+
if (result.state === "quarantine") await this.#quarantineRuntime(sessionRef, operationRef, active, stream, result.reasonCode);
|
|
298
|
+
else if (this.#active.get(sessionRef) === active) { active.stream = null; active.watchdog = null; active.settling = false; }
|
|
299
|
+
}
|
|
300
|
+
async #quarantineRuntime(sessionRef: string, operationRef: string, active: ActiveRuntime,
|
|
301
|
+
stream: GatewaySessionStream, reasonCode: string): Promise<void> {
|
|
302
|
+
if (this.#active.get(sessionRef) !== active) return;
|
|
303
|
+
bestEffortUnsubscribe(active.unsubscribe); active.unsubscribe = null; active.watchdog?.close(); active.watchdog = null;
|
|
304
|
+
if (active.operationRef === operationRef) { active.operationRef = null; stream.complete(null); }
|
|
305
|
+
active.stream = null; active.completion = null; active.settling = false; active.approvalWaiting = false;
|
|
306
|
+
this.#active.delete(sessionRef); bestEffortUnsubscribe(active.unsubscribeApproval); bestEffortUnsubscribe(active.unbindApproval);
|
|
307
|
+
try { this.#leases.requireRecovery(sessionRef, active.lease.ownerEpoch!, this.#gatewayInstanceRef,
|
|
308
|
+
active.lease.runtimeInstanceRef!, reasonCode); } catch { /* continuity remains fail closed */ }
|
|
309
|
+
if (active.lastSessionRevision) this.#events.publish("runtime.changed", { sessionRef,
|
|
310
|
+
sessionRevision: active.lastSessionRevision, liveState: "uncertain", operationRef: null, reasonCode });
|
|
311
|
+
await boundedResult(Promise.resolve().then(() => active.runtime.dispose()), this.#operationDeadlinePolicy.terminationTimeoutMs);
|
|
308
312
|
}
|
|
309
|
-
|
|
310
313
|
#bindApproval(sessionRef: string, active: ActiveRuntime): void {
|
|
311
314
|
const authority = (): ApprovalAuthority => {
|
|
312
315
|
if (!active.operationRef || active.lease.state !== "gateway-owned" || !active.lease.revision) return null;
|
|
@@ -331,50 +334,53 @@ export class SessionRuntimeSupervisor {
|
|
|
331
334
|
active.unbindApproval = piApprovalBroker.bind({ cwd: active.info.cwd, rawSessionId: active.info.id,
|
|
332
335
|
runtimeInstanceId: active.lease.runtimeInstanceRef!, authority });
|
|
333
336
|
active.unsubscribeApproval = piApprovalBroker.subscribe(active.info.cwd, active.info.id, (event: ApprovalBrokerEvent) => {
|
|
337
|
+
active.watchdog?.progress();
|
|
334
338
|
const projection = piApprovalBroker.projection(active.info.cwd, active.info.id);
|
|
335
339
|
active.approvalWaiting = projection.summary.state === "waiting";
|
|
336
340
|
void this.#publishApprovalState(sessionRef, active, event);
|
|
337
341
|
});
|
|
338
342
|
}
|
|
339
|
-
|
|
340
343
|
async #publishApprovalState(sessionRef: string, active: ActiveRuntime, event: ApprovalBrokerEvent): Promise<void> {
|
|
341
344
|
try {
|
|
342
345
|
const projection = await this.#readProjection?.(sessionRef);
|
|
343
346
|
if (!projection || this.#active.get(sessionRef) !== active) return;
|
|
347
|
+
active.lastSessionRevision = projection.sessionRevision;
|
|
344
348
|
this.#events.publish("runtime.changed", { sessionRef, sessionRevision: projection.sessionRevision,
|
|
345
349
|
liveState: active.approvalWaiting ? "waiting-approval" : active.operationRef ? "running" : projection.liveState,
|
|
346
350
|
operationRef: active.operationRef, reasonCode: `approval-${event.kind}` });
|
|
347
351
|
} catch { /* approval truth remains available through the canonical inspection route */ }
|
|
348
352
|
}
|
|
349
|
-
|
|
350
353
|
async setModel(sessionRef: string, modelRef: string): Promise<"model-changed" | "no-change"> {
|
|
351
354
|
await this.acquire(sessionRef);
|
|
352
355
|
const active = this.#active.get(sessionRef), session = active?.runtime.session;
|
|
353
356
|
if (!active || !session) throw new Error("session-runtime-unavailable");
|
|
354
357
|
if (active.operationRef || !session.isIdle) throw new Error("session-runtime-busy");
|
|
355
|
-
const current = session
|
|
356
|
-
? webUiModelRef(String(session.model.provider ?? ""), String(session.model.id ?? session.model.modelId ?? "")) : null;
|
|
358
|
+
const current = effectiveModelRef(session);
|
|
357
359
|
if (current === modelRef) return "no-change";
|
|
358
360
|
const models = session.modelRuntime?.getAvailableSnapshot?.() ?? [];
|
|
359
|
-
const model = models.find((value: any) => webUiModelRef(String(value.provider ?? ""), String(value.id ?? "")) === modelRef);
|
|
361
|
+
const model = models.find((value: any) => webUiModelRef(String(value.provider ?? ""), String(value.id ?? value.modelId ?? "")) === modelRef);
|
|
360
362
|
if (!model) throw new Error("session-model-unavailable");
|
|
361
363
|
await session.setModel(model);
|
|
364
|
+
const effective = effectiveModelRef(session);
|
|
365
|
+
if (!effective) throw new Error("session-model-effect-unknown");
|
|
366
|
+
if (effective !== modelRef) throw new Error("session-model-mismatch");
|
|
362
367
|
this.#touchCreated(sessionRef);
|
|
363
368
|
return "model-changed";
|
|
364
369
|
}
|
|
365
|
-
|
|
366
370
|
async setThinking(sessionRef: string, thinkingLevel: string): Promise<"thinking-changed" | "no-change"> {
|
|
367
371
|
await this.acquire(sessionRef);
|
|
368
372
|
const active = this.#active.get(sessionRef), session = active?.runtime.session;
|
|
369
373
|
if (!active || !session) throw new Error("session-runtime-unavailable");
|
|
370
374
|
if (active.operationRef || !session.isIdle) throw new Error("session-runtime-busy");
|
|
371
|
-
const current =
|
|
375
|
+
const current = effectiveThinkingLevel(session);
|
|
372
376
|
if (current === thinkingLevel) return "no-change";
|
|
373
|
-
session.setThinkingLevel(thinkingLevel);
|
|
377
|
+
await session.setThinkingLevel(thinkingLevel);
|
|
378
|
+
const effective = effectiveThinkingLevel(session);
|
|
379
|
+
if (!effective) throw new Error("session-thinking-effect-unknown");
|
|
380
|
+
if (effective !== thinkingLevel) throw new Error("session-thinking-mismatch");
|
|
374
381
|
this.#touchCreated(sessionRef);
|
|
375
382
|
return "thinking-changed";
|
|
376
383
|
}
|
|
377
|
-
|
|
378
384
|
async setPermission(sessionRef: string, permissionMode: "read-only" | "workspace-write" | "trusted-full-access"): Promise<"permission-changed"> {
|
|
379
385
|
await this.acquire(sessionRef);
|
|
380
386
|
const active = this.#active.get(sessionRef), session = active?.runtime.session;
|
|
@@ -384,7 +390,6 @@ export class SessionRuntimeSupervisor {
|
|
|
384
390
|
this.#touchCreated(sessionRef);
|
|
385
391
|
return "permission-changed";
|
|
386
392
|
}
|
|
387
|
-
|
|
388
393
|
async runRuntimeCommand(sessionRef: string, command: string): Promise<RuntimeCommandResult> {
|
|
389
394
|
await this.acquire(sessionRef);
|
|
390
395
|
const active = this.#active.get(sessionRef), session = active?.runtime.session;
|
|
@@ -398,12 +403,10 @@ export class SessionRuntimeSupervisor {
|
|
|
398
403
|
health: { state: "unavailable", reasonCode: "approval-broker-unavailable", message: "Approval broker is unavailable" } } };
|
|
399
404
|
return piApprovalBroker.projection(active.info.cwd, active.info.id);
|
|
400
405
|
}
|
|
401
|
-
|
|
402
406
|
approvalDetail(sessionRef: string, approvalRef: string): unknown | null {
|
|
403
407
|
const active = this.#active.get(sessionRef);
|
|
404
408
|
return active ? piApprovalBroker.detail(active.info.cwd, active.info.id, approvalRef) : null;
|
|
405
409
|
}
|
|
406
|
-
|
|
407
410
|
async decideApproval(approvalRef: string, decision: unknown): Promise<unknown> {
|
|
408
411
|
for (const active of this.#active.values()) {
|
|
409
412
|
if (piApprovalBroker.detail(active.info.cwd, active.info.id, approvalRef)) {
|
|
@@ -412,7 +415,6 @@ export class SessionRuntimeSupervisor {
|
|
|
412
415
|
}
|
|
413
416
|
throw new Error("approval-not-pending");
|
|
414
417
|
}
|
|
415
|
-
|
|
416
418
|
async rename(sessionRef: string, title: string): Promise<"renamed" | "no-change"> {
|
|
417
419
|
await this.acquire(sessionRef);
|
|
418
420
|
const active = this.#active.get(sessionRef), session = active?.runtime.session;
|
|
@@ -424,7 +426,6 @@ export class SessionRuntimeSupervisor {
|
|
|
424
426
|
this.#touchCreated(sessionRef);
|
|
425
427
|
return "renamed";
|
|
426
428
|
}
|
|
427
|
-
|
|
428
429
|
async fork(sessionRef: string, entryRef: string | null, title: string | null): Promise<string> {
|
|
429
430
|
if (!this.#host) throw new Error("session-fork-unavailable");
|
|
430
431
|
await this.acquire(sessionRef);
|
|
@@ -451,28 +452,43 @@ export class SessionRuntimeSupervisor {
|
|
|
451
452
|
this.#created.set(forkRef, created);
|
|
452
453
|
return forkRef;
|
|
453
454
|
}
|
|
454
|
-
|
|
455
|
-
#touchCreated(sessionRef: string): void {
|
|
456
|
-
const created = this.#created.get(sessionRef);
|
|
457
|
-
if (created) created.modified = new Date();
|
|
458
|
-
}
|
|
459
|
-
|
|
455
|
+
#touchCreated(sessionRef: string): void { const created = this.#created.get(sessionRef); if (created) created.modified = new Date(); }
|
|
460
456
|
async #finishOperation(sessionRef: string, operationRef: string, stream: GatewaySessionStream): Promise<void> {
|
|
461
457
|
const active = this.#active.get(sessionRef);
|
|
462
458
|
if (!active || active.operationRef !== operationRef) return;
|
|
463
|
-
|
|
459
|
+
const restartRequired = stream.runtimeRestartRequired; let projection: Projection | null = null;
|
|
460
|
+
bestEffortUnsubscribe(active.unsubscribe); active.unsubscribe = null; active.watchdog?.close(); active.settling = true;
|
|
464
461
|
try {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
462
|
+
if (this.#readProjection) {
|
|
463
|
+
const read = await boundedResult(this.#readProjection(sessionRef), this.#operationDeadlinePolicy.projectionTimeoutMs);
|
|
464
|
+
if (read.state === "settled") projection = read.value;
|
|
465
|
+
else if (read.state === "timeout") stream.markError("session-projection-timeout");
|
|
466
|
+
}
|
|
470
467
|
} catch { /* Canonical refresh failure cannot be replaced by an invented revision. */ }
|
|
471
|
-
|
|
472
|
-
|
|
468
|
+
if (this.#active.get(sessionRef) !== active || active.operationRef !== operationRef) return;
|
|
469
|
+
if (this.ownership(sessionRef).state !== "gateway-owned") { stream.markError("session-owner-continuity-lost"); await this.#quarantineRuntime(sessionRef, operationRef, active, stream, "session-owner-continuity-lost"); return; }
|
|
470
|
+
// The pre-clear projection is stale; derive post-settlement liveness from exact ownership.
|
|
471
|
+
active.operationRef = null; const settledLiveState = this.ownership(sessionRef).liveState;
|
|
472
|
+
if (projection) active.lastSessionRevision = projection.sessionRevision;
|
|
473
|
+
const taskOutcome = activeSessionTask(active.info.cwd, active.info.id)?.trace?.outcome ?? null;
|
|
474
|
+
stream.complete(projection?.sessionRevision ?? null, taskOutcome);
|
|
475
|
+
if (projection) this.#events.publish("runtime.changed", { sessionRef, sessionRevision: projection.sessionRevision,
|
|
476
|
+
liveState: restartRequired ? "uncertain" : settledLiveState, operationRef: null,
|
|
477
|
+
reasonCode: restartRequired ? "runtime-restart-required" : null });
|
|
478
|
+
if (this.#active.get(sessionRef) === active) { active.completion = null;
|
|
479
|
+
if (!active.watchdog?.terminating) { active.stream = null; active.watchdog = null; active.settling = false; } }
|
|
480
|
+
if (!restartRequired || !projection || this.#active.get(sessionRef) !== active) return;
|
|
481
|
+
let restartAuthority: ActiveRuntime | null = active;
|
|
482
|
+
try {
|
|
483
|
+
await this.restart(sessionRef); restartAuthority = this.#active.get(sessionRef) ?? null;
|
|
484
|
+
const projection = await this.#readProjection?.(sessionRef);
|
|
485
|
+
if (!projection || !restartAuthority || this.#active.get(sessionRef) !== restartAuthority || this.ownership(sessionRef).state !== "gateway-owned") return;
|
|
486
|
+
this.#events.publish("runtime.changed", { sessionRef, sessionRevision: projection.sessionRevision, liveState: projection.liveState, operationRef: null, reasonCode: null });
|
|
487
|
+
} catch {
|
|
488
|
+
if (!restartAuthority || this.#active.get(sessionRef) !== restartAuthority || this.ownership(sessionRef).state !== "gateway-owned") return;
|
|
489
|
+
this.#events.publish("runtime.changed", { sessionRef, sessionRevision: projection.sessionRevision, liveState: "uncertain", operationRef: null, reasonCode: "runtime-restart-failed" });
|
|
473
490
|
}
|
|
474
491
|
}
|
|
475
|
-
|
|
476
492
|
async close(): Promise<void> {
|
|
477
493
|
if (this.#closed) return;
|
|
478
494
|
this.#closed = true;
|
|
@@ -237,8 +237,11 @@ export class SessionLeaseStore {
|
|
|
237
237
|
if (!Number.isFinite(now.getTime())) throw new Error("session-lease-input-invalid");
|
|
238
238
|
return this.#withMutationLock(sessionRef, () => {
|
|
239
239
|
const current = this.inspect(sessionRef);
|
|
240
|
-
const
|
|
241
|
-
|
|
240
|
+
const terminalOwner = current.state === "terminal-owned"
|
|
241
|
+
|| (current.state === "recovery-required" && current.gatewayInstanceRef?.startsWith(TERMINAL_OWNER_PREFIX));
|
|
242
|
+
const pid = terminalOwner ? terminalOwnerPid(current.gatewayInstanceRef)
|
|
243
|
+
: current.state === "gateway-owned" || current.state === "recovery-required"
|
|
244
|
+
? gatewayOwnerPid(current.gatewayInstanceRef) : null;
|
|
242
245
|
if (!pid || processAlive(pid)) throw new Error("session-owner-not-proven-dead");
|
|
243
246
|
this.#append(sessionRef, { recordedAt: now.toISOString(), sessionRef, event: "recovery-required", ownerEpoch: current.ownerEpoch!,
|
|
244
247
|
gatewayInstanceRef: current.gatewayInstanceRef!, runtimeInstanceRef: current.runtimeInstanceRef!, reasonCode: "owner-process-exited" });
|
|
@@ -236,6 +236,7 @@ export class CoreInspectionProvider implements WebUiReadModelProvider {
|
|
|
236
236
|
}
|
|
237
237
|
async transcript(beforeCursor: string | null, limit: number): Promise<unknown> {
|
|
238
238
|
const snapshot = await this.snapshot() as any;
|
|
239
|
+
const activeTask = this.#input.task?.() as any;
|
|
239
240
|
return projectTranscript({ identity: { projectRef: snapshot.identity.projectRef, runtimeInstanceId: snapshot.identity.runtimeInstanceId,
|
|
240
241
|
sessionRef: snapshot.identity.sessionRef, taskId: snapshot.identity.taskId, taskRunId: snapshot.identity.taskRunId, agentOperationId: null, toolCallId: null },
|
|
241
242
|
revision: { runtimeRevision: snapshot.revision.runtimeRevision, taskRevision: snapshot.revision.taskRevision,
|
|
@@ -243,7 +244,7 @@ export class CoreInspectionProvider implements WebUiReadModelProvider {
|
|
|
243
244
|
indexRevision: snapshot.revision.indexRevision, approvalRevision: snapshot.revision.approvalRevision,
|
|
244
245
|
sessionOptionRevision: snapshot.revision.sessionOptionRevision, queueRevision: snapshot.revision.queueRevision },
|
|
245
246
|
eventCursor: snapshot.revision.eventCursor, entries: this.#input.sessionEntries?.() ?? [], beforeCursor, limit,
|
|
246
|
-
generatedAt: snapshot.generatedAt });
|
|
247
|
+
generatedAt: snapshot.generatedAt, taskOutcome: typeof activeTask?.trace?.outcome === "string" ? activeTask.trace.outcome : null });
|
|
247
248
|
}
|
|
248
249
|
async queue(): Promise<unknown> {
|
|
249
250
|
if (!this.#input.queueProjection) throw new ReadModelNotFound();
|
|
@@ -56,6 +56,7 @@ export async function startLoopbackServer(options: {
|
|
|
56
56
|
mode?: "session" | "gateway";
|
|
57
57
|
readCapabilities: ReadCapabilities;
|
|
58
58
|
readSessionCatalog?: () => unknown | Promise<unknown>;
|
|
59
|
+
readSessionLiveState?: () => unknown | Promise<unknown>;
|
|
59
60
|
readSessionCreationOptions?: () => unknown | Promise<unknown>;
|
|
60
61
|
readSessionModel?: (sessionRef: string) => WebUiReadModelProvider | Promise<WebUiReadModelProvider>;
|
|
61
62
|
executeSessionAttachment?: (sessionRef: string, command: unknown) => unknown | Promise<unknown>;
|
|
@@ -296,6 +297,13 @@ export async function startLoopbackServer(options: {
|
|
|
296
297
|
catch { return errorResponse(response, 503, "session-catalog-unavailable"); }
|
|
297
298
|
}
|
|
298
299
|
|
|
300
|
+
if (request.method === "GET" && url.pathname === "/api/v1/session-live-state" && options.readSessionLiveState) {
|
|
301
|
+
if (!auth.authenticate(request)) return errorResponse(response, 401, "authentication-required");
|
|
302
|
+
if (url.search) return errorResponse(response, 400, "invalid-session-live-state-query");
|
|
303
|
+
try { return jsonResponse(response, 200, await options.readSessionLiveState()); }
|
|
304
|
+
catch { return errorResponse(response, 503, "session-live-state-unavailable"); }
|
|
305
|
+
}
|
|
306
|
+
|
|
299
307
|
if (request.method === "GET" && url.pathname === "/api/v1/session-creation-options" && options.readSessionCreationOptions) {
|
|
300
308
|
if (!auth.authenticate(request)) return errorResponse(response, 401, "authentication-required");
|
|
301
309
|
if (url.search) return errorResponse(response, 400, "invalid-session-creation-query");
|