@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
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { hasVisibleText } from "../shared/text-visibility.ts";
|
|
2
|
+
|
|
3
|
+
export type SessionOperationPhase = "pending" | "running" | "retry" | "compaction" | "settled";
|
|
4
|
+
|
|
5
|
+
export type SessionOperationRetryPolicy = Readonly<{
|
|
6
|
+
maximumAttempts: number;
|
|
7
|
+
maximumDelayMs: number;
|
|
8
|
+
}>;
|
|
9
|
+
|
|
10
|
+
export type SessionOperationRetryPolicyOptions = Partial<SessionOperationRetryPolicy>;
|
|
11
|
+
|
|
12
|
+
export type SessionOperationObservation = Readonly<{
|
|
13
|
+
accepted: boolean;
|
|
14
|
+
phase: SessionOperationPhase;
|
|
15
|
+
retry: "none" | "allowed" | "abort";
|
|
16
|
+
reasonCode: string | null;
|
|
17
|
+
}>;
|
|
18
|
+
|
|
19
|
+
const DEFAULT_RETRY_POLICY: SessionOperationRetryPolicy = Object.freeze({
|
|
20
|
+
// Pi's provider retry is useful before a response exists, but a long retry
|
|
21
|
+
// chain burns quota and keeps the browser in Working. One automatic retry
|
|
22
|
+
// absorbs an ordinary transient failure without replaying a persistent one.
|
|
23
|
+
maximumAttempts: 1,
|
|
24
|
+
maximumDelayMs: 8_000
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
function boundedInteger(value: unknown, fallback: number, minimum: number, maximum: number): number {
|
|
28
|
+
if (value === undefined) return fallback;
|
|
29
|
+
if (!Number.isSafeInteger(value) || Number(value) < minimum || Number(value) > maximum) {
|
|
30
|
+
throw new Error("session-operation-retry-policy-invalid");
|
|
31
|
+
}
|
|
32
|
+
return Number(value);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function sessionOperationRetryPolicy(
|
|
36
|
+
options: SessionOperationRetryPolicyOptions = {}
|
|
37
|
+
): SessionOperationRetryPolicy {
|
|
38
|
+
return Object.freeze({
|
|
39
|
+
maximumAttempts: boundedInteger(options.maximumAttempts, DEFAULT_RETRY_POLICY.maximumAttempts, 0, 10),
|
|
40
|
+
maximumDelayMs: boundedInteger(options.maximumDelayMs, DEFAULT_RETRY_POLICY.maximumDelayMs, 0, 300_000)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function correlationRef(event: any): string | null {
|
|
45
|
+
for (const key of ["operationRef", "agentOperationId", "operationId"]) {
|
|
46
|
+
if (typeof event?.[key] === "string" && event[key]) return event[key];
|
|
47
|
+
}
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function visibleAssistantContent(message: any): boolean {
|
|
52
|
+
if (typeof message?.content === "string") return hasVisibleText(message.content);
|
|
53
|
+
if (!Array.isArray(message?.content)) return false;
|
|
54
|
+
return message.content.some((item: any) => {
|
|
55
|
+
if (item?.type === "text") return hasVisibleText(item.text);
|
|
56
|
+
// Images and every model-emitted tool/server-tool block are observable
|
|
57
|
+
// output even when they do not contain user-visible text.
|
|
58
|
+
return ["image", "image_url", "toolCall", "tool_call", "tool_use", "server_tool_use"]
|
|
59
|
+
.includes(String(item?.type ?? ""));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function attempt(value: unknown): number | null {
|
|
64
|
+
return Number.isSafeInteger(value) && Number(value) >= 1 ? Number(value) : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function delay(value: unknown): number | null {
|
|
68
|
+
return Number.isSafeInteger(value) && Number(value) >= 0 ? Number(value) : null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Operation-local authority for provider retry, compaction and settlement.
|
|
73
|
+
*
|
|
74
|
+
* Pi's pinned host owns the actual provider retry. This state machine owns the
|
|
75
|
+
* stricter Piagent admission decision and is shared by the Gateway stream and
|
|
76
|
+
* runtime supervisor. It never treats compaction as a provider retry, and it
|
|
77
|
+
* fails closed once assistant output or a tool call makes replay unsafe.
|
|
78
|
+
*/
|
|
79
|
+
export class SessionOperationLifecycle {
|
|
80
|
+
readonly operationRef: string;
|
|
81
|
+
readonly retryPolicy: SessionOperationRetryPolicy;
|
|
82
|
+
#phase: SessionOperationPhase = "pending";
|
|
83
|
+
#started = false;
|
|
84
|
+
#hostSettled = false;
|
|
85
|
+
#terminal = false;
|
|
86
|
+
#visibleAssistantOutput = false;
|
|
87
|
+
#toolCallObserved = false;
|
|
88
|
+
#lastRetryAttempt = 0;
|
|
89
|
+
#activeRetryAttempt: number | null = null;
|
|
90
|
+
#retryAbortRequired = false;
|
|
91
|
+
|
|
92
|
+
constructor(options: {
|
|
93
|
+
operationRef: string;
|
|
94
|
+
retryPolicy?: SessionOperationRetryPolicyOptions | SessionOperationRetryPolicy;
|
|
95
|
+
}) {
|
|
96
|
+
this.operationRef = options.operationRef;
|
|
97
|
+
this.retryPolicy = sessionOperationRetryPolicy(options.retryPolicy);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get phase(): SessionOperationPhase { return this.#phase; }
|
|
101
|
+
get started(): boolean { return this.#started; }
|
|
102
|
+
get hostSettled(): boolean { return this.#hostSettled; }
|
|
103
|
+
get terminal(): boolean { return this.#terminal; }
|
|
104
|
+
get replayUnsafe(): boolean { return this.#visibleAssistantOutput || this.#toolCallObserved; }
|
|
105
|
+
get retryAbortRequired(): boolean { return this.#retryAbortRequired; }
|
|
106
|
+
|
|
107
|
+
forceHostSettlement(): void {
|
|
108
|
+
this.#started = true;
|
|
109
|
+
this.#hostSettled = true;
|
|
110
|
+
this.#phase = "settled";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
markTerminal(): boolean {
|
|
114
|
+
if (this.#terminal) return false;
|
|
115
|
+
this.#terminal = true;
|
|
116
|
+
this.#hostSettled = true;
|
|
117
|
+
this.#phase = "settled";
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
observe(event: any): SessionOperationObservation {
|
|
122
|
+
if (this.#terminal || this.#hostSettled) return this.#ignored("operation-event-after-settlement");
|
|
123
|
+
const correlated = correlationRef(event);
|
|
124
|
+
if (correlated && correlated !== this.operationRef) return this.#ignored("operation-correlation-mismatch");
|
|
125
|
+
const type = String(event?.type ?? "");
|
|
126
|
+
|
|
127
|
+
if (type === "agent_start") {
|
|
128
|
+
this.#started = true;
|
|
129
|
+
this.#phase = "running";
|
|
130
|
+
return this.#accepted();
|
|
131
|
+
}
|
|
132
|
+
if (type === "agent_settled") {
|
|
133
|
+
this.#started = true;
|
|
134
|
+
this.#hostSettled = true;
|
|
135
|
+
this.#phase = "settled";
|
|
136
|
+
return this.#accepted();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Direct stream fixtures and some deferred command hosts begin with a
|
|
140
|
+
// message/tool event. Treat that as an observed start while still refusing
|
|
141
|
+
// uncorrelated retry/compaction lifecycle events before any operation work.
|
|
142
|
+
if (["message_start", "message_update", "message_end", "tool_execution_start",
|
|
143
|
+
"tool_execution_update", "tool_execution_end", "turn_start", "turn_end"].includes(type)) {
|
|
144
|
+
this.#started = true;
|
|
145
|
+
if (this.#phase === "pending") this.#phase = "running";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (type === "message_update") {
|
|
149
|
+
const update = event?.assistantMessageEvent;
|
|
150
|
+
if (update?.type === "text_delta" && hasVisibleText(update.delta)) this.#visibleAssistantOutput = true;
|
|
151
|
+
if (visibleAssistantContent(event?.message)) this.#visibleAssistantOutput = true;
|
|
152
|
+
return this.#accepted();
|
|
153
|
+
}
|
|
154
|
+
if (type === "message_end" && event?.message?.role === "assistant") {
|
|
155
|
+
if (visibleAssistantContent(event.message)) this.#visibleAssistantOutput = true;
|
|
156
|
+
return this.#accepted();
|
|
157
|
+
}
|
|
158
|
+
if (type === "tool_execution_start") {
|
|
159
|
+
this.#toolCallObserved = true;
|
|
160
|
+
return this.#accepted();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (type === "agent_end" && event?.willRetry === true && this.replayUnsafe) {
|
|
164
|
+
return this.#abortRetry("automatic-retry-replay-unsafe");
|
|
165
|
+
}
|
|
166
|
+
if (type === "auto_retry_start") return this.#retryStarted(event);
|
|
167
|
+
if (type === "auto_retry_end") return this.#retryEnded(event);
|
|
168
|
+
|
|
169
|
+
// Summarization retries belong to compaction and never consume or inherit
|
|
170
|
+
// the provider-turn retry budget.
|
|
171
|
+
if (type === "compaction_start") {
|
|
172
|
+
if (!this.#started) return this.#ignored("operation-event-before-start");
|
|
173
|
+
if (this.#phase === "retry") this.#retryAbortRequired = true;
|
|
174
|
+
this.#phase = "compaction";
|
|
175
|
+
return this.#retryAbortRequired
|
|
176
|
+
? this.#abortRetry("automatic-retry-compaction-overlap")
|
|
177
|
+
: this.#accepted();
|
|
178
|
+
}
|
|
179
|
+
if (type === "compaction_end") {
|
|
180
|
+
if (this.#phase !== "compaction") return this.#ignored("compaction-event-stale");
|
|
181
|
+
this.#phase = "running";
|
|
182
|
+
return this.#accepted();
|
|
183
|
+
}
|
|
184
|
+
if (["summarization_retry_scheduled", "summarization_retry_attempt_start",
|
|
185
|
+
"summarization_retry_finished"].includes(type)) {
|
|
186
|
+
return this.#phase === "compaction" ? this.#accepted() : this.#ignored("compaction-event-stale");
|
|
187
|
+
}
|
|
188
|
+
return this.#accepted();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#retryStarted(event: any): SessionOperationObservation {
|
|
192
|
+
if (!this.#started) return this.#ignored("operation-event-before-start");
|
|
193
|
+
const currentAttempt = attempt(event?.attempt), currentDelay = delay(event?.delayMs);
|
|
194
|
+
if (currentAttempt === null || currentDelay === null) return this.#abortRetry("automatic-retry-metadata-invalid");
|
|
195
|
+
if (currentAttempt === this.#lastRetryAttempt) return this.#ignored("automatic-retry-event-duplicate");
|
|
196
|
+
if (currentAttempt !== this.#lastRetryAttempt + 1) {
|
|
197
|
+
this.#activeRetryAttempt = currentAttempt;
|
|
198
|
+
this.#phase = "retry";
|
|
199
|
+
return this.#abortRetry("automatic-retry-attempt-stale");
|
|
200
|
+
}
|
|
201
|
+
this.#lastRetryAttempt = currentAttempt;
|
|
202
|
+
this.#activeRetryAttempt = currentAttempt;
|
|
203
|
+
if (this.#phase === "compaction") return this.#abortRetry("automatic-retry-compaction-overlap");
|
|
204
|
+
this.#phase = "retry";
|
|
205
|
+
if (this.replayUnsafe) return this.#abortRetry("automatic-retry-replay-unsafe");
|
|
206
|
+
if (currentAttempt > this.retryPolicy.maximumAttempts) return this.#abortRetry("automatic-retry-attempt-limit");
|
|
207
|
+
if (currentDelay > this.retryPolicy.maximumDelayMs) return this.#abortRetry("automatic-retry-delay-limit");
|
|
208
|
+
return { accepted: true, phase: this.#phase, retry: "allowed", reasonCode: null };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
#retryEnded(event: any): SessionOperationObservation {
|
|
212
|
+
const currentAttempt = attempt(event?.attempt);
|
|
213
|
+
if (currentAttempt === null || currentAttempt !== this.#activeRetryAttempt) {
|
|
214
|
+
return this.#ignored("automatic-retry-event-stale");
|
|
215
|
+
}
|
|
216
|
+
this.#activeRetryAttempt = null;
|
|
217
|
+
// Keep the highest attempt for the whole logical operation so hosts that
|
|
218
|
+
// emit retry_end between attempts still have a monotonic sequence. Keep an
|
|
219
|
+
// abort latched too: retry_end can arrive synchronously before the
|
|
220
|
+
// supervisor's cancellation microtask observes the host AbortController.
|
|
221
|
+
if (this.#phase === "retry") this.#phase = "running";
|
|
222
|
+
return this.#accepted();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
#abortRetry(reasonCode: string): SessionOperationObservation {
|
|
226
|
+
this.#retryAbortRequired = true;
|
|
227
|
+
return { accepted: true, phase: this.#phase, retry: "abort", reasonCode };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
#accepted(): SessionOperationObservation {
|
|
231
|
+
return { accepted: true, phase: this.#phase, retry: "none", reasonCode: null };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
#ignored(reasonCode: string): SessionOperationObservation {
|
|
235
|
+
return { accepted: false, phase: this.#phase, retry: "none", reasonCode };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type RetryObservation = { retry?: unknown } | null | undefined;
|
|
2
|
+
|
|
3
|
+
function invoke(action: (() => unknown) | null): void {
|
|
4
|
+
if (!action) return;
|
|
5
|
+
try {
|
|
6
|
+
const result = action();
|
|
7
|
+
if (result && typeof (result as PromiseLike<unknown>).then === "function") {
|
|
8
|
+
void Promise.resolve(result).catch(() => undefined);
|
|
9
|
+
}
|
|
10
|
+
} catch { /* the operation watchdog remains the terminal authority */ }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cancels a host-owned provider retry without replaying the prompt. Pinned Pi
|
|
15
|
+
* creates its retry AbortController immediately after auto_retry_start is
|
|
16
|
+
* emitted, so the microtask always runs before even a zero-millisecond timer.
|
|
17
|
+
*/
|
|
18
|
+
export class SessionOperationRetryCancellation {
|
|
19
|
+
readonly #session: any;
|
|
20
|
+
#fallbackIssued = false;
|
|
21
|
+
|
|
22
|
+
constructor(session: any) { this.#session = session; }
|
|
23
|
+
|
|
24
|
+
observe(observation: RetryObservation): void {
|
|
25
|
+
if (observation?.retry !== "abort") return;
|
|
26
|
+
if (typeof this.#session?.abortRetry === "function") {
|
|
27
|
+
const cancel = () => invoke(() => this.#session.abortRetry());
|
|
28
|
+
cancel();
|
|
29
|
+
queueMicrotask(cancel);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
// Unknown hosts fail closed once. Replaying a tool-capable turn is more
|
|
33
|
+
// dangerous than ending the logical operation with an explicit error.
|
|
34
|
+
if (this.#fallbackIssued || typeof this.#session?.abort !== "function") return;
|
|
35
|
+
this.#fallbackIssued = true;
|
|
36
|
+
invoke(() => this.#session.abort());
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -12,7 +12,10 @@ export async function waitForOperationStart(
|
|
|
12
12
|
prompt.then(() => ({ state: "prompt-resolved" as const }),
|
|
13
13
|
(error: unknown) => ({ state: "prompt-rejected" as const, error }))
|
|
14
14
|
]);
|
|
15
|
-
if (first.state === "started")
|
|
15
|
+
if (first.state === "started") {
|
|
16
|
+
if (stream.lifecycleTerminationReasonCode) throw new Error(stream.lifecycleTerminationReasonCode);
|
|
17
|
+
return "prompt-owned";
|
|
18
|
+
}
|
|
16
19
|
if (first.state === "prompt-rejected") throw first.error;
|
|
17
20
|
|
|
18
21
|
// Extension commands such as `/workflow` launch their agent turn through
|
|
@@ -27,6 +30,7 @@ export async function waitForOperationStart(
|
|
|
27
30
|
DEFERRED_OPERATION_START_GRACE_MS);
|
|
28
31
|
})
|
|
29
32
|
]);
|
|
33
|
+
if (stream.lifecycleTerminationReasonCode) throw new Error(stream.lifecycleTerminationReasonCode);
|
|
30
34
|
return "deferred";
|
|
31
35
|
} finally {
|
|
32
36
|
if (timer) clearTimeout(timer);
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { SessionOperationRetryCancellation } from "./session-operation-retry-control.ts";
|
|
2
|
+
|
|
3
|
+
export type SessionOperationDeadlineReason = "operation-inactivity-timeout" | "operation-deadline-exceeded";
|
|
4
|
+
|
|
5
|
+
export type SessionOperationDeadlinePolicy = Readonly<{
|
|
6
|
+
inactivityTimeoutMs: number;
|
|
7
|
+
maximumDurationMs: number;
|
|
8
|
+
terminationTimeoutMs: number;
|
|
9
|
+
projectionTimeoutMs: number;
|
|
10
|
+
}>;
|
|
11
|
+
|
|
12
|
+
export type SessionOperationWatchdogOptions = Partial<SessionOperationDeadlinePolicy>;
|
|
13
|
+
export type BoundedResult<T> = { state: "settled"; value: T } | { state: "rejected"; error: unknown } | { state: "timeout" };
|
|
14
|
+
export type SessionOperationTerminationResult = { state: "settled" } | { state: "quarantine"; reasonCode: string };
|
|
15
|
+
|
|
16
|
+
export function bestEffortUnsubscribe(unsubscribe: (() => void) | null): void {
|
|
17
|
+
try { unsubscribe?.(); } catch { /* broken host cleanup cannot retain operation authority */ }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// A silent tool/provider gets 15 minutes. Any observed host event refreshes that
|
|
21
|
+
// allowance, while the six-hour ceiling prevents an endless stream of progress
|
|
22
|
+
// events from retaining runtime authority forever. Cleanup itself is separately
|
|
23
|
+
// bounded so Stop and watchdog recovery cannot inherit the original hang.
|
|
24
|
+
const DEFAULT_POLICY: SessionOperationDeadlinePolicy = Object.freeze({
|
|
25
|
+
inactivityTimeoutMs: 15 * 60_000,
|
|
26
|
+
maximumDurationMs: 6 * 60 * 60_000,
|
|
27
|
+
terminationTimeoutMs: 5_000,
|
|
28
|
+
projectionTimeoutMs: 2_500
|
|
29
|
+
});
|
|
30
|
+
const MAX_TIMEOUT_MS = 24 * 60 * 60_000;
|
|
31
|
+
const TERMINATION_CLEANUP_FAILED = "operation-termination-cleanup-failed";
|
|
32
|
+
|
|
33
|
+
function duration(value: unknown, fallback: number): number {
|
|
34
|
+
if (value === undefined) return fallback;
|
|
35
|
+
if (!Number.isSafeInteger(value) || Number(value) < 1 || Number(value) > MAX_TIMEOUT_MS) {
|
|
36
|
+
throw new Error("session-operation-watchdog-config-invalid");
|
|
37
|
+
}
|
|
38
|
+
return Number(value);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function sessionOperationDeadlinePolicy(options: SessionOperationWatchdogOptions = {}): SessionOperationDeadlinePolicy {
|
|
42
|
+
const policy = {
|
|
43
|
+
inactivityTimeoutMs: duration(options.inactivityTimeoutMs, DEFAULT_POLICY.inactivityTimeoutMs),
|
|
44
|
+
maximumDurationMs: duration(options.maximumDurationMs, DEFAULT_POLICY.maximumDurationMs),
|
|
45
|
+
terminationTimeoutMs: duration(options.terminationTimeoutMs, DEFAULT_POLICY.terminationTimeoutMs),
|
|
46
|
+
projectionTimeoutMs: duration(options.projectionTimeoutMs, DEFAULT_POLICY.projectionTimeoutMs)
|
|
47
|
+
};
|
|
48
|
+
if (policy.projectionTimeoutMs > policy.terminationTimeoutMs) throw new Error("session-operation-watchdog-config-invalid");
|
|
49
|
+
return Object.freeze(policy);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function boundedResult<T>(promise: PromiseLike<T>, timeoutMs: number): Promise<BoundedResult<T>> {
|
|
53
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
54
|
+
try {
|
|
55
|
+
return await Promise.race([
|
|
56
|
+
Promise.resolve(promise).then((value) => ({ state: "settled" as const, value }),
|
|
57
|
+
(error: unknown) => ({ state: "rejected" as const, error })),
|
|
58
|
+
new Promise<{ state: "timeout" }>((resolve) => { timer = setTimeout(() => resolve({ state: "timeout" }), timeoutMs); })
|
|
59
|
+
]);
|
|
60
|
+
} finally {
|
|
61
|
+
if (timer) clearTimeout(timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export class SessionOperationWatchdog {
|
|
66
|
+
readonly policy: SessionOperationDeadlinePolicy;
|
|
67
|
+
#idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
68
|
+
#maximumTimer: ReturnType<typeof setTimeout> | null = null;
|
|
69
|
+
#expire: ((reason: SessionOperationDeadlineReason) => void) | null = null;
|
|
70
|
+
#closed = false;
|
|
71
|
+
#termination: Promise<unknown> | null = null;
|
|
72
|
+
|
|
73
|
+
constructor(policy: SessionOperationDeadlinePolicy) { this.policy = policy; }
|
|
74
|
+
get terminating(): boolean { return this.#termination !== null; }
|
|
75
|
+
|
|
76
|
+
start(expire: (reason: SessionOperationDeadlineReason) => void): void {
|
|
77
|
+
if (this.#expire || this.#closed) throw new Error("session-operation-watchdog-state-invalid");
|
|
78
|
+
this.#expire = expire;
|
|
79
|
+
this.#armIdle();
|
|
80
|
+
this.#maximumTimer = setTimeout(() => this.#deadline("operation-deadline-exceeded"), this.policy.maximumDurationMs);
|
|
81
|
+
// An accepted operation owns live runtime work until it settles. Keep both
|
|
82
|
+
// deadline timers referenced so a headless Gateway cannot let Node exit
|
|
83
|
+
// while the provider promise is pending and silently skip recovery.
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
progress(): void {
|
|
87
|
+
if (this.#closed || !this.#expire) return;
|
|
88
|
+
this.#armIdle();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
close(): void {
|
|
92
|
+
if (this.#closed) return;
|
|
93
|
+
this.#closed = true;
|
|
94
|
+
if (this.#idleTimer) clearTimeout(this.#idleTimer);
|
|
95
|
+
if (this.#maximumTimer) clearTimeout(this.#maximumTimer);
|
|
96
|
+
this.#idleTimer = null; this.#maximumTimer = null; this.#expire = null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
terminate<T>(run: () => Promise<T>): Promise<T> {
|
|
100
|
+
if (!this.#termination) { this.close(); this.#termination = Promise.resolve().then(run); }
|
|
101
|
+
return this.#termination as Promise<T>;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#armIdle(): void {
|
|
105
|
+
if (this.#idleTimer) clearTimeout(this.#idleTimer);
|
|
106
|
+
this.#idleTimer = setTimeout(() => this.#deadline("operation-inactivity-timeout"), this.policy.inactivityTimeoutMs);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#deadline(reason: SessionOperationDeadlineReason): void {
|
|
110
|
+
if (this.#closed) return;
|
|
111
|
+
const expire = this.#expire; this.close(); expire?.(reason);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function armSessionOperationWatchdog(options: {
|
|
116
|
+
watchdog: SessionOperationWatchdog;
|
|
117
|
+
subscribe(listener: (event: unknown) => void): unknown;
|
|
118
|
+
observe(event: unknown): unknown;
|
|
119
|
+
retrySession?: any;
|
|
120
|
+
expire(reason: SessionOperationDeadlineReason): void;
|
|
121
|
+
}): () => void {
|
|
122
|
+
const retry = new SessionOperationRetryCancellation(options.retrySession);
|
|
123
|
+
const unsubscribe = options.subscribe((event) => {
|
|
124
|
+
options.watchdog.progress();
|
|
125
|
+
retry.observe(options.observe(event) as { retry?: unknown } | null | undefined);
|
|
126
|
+
});
|
|
127
|
+
if (typeof unsubscribe !== "function") throw new Error("session-operation-subscribe-invalid");
|
|
128
|
+
try { options.watchdog.start(options.expire); }
|
|
129
|
+
catch (error) { try { unsubscribe(); } catch { /* authority is quarantined by the caller */ } throw error; }
|
|
130
|
+
return () => { unsubscribe(); };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function terminateWatchedSessionOperation(options: {
|
|
134
|
+
watchdog: SessionOperationWatchdog;
|
|
135
|
+
settlement: "aborted" | "error";
|
|
136
|
+
reasonCode: string;
|
|
137
|
+
forcedReasonCode: string;
|
|
138
|
+
stream: { markAborted(reasonCode: string): void; markError(reasonCode: string): void;
|
|
139
|
+
forceLifecycleTermination(reasonCode: string): void };
|
|
140
|
+
completion(): Promise<void> | null;
|
|
141
|
+
settledCleanly(): boolean;
|
|
142
|
+
cancelApproval(reasonCode: string): void;
|
|
143
|
+
abortHost(): unknown;
|
|
144
|
+
clearQueue?(): void;
|
|
145
|
+
}): Promise<SessionOperationTerminationResult> {
|
|
146
|
+
return await options.watchdog.terminate(async () => {
|
|
147
|
+
const mark = (reasonCode: string) => options.settlement === "aborted"
|
|
148
|
+
? options.stream.markAborted(reasonCode) : options.stream.markError(reasonCode);
|
|
149
|
+
let cleanupFailed = false;
|
|
150
|
+
try { options.cancelApproval(options.reasonCode); } catch { cleanupFailed = true; }
|
|
151
|
+
try { options.clearQueue?.(); } catch { cleanupFailed = true; }
|
|
152
|
+
const terminalReason = cleanupFailed ? TERMINATION_CLEANUP_FAILED : options.reasonCode;
|
|
153
|
+
mark(terminalReason);
|
|
154
|
+
void Promise.resolve().then(options.abortHost).catch(() => undefined);
|
|
155
|
+
const completion = options.completion();
|
|
156
|
+
const completed = completion ? await boundedResult(completion, options.watchdog.policy.terminationTimeoutMs)
|
|
157
|
+
: { state: "timeout" as const };
|
|
158
|
+
if (!cleanupFailed && completed.state === "settled" && options.settledCleanly()) return { state: "settled" as const };
|
|
159
|
+
const quarantineReason = cleanupFailed ? TERMINATION_CLEANUP_FAILED : options.forcedReasonCode;
|
|
160
|
+
mark(quarantineReason); options.stream.forceLifecycleTermination(quarantineReason);
|
|
161
|
+
const forcedCompletion = options.completion();
|
|
162
|
+
if (forcedCompletion) await boundedResult(forcedCompletion, options.watchdog.policy.projectionTimeoutMs);
|
|
163
|
+
return { state: "quarantine" as const, reasonCode: quarantineReason };
|
|
164
|
+
});
|
|
165
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import type { PiSessionInfo } from "./session-catalog.ts";
|
|
4
|
+
import { preferAuthoritativePiagentGuard } from "./extension-authority.ts";
|
|
5
|
+
import { rpcUiContext } from "./rpc-ui-context.ts";
|
|
6
|
+
|
|
7
|
+
export type RuntimeHandle = { dispose(): Promise<void>; session?: any };
|
|
8
|
+
export type RuntimeFactory = (info: PiSessionInfo, runtimeInstanceRef: string,
|
|
9
|
+
sessionManager?: any) => Promise<RuntimeHandle>;
|
|
10
|
+
|
|
11
|
+
export function createProductionRuntimeFactory(options: {
|
|
12
|
+
host: any;
|
|
13
|
+
agentDir: string;
|
|
14
|
+
packageRoot: string;
|
|
15
|
+
modelRuntime?: any;
|
|
16
|
+
}): RuntimeFactory {
|
|
17
|
+
return async (info, _runtimeInstanceRef, sessionManager) => {
|
|
18
|
+
const guard = path.join(options.packageRoot, "packages", "piagent-core", "extensions", "piagent-guard.ts");
|
|
19
|
+
const createRuntime = async ({ cwd, agentDir, sessionManager, sessionStartEvent }: any) => {
|
|
20
|
+
const services = await options.host.createAgentSessionServices({
|
|
21
|
+
cwd, agentDir, modelRuntime: options.modelRuntime,
|
|
22
|
+
resourceLoaderOptions: {
|
|
23
|
+
additionalExtensionPaths: [guard],
|
|
24
|
+
extensionsOverride: preferAuthoritativePiagentGuard(guard)
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
const extensionErrors = services.resourceLoader.getExtensions().errors;
|
|
28
|
+
if (extensionErrors.length) throw new Error("session-runtime-extension-load-failed");
|
|
29
|
+
const created = await options.host.createAgentSessionFromServices({ services, sessionManager, sessionStartEvent });
|
|
30
|
+
return { ...created, services, diagnostics: services.diagnostics };
|
|
31
|
+
};
|
|
32
|
+
const manager = sessionManager ?? options.host.SessionManager.open(info.path);
|
|
33
|
+
const runtime = await options.host.createAgentSessionRuntime(createRuntime, {
|
|
34
|
+
cwd: info.cwd,
|
|
35
|
+
agentDir: options.agentDir,
|
|
36
|
+
sessionManager: manager,
|
|
37
|
+
sessionStartEvent: { type: "session_start", reason: "resume", previousSessionFile: info.path }
|
|
38
|
+
});
|
|
39
|
+
const bind = async (session: any) => session.bindExtensions({ mode: "rpc", uiContext: rpcUiContext() });
|
|
40
|
+
runtime.setRebindSession(bind);
|
|
41
|
+
try { await bind(runtime.session); }
|
|
42
|
+
catch (error) { await runtime.dispose().catch(() => undefined); throw error; }
|
|
43
|
+
return runtime;
|
|
44
|
+
};
|
|
45
|
+
}
|