@piagent/platform 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -3
- package/README.md +3 -3
- package/architecture/layers.json +96 -1
- package/docs/README.md +1 -0
- package/docs/architecture.md +1 -0
- package/docs/authority-manifest.md +1 -0
- package/docs/auto-delegation-policy.md +1 -0
- package/docs/capability-packs.md +10 -2
- package/docs/command-reference-vietnamese.md +5 -4
- package/docs/context-engine.md +1 -0
- package/docs/context-window-policy.md +1 -0
- package/docs/distribution-standard.md +8 -7
- package/docs/herdr-workflow.md +1 -0
- package/docs/mcp-and-tools.md +14 -9
- package/docs/memory-policy.md +1 -0
- package/docs/model-options.md +1 -0
- package/docs/oauth-providers.md +1 -0
- package/docs/operator-manual-vietnamese.md +15 -12
- package/docs/package-architecture-notes.md +1 -0
- package/docs/project-adapters.md +1 -0
- package/docs/project-onboarding.md +1 -0
- package/docs/publishing-for-teams.md +1 -0
- package/docs/quality-benchmark.md +1 -0
- package/docs/quickstart-vietnamese.md +3 -2
- package/docs/release-install-policy.md +11 -7
- package/docs/runtime-harness-standard.md +1 -0
- package/docs/runtime-policy-design.md +1 -0
- package/docs/runtime-quality-baseline.md +1 -0
- package/docs/security-redaction-benchmark.md +1 -0
- package/docs/security-threat-model.md +2 -1
- package/docs/subagent-orchestration-capabilities.md +1 -0
- package/docs/subagents-and-multiagent.md +1 -0
- package/docs/task-implementation-contract.md +1 -0
- package/docs/task-lifecycle-vietnamese.md +1 -0
- package/docs/team-onboarding.md +3 -2
- package/docs/third-party-neutrality.md +26 -0
- package/docs/usage-observability.md +1 -0
- package/docs/vercel-docs-site.md +1 -0
- package/docs/workflow-recipes.md +1 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- 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 +1 -1
- 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 +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/fixtures/mutation-provenance-record.invalid.json +17 -0
- package/evals/fixtures/mutation-provenance-record.valid.json +27 -0
- package/evals/fixtures/piagent-webui/approval-v1.invalid.json +54 -0
- package/evals/fixtures/piagent-webui/approval-v1.valid.json +58 -0
- package/evals/fixtures/piagent-webui/attachment-v1.invalid.json +25 -0
- package/evals/fixtures/piagent-webui/attachment-v1.valid.json +25 -0
- package/evals/fixtures/piagent-webui/capabilities-v1.invalid.json +51 -0
- package/evals/fixtures/piagent-webui/capabilities-v1.valid.json +122 -0
- package/evals/fixtures/piagent-webui/commit-summary-v1.invalid.json +10 -0
- package/evals/fixtures/piagent-webui/commit-summary-v1.valid.json +25 -0
- package/evals/fixtures/piagent-webui/control-command-v1.invalid.json +37 -0
- package/evals/fixtures/piagent-webui/control-command-v1.valid.json +35 -0
- package/evals/fixtures/piagent-webui/diff-v1.invalid.json +13 -0
- package/evals/fixtures/piagent-webui/diff-v1.valid.json +141 -0
- package/evals/fixtures/piagent-webui/gateway-capabilities-v1.invalid.json +35 -0
- package/evals/fixtures/piagent-webui/gateway-capabilities-v1.valid.json +35 -0
- package/evals/fixtures/piagent-webui/gateway-protocol-v1.invalid.json +14 -0
- package/evals/fixtures/piagent-webui/gateway-protocol-v1.valid.json +14 -0
- package/evals/fixtures/piagent-webui/handoff-history-v1.invalid.json +16 -0
- package/evals/fixtures/piagent-webui/handoff-history-v1.valid.json +16 -0
- package/evals/fixtures/piagent-webui/model-catalog-v1.invalid.json +31 -0
- package/evals/fixtures/piagent-webui/model-catalog-v1.valid.json +43 -0
- package/evals/fixtures/piagent-webui/queue-v1.invalid.json +29 -0
- package/evals/fixtures/piagent-webui/queue-v1.valid.json +44 -0
- package/evals/fixtures/piagent-webui/recovery-history-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/recovery-history-v1.valid.json +20 -0
- package/evals/fixtures/piagent-webui/release-monitor-v1.invalid.json +12 -0
- package/evals/fixtures/piagent-webui/release-monitor-v1.valid.json +29 -0
- package/evals/fixtures/piagent-webui/review-state-v1.invalid.json +25 -0
- package/evals/fixtures/piagent-webui/review-state-v1.valid.json +38 -0
- package/evals/fixtures/piagent-webui/runtime-event-v2.invalid.json +49 -0
- package/evals/fixtures/piagent-webui/runtime-event-v2.valid.json +49 -0
- package/evals/fixtures/piagent-webui/session-catalog-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/session-catalog-v1.valid.json +58 -0
- package/evals/fixtures/piagent-webui/session-command-v1.invalid.json +19 -0
- package/evals/fixtures/piagent-webui/session-command-v1.valid.json +19 -0
- package/evals/fixtures/piagent-webui/snapshot-v1.invalid.json +5 -0
- package/evals/fixtures/piagent-webui/snapshot-v1.valid.json +191 -0
- package/evals/fixtures/piagent-webui/source-change-v1.invalid.json +57 -0
- package/evals/fixtures/piagent-webui/source-change-v1.valid.json +98 -0
- package/evals/fixtures/piagent-webui/source-mutation-v1.invalid.json +19 -0
- package/evals/fixtures/piagent-webui/source-mutation-v1.valid.json +38 -0
- package/evals/fixtures/piagent-webui/source-revert-v1.invalid.json +20 -0
- package/evals/fixtures/piagent-webui/source-revert-v1.valid.json +62 -0
- package/evals/fixtures/piagent-webui/subagent-tree-v1.invalid.json +26 -0
- package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +39 -0
- package/evals/fixtures/piagent-webui/task-index-v1.invalid.json +15 -0
- package/evals/fixtures/piagent-webui/task-index-v1.valid.json +15 -0
- package/evals/fixtures/piagent-webui/task-timeline-v1.invalid.json +14 -0
- package/evals/fixtures/piagent-webui/task-timeline-v1.valid.json +16 -0
- package/evals/fixtures/piagent-webui/transcript-v1.invalid.json +35 -0
- package/evals/fixtures/piagent-webui/transcript-v1.valid.json +55 -0
- package/evals/fixtures/task-baseline-manifest.invalid.json +16 -0
- package/evals/fixtures/task-baseline-manifest.valid.json +41 -0
- package/evals/fixtures/verifier-file-snapshot.invalid.json +21 -0
- package/evals/fixtures/verifier-file-snapshot.valid.json +21 -0
- package/package.json +30 -3
- package/packages/piagent-core/benchmark/benchmark-candidate.d.ts +1 -0
- package/packages/piagent-core/benchmark/benchmark-forensics.d.ts +1 -0
- package/packages/piagent-core/benchmark/benchmark-ledger.d.ts +4 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.d.ts +1 -0
- package/packages/piagent-core/benchmark/benchmark-runtime.d.ts +1 -0
- package/packages/piagent-core/capabilities/capability-core.js +2 -2
- package/packages/piagent-core/capabilities/capability-verification-cache.js +280 -0
- package/packages/piagent-core/capabilities/runtime-integrity.js +153 -1
- package/packages/piagent-core/extensions/document-intake.ts +24 -10
- package/packages/piagent-core/extensions/piagent-guard.ts +90 -73
- package/packages/piagent-core/extensions/redaction-core.d.ts +2 -0
- package/packages/piagent-core/extensions/state-retention.js +77 -44
- package/packages/piagent-core/extensions/task-journal.js +30 -1
- package/packages/piagent-core/extensions/task-state.js +33 -36
- package/packages/piagent-core/mcp/mcp-server-catalog.js +5 -5
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +2 -2
- package/packages/piagent-core/runtime/hooks/tool-call-hook.ts +5 -2
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +20 -19
- package/packages/piagent-core/runtime/hooks/tool-result-metadata.ts +13 -0
- package/packages/piagent-core/runtime/input/chat-images.ts +2 -2
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +94 -0
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +318 -0
- package/packages/piagent-core/runtime/inspection/commit-summary-projection.ts +59 -0
- package/packages/piagent-core/runtime/inspection/context-telemetry-inspection.ts +37 -0
- package/packages/piagent-core/runtime/inspection/criteria-links.ts +295 -0
- package/packages/piagent-core/runtime/inspection/diff-projection.ts +499 -0
- package/packages/piagent-core/runtime/inspection/git-filter-safety.ts +38 -0
- package/packages/piagent-core/runtime/inspection/git-numstat-adapter.ts +71 -0
- package/packages/piagent-core/runtime/inspection/git-status-adapter.ts +493 -0
- package/packages/piagent-core/runtime/inspection/git-tree-adapter.ts +108 -0
- package/packages/piagent-core/runtime/inspection/mcp-control.ts +10 -0
- package/packages/piagent-core/runtime/inspection/mutation-provenance-contract.ts +131 -0
- package/packages/piagent-core/runtime/inspection/mutation-provenance-recorder.ts +48 -0
- package/packages/piagent-core/runtime/inspection/mutation-provenance-store.ts +161 -0
- package/packages/piagent-core/runtime/inspection/project-runtime-inspection.ts +36 -0
- package/packages/piagent-core/runtime/inspection/review-state-contract.ts +108 -0
- package/packages/piagent-core/runtime/inspection/review-state-projection.ts +93 -0
- package/packages/piagent-core/runtime/inspection/review-state-store.ts +112 -0
- package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +262 -0
- package/packages/piagent-core/runtime/inspection/same-process-bridge-proof.ts +103 -0
- package/packages/piagent-core/runtime/inspection/source-change-projection.ts +471 -0
- package/packages/piagent-core/runtime/inspection/source-evidence-contract.ts +139 -0
- package/packages/piagent-core/runtime/inspection/source-evidence-store.ts +315 -0
- package/packages/piagent-core/runtime/inspection/source-handoff-store.ts +70 -0
- package/packages/piagent-core/runtime/inspection/source-inspection-plan.ts +34 -0
- package/packages/piagent-core/runtime/inspection/source-mutation-projection.ts +164 -0
- package/packages/piagent-core/runtime/inspection/source-mutation-store.ts +168 -0
- package/packages/piagent-core/runtime/inspection/source-open-target.ts +38 -0
- package/packages/piagent-core/runtime/inspection/source-revert-projection.ts +147 -0
- package/packages/piagent-core/runtime/inspection/task-baseline-start-capture.ts +22 -0
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +160 -0
- package/packages/piagent-core/runtime/inspection/task-control-journal.ts +131 -0
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +95 -0
- package/packages/piagent-core/runtime/inspection/task-provenance-projection.ts +60 -0
- package/packages/piagent-core/runtime/inspection/task-recovery-timeline.ts +88 -0
- package/packages/piagent-core/runtime/inspection/task-run-index.ts +87 -0
- package/packages/piagent-core/runtime/inspection/task-source-projection.ts +401 -0
- package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +101 -0
- package/packages/piagent-core/runtime/inspection/text-diff.ts +60 -0
- package/packages/piagent-core/runtime/inspection/verifier-snapshot-contract.ts +114 -0
- package/packages/piagent-core/runtime/inspection/verifier-snapshot-store.ts +189 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +320 -0
- package/packages/piagent-core/runtime/inspection/workspace-file-reader.ts +132 -0
- package/packages/piagent-core/runtime/inspection/workspace-source-projection.ts +96 -0
- package/packages/piagent-core/runtime/inspection/zero-turn-conformance.ts +175 -0
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +68 -4
- package/packages/piagent-core/runtime/policy/source-index-transaction.ts +173 -0
- package/packages/piagent-core/runtime/policy/source-mutation-guard-binding.ts +42 -0
- package/packages/piagent-core/runtime/policy/source-mutation-guard.ts +184 -0
- package/packages/piagent-core/runtime/policy/source-worktree-transaction.ts +105 -0
- package/packages/piagent-core/runtime/product/activity-inspector-footer.ts +5 -3
- package/packages/piagent-core/runtime/product/activity-inspector.ts +85 -343
- package/packages/piagent-core/runtime/recovery/resume-state.ts +38 -1
- package/packages/piagent-core/runtime/registration/activity-inspector-command.ts +68 -14
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +13 -14
- package/packages/piagent-core/runtime/session/model-authorship-state.ts +31 -3
- package/packages/piagent-core/runtime/session/runtime-state.ts +11 -1
- package/packages/piagent-webui/dist/client/assets/index-DjMntaK5.js +172 -0
- package/packages/piagent-webui/dist/client/assets/index-DwKCWbwd.css +1 -0
- package/packages/piagent-webui/dist/client/index.html +16 -0
- package/packages/piagent-webui/extension/attachment-store.ts +323 -0
- package/packages/piagent-webui/extension/held-message-queue.ts +311 -0
- package/packages/piagent-webui/extension/lifecycle-controller.ts +408 -0
- package/packages/piagent-webui/extension/lifecycle-event-adapter.ts +23 -0
- package/packages/piagent-webui/extension/piagent-webui.ts +470 -0
- package/packages/piagent-webui/extension/review-controller.ts +160 -0
- package/packages/piagent-webui/extension/same-session-bridge.ts +500 -0
- package/packages/piagent-webui/extension/session-options-controller.ts +281 -0
- package/packages/piagent-webui/extension/session-stream-adapter.ts +249 -0
- package/packages/piagent-webui/extension/source-mutation-controller.ts +215 -0
- package/packages/piagent-webui/extension/source-open-controller.ts +136 -0
- package/packages/piagent-webui/extension/source-revert-controller.ts +189 -0
- package/packages/piagent-webui/extension/terminal-session-adapter.ts +73 -0
- package/packages/piagent-webui/extension/vscode-handoff.ts +51 -0
- package/packages/piagent-webui/gateway/control-socket.ts +92 -0
- package/packages/piagent-webui/gateway/gateway-events.ts +60 -0
- package/packages/piagent-webui/gateway/gateway-protocol-service.ts +64 -0
- package/packages/piagent-webui/gateway/gateway-service.ts +215 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +110 -0
- package/packages/piagent-webui/gateway/mcp-auth-broker.ts +158 -0
- package/packages/piagent-webui/gateway/native-project-picker.ts +29 -0
- package/packages/piagent-webui/gateway/pi-host.ts +41 -0
- package/packages/piagent-webui/gateway/profile-state.ts +1 -0
- package/packages/piagent-webui/gateway/project-registry.ts +108 -0
- package/packages/piagent-webui/gateway/provider-auth-broker.ts +172 -0
- package/packages/piagent-webui/gateway/session-catalog.ts +128 -0
- package/packages/piagent-webui/gateway/session-command-controller.ts +237 -0
- package/packages/piagent-webui/gateway/session-command-store.ts +224 -0
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +204 -0
- package/packages/piagent-webui/gateway/session-lease-store.ts +1 -0
- package/packages/piagent-webui/gateway/session-metadata-store.ts +152 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +485 -0
- package/packages/piagent-webui/ownership/gateway-runtime-context.ts +8 -0
- package/packages/piagent-webui/ownership/profile-state.ts +117 -0
- package/packages/piagent-webui/ownership/session-lease-store.ts +329 -0
- package/packages/piagent-webui/ownership/session-refs.ts +8 -0
- package/packages/piagent-webui/package.json +33 -0
- package/packages/piagent-webui/server/README.md +4 -0
- package/packages/piagent-webui/server/benchmark-release-monitor.ts +230 -0
- package/packages/piagent-webui/server/core-inspection-provider.ts +403 -0
- package/packages/piagent-webui/server/gateway-websocket.ts +143 -0
- package/packages/piagent-webui/server/http-security.ts +27 -0
- package/packages/piagent-webui/server/ipc-read-model-client.ts +89 -0
- package/packages/piagent-webui/server/loopback-server.ts +368 -0
- package/packages/piagent-webui/server/read-model-provider.ts +37 -0
- package/packages/piagent-webui/server/read-only-router.ts +105 -0
- package/packages/piagent-webui/server/session-auth.ts +88 -0
- package/packages/piagent-webui/server/sidecar-main.ts +86 -0
- package/packages/piagent-webui/server/sse-hub.ts +76 -0
- package/packages/piagent-webui/server/static-bundle.ts +40 -0
- package/packages/piagent-webui/server/transcript-projection.ts +110 -0
- package/schemas/mutation-provenance-record.schema.json +100 -0
- package/schemas/piagent-webui/approval-v1.schema.json +654 -0
- package/schemas/piagent-webui/attachment-v1.schema.json +267 -0
- package/schemas/piagent-webui/capabilities-v1.schema.json +729 -0
- package/schemas/piagent-webui/catalog-v1.json +240 -0
- package/schemas/piagent-webui/commit-summary-v1.schema.json +63 -0
- package/schemas/piagent-webui/common-v1.schema.json +479 -0
- package/schemas/piagent-webui/control-command-v1.schema.json +1780 -0
- package/schemas/piagent-webui/diff-v1.schema.json +826 -0
- package/schemas/piagent-webui/gateway-capabilities-v1.schema.json +136 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +456 -0
- package/schemas/piagent-webui/handoff-history-v1.schema.json +92 -0
- package/schemas/piagent-webui/model-catalog-v1.schema.json +95 -0
- package/schemas/piagent-webui/queue-v1.schema.json +99 -0
- package/schemas/piagent-webui/recovery-history-v1.schema.json +144 -0
- package/schemas/piagent-webui/release-monitor-v1.schema.json +173 -0
- package/schemas/piagent-webui/review-state-v1.schema.json +206 -0
- package/schemas/piagent-webui/runtime-event-v2.schema.json +5206 -0
- package/schemas/piagent-webui/session-catalog-v1.schema.json +222 -0
- package/schemas/piagent-webui/session-command-v1.schema.json +390 -0
- package/schemas/piagent-webui/snapshot-v1.schema.json +2622 -0
- package/schemas/piagent-webui/source-change-v1.schema.json +763 -0
- package/schemas/piagent-webui/source-mutation-v1.schema.json +96 -0
- package/schemas/piagent-webui/source-revert-v1.schema.json +130 -0
- package/schemas/piagent-webui/subagent-tree-v1.schema.json +88 -0
- package/schemas/piagent-webui/task-index-v1.schema.json +74 -0
- package/schemas/piagent-webui/task-timeline-v1.schema.json +49 -0
- package/schemas/piagent-webui/transcript-v1.schema.json +243 -0
- package/schemas/task-baseline-manifest.schema.json +122 -0
- package/schemas/verifier-file-snapshot.schema.json +81 -0
- package/scripts/check-architecture.mjs +1 -0
- package/scripts/check-doc-languages.mjs +19 -2
- package/scripts/check-third-party-neutrality.mjs +43 -0
- package/scripts/install-global.sh +1 -1
- package/scripts/piagent-cli.mjs +17 -3
- package/scripts/piagent-dashboard.mjs +208 -0
- package/scripts/piagent-webui-launcher.mjs +121 -0
- package/scripts/typescript-loader.mjs +26 -1
- package/scripts/verify-local.sh +253 -0
- package/scripts/verify-release-identity.mjs +4 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
1
2
|
import fs from "node:fs";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
|
|
@@ -16,75 +17,107 @@ function processAlive(pid) {
|
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
function ensurePrivateParent(filePath, projectRoot) {
|
|
21
|
+
const safeFilePath = projectRoot
|
|
22
|
+
? resolveLocalStatePath(projectRoot, filePath, { label: "Local JSONL state" })
|
|
23
|
+
: filePath;
|
|
24
|
+
if (projectRoot) ensurePrivateStateDirectory(projectRoot, path.dirname(filePath), "Local JSONL state directory");
|
|
25
|
+
else fs.mkdirSync(path.dirname(safeFilePath), { recursive: true, mode: 0o700 });
|
|
26
|
+
try {
|
|
27
|
+
fs.chmodSync(path.dirname(safeFilePath), 0o700);
|
|
28
|
+
} catch {
|
|
29
|
+
// Best effort on filesystems that do not expose POSIX modes.
|
|
30
|
+
}
|
|
31
|
+
return safeFilePath;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function publishOwnedLock(lockPath) {
|
|
35
|
+
let candidatePath;
|
|
36
|
+
let descriptor;
|
|
37
|
+
while (true) {
|
|
38
|
+
const candidate = `${lockPath}.${process.pid}.${crypto.randomBytes(12).toString("hex")}.candidate`;
|
|
39
|
+
try {
|
|
40
|
+
descriptor = fs.openSync(candidate, "wx", 0o600);
|
|
41
|
+
candidatePath = candidate;
|
|
42
|
+
break;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error?.code !== "EEXIST") throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
fs.writeFileSync(descriptor, `${process.pid}\n`);
|
|
49
|
+
fs.closeSync(descriptor);
|
|
50
|
+
descriptor = undefined;
|
|
51
|
+
try {
|
|
52
|
+
fs.linkSync(candidatePath, lockPath);
|
|
53
|
+
const stat = fs.lstatSync(candidatePath);
|
|
54
|
+
return { device: stat.dev, inode: stat.ino };
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error?.code === "EEXIST") return undefined;
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
} finally {
|
|
60
|
+
if (descriptor !== undefined) {
|
|
61
|
+
try { fs.closeSync(descriptor); } catch {}
|
|
62
|
+
}
|
|
63
|
+
fs.rmSync(candidatePath, { force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function releaseOwnedLock(lockPath, owner) {
|
|
68
|
+
try {
|
|
69
|
+
const stat = fs.lstatSync(lockPath);
|
|
70
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.dev !== owner.device || stat.ino !== owner.inode) return;
|
|
71
|
+
fs.rmSync(lockPath, { force: true });
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error?.code !== "ENOENT") throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
19
77
|
function recoverDeadLock(lockPath) {
|
|
20
78
|
const recoveryPath = `${lockPath}.recovery`;
|
|
21
|
-
|
|
79
|
+
const recoveryOwner = publishOwnedLock(recoveryPath);
|
|
80
|
+
// A recovery owner is never stolen automatically: without a kernel
|
|
81
|
+
// compare-and-delete primitive, doing so can delete a live successor. A
|
|
82
|
+
// crashed recovery owner therefore fails closed and needs operator cleanup.
|
|
83
|
+
if (!recoveryOwner) return false;
|
|
22
84
|
try {
|
|
23
|
-
recoveryDescriptor = fs.openSync(recoveryPath, "wx", 0o600);
|
|
24
85
|
let stat;
|
|
25
|
-
let
|
|
86
|
+
let ownerText;
|
|
26
87
|
try {
|
|
27
88
|
stat = fs.lstatSync(lockPath);
|
|
28
89
|
if (!stat.isFile() || stat.isSymbolicLink()) return false;
|
|
29
|
-
|
|
90
|
+
ownerText = fs.readFileSync(lockPath, "utf8").trim();
|
|
30
91
|
} catch (error) {
|
|
31
92
|
return error?.code === "ENOENT";
|
|
32
93
|
}
|
|
33
|
-
const
|
|
34
|
-
const
|
|
94
|
+
const parsedOwner = /^[1-9]\d*$/.test(ownerText) ? Number(ownerText) : undefined;
|
|
95
|
+
const owner = Number.isSafeInteger(parsedOwner) && parsedOwner > 0 ? parsedOwner : undefined;
|
|
96
|
+
const validDeadOwner = owner !== undefined && !processAlive(owner);
|
|
97
|
+
const malformedAndStale = owner === undefined && Date.now() - stat.mtimeMs > LOCK_STALE_MS;
|
|
35
98
|
if (!validDeadOwner && !malformedAndStale) return false;
|
|
36
99
|
fs.rmSync(lockPath, { force: true });
|
|
37
100
|
return true;
|
|
38
|
-
} catch (error) {
|
|
39
|
-
if (error?.code === "EEXIST") return false;
|
|
40
|
-
throw error;
|
|
41
101
|
} finally {
|
|
42
|
-
|
|
43
|
-
try { fs.closeSync(recoveryDescriptor); } catch {}
|
|
44
|
-
fs.rmSync(recoveryPath, { force: true });
|
|
45
|
-
}
|
|
102
|
+
releaseOwnedLock(recoveryPath, recoveryOwner);
|
|
46
103
|
}
|
|
47
104
|
}
|
|
48
105
|
|
|
49
|
-
function ensurePrivateParent(filePath, projectRoot) {
|
|
50
|
-
const safeFilePath = projectRoot
|
|
51
|
-
? resolveLocalStatePath(projectRoot, filePath, { label: "Local JSONL state" })
|
|
52
|
-
: filePath;
|
|
53
|
-
if (projectRoot) ensurePrivateStateDirectory(projectRoot, path.dirname(filePath), "Local JSONL state directory");
|
|
54
|
-
else fs.mkdirSync(path.dirname(safeFilePath), { recursive: true, mode: 0o700 });
|
|
55
|
-
try {
|
|
56
|
-
fs.chmodSync(path.dirname(safeFilePath), 0o700);
|
|
57
|
-
} catch {
|
|
58
|
-
// Best effort on filesystems that do not expose POSIX modes.
|
|
59
|
-
}
|
|
60
|
-
return safeFilePath;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
106
|
function withFileLock(filePath, action) {
|
|
64
107
|
const lockPath = `${filePath}.lock`;
|
|
65
108
|
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
66
|
-
let
|
|
67
|
-
while (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
} catch (error) {
|
|
72
|
-
if (error?.code !== "EEXIST") throw error;
|
|
73
|
-
if (recoverDeadLock(lockPath)) continue;
|
|
74
|
-
if (Date.now() >= deadline) {
|
|
75
|
-
throw new Error(`Timed out waiting for state lock: ${path.basename(filePath)}`);
|
|
76
|
-
}
|
|
77
|
-
Atomics.wait(sleepBuffer, 0, 0, 10);
|
|
109
|
+
let owner;
|
|
110
|
+
while (!(owner = publishOwnedLock(lockPath))) {
|
|
111
|
+
if (recoverDeadLock(lockPath)) continue;
|
|
112
|
+
if (Date.now() >= deadline) {
|
|
113
|
+
throw new Error(`Timed out waiting for state lock: ${path.basename(filePath)}`);
|
|
78
114
|
}
|
|
115
|
+
Atomics.wait(sleepBuffer, 0, 0, 10);
|
|
79
116
|
}
|
|
80
117
|
try {
|
|
81
118
|
return action();
|
|
82
119
|
} finally {
|
|
83
|
-
|
|
84
|
-
fs.closeSync(descriptor);
|
|
85
|
-
} finally {
|
|
86
|
-
fs.rmSync(lockPath, { force: true });
|
|
87
|
-
}
|
|
120
|
+
releaseOwnedLock(lockPath, owner);
|
|
88
121
|
}
|
|
89
122
|
}
|
|
90
123
|
|
|
@@ -282,6 +282,23 @@ export function appendTaskJournalEventAtMost(cwd, event, options = {}) {
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
+
export function transactTaskJournal(cwd, transaction) {
|
|
286
|
+
if (typeof transaction !== "function") throw new TypeError("Task journal transaction must be a function");
|
|
287
|
+
const safePaths = writableJournalPaths(cwd);
|
|
288
|
+
const release = acquireLock(safePaths);
|
|
289
|
+
try {
|
|
290
|
+
const journal = recoverIncompleteJournalTail(cwd, safePaths, readTaskJournal(cwd));
|
|
291
|
+
if (journal.corruptions.length > 0) throw new Error(`Task journal chain is corrupt: ${journal.corruptions[0]}`);
|
|
292
|
+
const decision = transaction({ events: [...journal.events], head: journal.head ? { ...journal.head } : undefined });
|
|
293
|
+
if (!decision || typeof decision !== "object" || Array.isArray(decision)) throw new Error("Task journal transaction returned an invalid decision");
|
|
294
|
+
if (!decision.event) return { appended: false, record: undefined, result: decision.result };
|
|
295
|
+
const record = appendJournalRecord(safePaths, journal, decision.event, decision.recordedAt);
|
|
296
|
+
return { appended: true, record, result: decision.result };
|
|
297
|
+
} finally {
|
|
298
|
+
release();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
285
302
|
export function readTaskJournal(cwd, options = {}) {
|
|
286
303
|
const paths = taskJournalPaths(cwd);
|
|
287
304
|
let safePath;
|
|
@@ -290,7 +307,17 @@ export function readTaskJournal(cwd, options = {}) {
|
|
|
290
307
|
} catch {
|
|
291
308
|
return { events: [], corruptions: ["journal path is unsafe"], head: undefined };
|
|
292
309
|
}
|
|
293
|
-
if (!fs.existsSync(safePath)) return { events: [], corruptions: [], head: undefined };
|
|
310
|
+
if (!fs.existsSync(safePath)) return { events: [], corruptions: [], head: undefined, inputTruncated: false, omittedEvents: 0 };
|
|
311
|
+
if (Number.isInteger(options.maximumBytes) && options.maximumBytes > 0) {
|
|
312
|
+
try {
|
|
313
|
+
const stat = fs.lstatSync(safePath);
|
|
314
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > options.maximumBytes) {
|
|
315
|
+
return { events: [], corruptions: ["journal exceeds bounded inspection size"], head: undefined, inputTruncated: true, omittedEvents: 0 };
|
|
316
|
+
}
|
|
317
|
+
} catch {
|
|
318
|
+
return { events: [], corruptions: ["journal size could not be inspected"], head: undefined, inputTruncated: false, omittedEvents: 0 };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
294
321
|
const content = fs.readFileSync(safePath);
|
|
295
322
|
const finalNewline = content.length === 0 || content.at(-1) === 0x0a;
|
|
296
323
|
const lastNewline = finalNewline ? content.length - 1 : content.lastIndexOf(0x0a);
|
|
@@ -329,6 +356,8 @@ export function readTaskJournal(cwd, options = {}) {
|
|
|
329
356
|
return {
|
|
330
357
|
events: visibleEvents,
|
|
331
358
|
corruptions,
|
|
359
|
+
inputTruncated: visibleEvents.length < events.length,
|
|
360
|
+
omittedEvents: Math.max(0, events.length - visibleEvents.length),
|
|
332
361
|
recoverableTailBytes,
|
|
333
362
|
validBytes,
|
|
334
363
|
head: events.at(-1)
|
|
@@ -66,17 +66,9 @@ export function safeTaskId(value) {
|
|
|
66
66
|
return normalized || "task";
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function stateRoot(cwd) {
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function tasksRoot(cwd) {
|
|
74
|
-
return path.join(stateRoot(cwd), "tasks");
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function sessionsRoot(cwd) {
|
|
78
|
-
return path.join(stateRoot(cwd), "session-tasks");
|
|
79
|
-
}
|
|
69
|
+
function stateRoot(cwd) { return path.join(cwd, ".pi", "piagent-state"); }
|
|
70
|
+
function tasksRoot(cwd) { return path.join(stateRoot(cwd), "tasks"); }
|
|
71
|
+
function sessionsRoot(cwd) { return path.join(stateRoot(cwd), "session-tasks"); }
|
|
80
72
|
|
|
81
73
|
function taskRunPath(cwd, taskRunId) {
|
|
82
74
|
return path.join(tasksRoot(cwd), `${safeTaskId(taskRunId)}.json`);
|
|
@@ -98,7 +90,12 @@ function readJson(cwd, filePath) {
|
|
|
98
90
|
const safePath = resolveLocalStatePath(cwd, filePath, { label: "Task state", kind: "file" });
|
|
99
91
|
const descriptor = fs.openSync(safePath, fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0));
|
|
100
92
|
try {
|
|
101
|
-
|
|
93
|
+
const before = fs.fstatSync(descriptor, { bigint: true });
|
|
94
|
+
if (!before.isFile() || before.size > BigInt(8 * 1024 * 1024)) return undefined;
|
|
95
|
+
const value = JSON.parse(fs.readFileSync(descriptor, "utf8"));
|
|
96
|
+
const after = fs.fstatSync(descriptor, { bigint: true });
|
|
97
|
+
const fields = ["dev", "ino", "mode", "size", "mtimeNs", "ctimeNs"];
|
|
98
|
+
return fields.every((field) => before[field] === after[field]) ? value : undefined;
|
|
102
99
|
} finally {
|
|
103
100
|
fs.closeSync(descriptor);
|
|
104
101
|
}
|
|
@@ -586,7 +583,6 @@ function gitOutput(cwd, args, options = {}) {
|
|
|
586
583
|
stdio: ["ignore", "pipe", "ignore"]
|
|
587
584
|
});
|
|
588
585
|
}
|
|
589
|
-
|
|
590
586
|
function gitHasHead(cwd) {
|
|
591
587
|
try {
|
|
592
588
|
gitOutput(cwd, ["rev-parse", "--verify", "HEAD"]);
|
|
@@ -595,7 +591,6 @@ function gitHasHead(cwd) {
|
|
|
595
591
|
return false;
|
|
596
592
|
}
|
|
597
593
|
}
|
|
598
|
-
|
|
599
594
|
export function isGitWorkingTree(cwd) {
|
|
600
595
|
try {
|
|
601
596
|
return gitOutput(cwd, ["rev-parse", "--is-inside-work-tree"]).trim() === "true";
|
|
@@ -603,20 +598,18 @@ export function isGitWorkingTree(cwd) {
|
|
|
603
598
|
return false;
|
|
604
599
|
}
|
|
605
600
|
}
|
|
606
|
-
|
|
607
601
|
function normalizedGitPath(value) {
|
|
608
602
|
return String(value ?? "").replaceAll("\\", "/").replace(/^\/+/, "");
|
|
609
603
|
}
|
|
610
|
-
|
|
611
604
|
function prefixedGitPath(prefix, file) {
|
|
612
605
|
const normalized = normalizedGitPath(file);
|
|
613
606
|
return prefix ? `${prefix}/${normalized}` : normalized;
|
|
614
607
|
}
|
|
615
608
|
|
|
616
|
-
function workingTreeFilesForGitRoot(cwd) {
|
|
609
|
+
function workingTreeFilesForGitRoot(cwd, observeRename) {
|
|
617
610
|
if (!isGitWorkingTree(cwd)) return [];
|
|
618
611
|
const changed = gitHasHead(cwd)
|
|
619
|
-
? changedPathsFromNameStatus(gitOutput(cwd, ["diff", "--name-status", "-z", "--find-renames", "--diff-filter=ACMRD", "HEAD", "--"]))
|
|
612
|
+
? changedPathsFromNameStatus(gitOutput(cwd, ["diff", "--name-status", "-z", "--find-renames", "--diff-filter=ACMRD", "HEAD", "--"]), observeRename)
|
|
620
613
|
: gitOutput(cwd, ["ls-files", "-z"]).split("\0").filter(Boolean);
|
|
621
614
|
const untracked = gitOutput(cwd, ["ls-files", "--others", "--exclude-standard", "-z"]).split("\0").filter(Boolean);
|
|
622
615
|
return [...new Set([...changed, ...untracked].map(normalizedGitPath).filter((item) => item && !item.startsWith(".pi/piagent-state/")))].sort();
|
|
@@ -694,9 +687,7 @@ function nonGitWorkspaceFiles(cwd, gitRootPrefixes) {
|
|
|
694
687
|
return files.sort();
|
|
695
688
|
}
|
|
696
689
|
|
|
697
|
-
function nonGitWorkspaceFileDigest(cwd, file) {
|
|
698
|
-
return streamedWorkingTreeFileDigest(cwd, file, false);
|
|
699
|
-
}
|
|
690
|
+
function nonGitWorkspaceFileDigest(cwd, file, protectedProjectPath = null) { return streamedWorkingTreeFileDigest(cwd, file, false, protectedProjectPath); }
|
|
700
691
|
|
|
701
692
|
function pathWithinNonGitWorkspaceEvidenceRoot(candidate, gitRootPrefixes) {
|
|
702
693
|
const topSegment = candidate.split("/")[0];
|
|
@@ -777,7 +768,7 @@ export function repositoryFileManifest(cwd, maxFiles = 10_000) {
|
|
|
777
768
|
return [...new Set(files)].sort();
|
|
778
769
|
}
|
|
779
770
|
|
|
780
|
-
function changedPathsFromNameStatus(output) {
|
|
771
|
+
function changedPathsFromNameStatus(output, observeRename) {
|
|
781
772
|
const fields = String(output ?? "").split("\0").filter(Boolean);
|
|
782
773
|
const files = [];
|
|
783
774
|
for (let index = 0; index < fields.length;) {
|
|
@@ -787,7 +778,7 @@ function changedPathsFromNameStatus(output) {
|
|
|
787
778
|
files.push(source);
|
|
788
779
|
if (/^[RC]/.test(status)) {
|
|
789
780
|
const destination = fields[index++];
|
|
790
|
-
if (destination) files.push(destination);
|
|
781
|
+
if (destination) { files.push(destination); observeRename?.(source, destination); }
|
|
791
782
|
}
|
|
792
783
|
}
|
|
793
784
|
return files;
|
|
@@ -804,8 +795,12 @@ export function workingTreeSnapshotHasUnavailableEvidence(snapshot) {
|
|
|
804
795
|
return Object.values(snapshot ?? {}).some((digest) => isUnavailableWorkingTreeDigest(digest));
|
|
805
796
|
}
|
|
806
797
|
|
|
807
|
-
function streamedWorkingTreeFileDigest(root, file, hasHead) {
|
|
798
|
+
function streamedWorkingTreeFileDigest(root, file, hasHead, protectedProjectPath = null) {
|
|
808
799
|
const absolute = path.resolve(root, file);
|
|
800
|
+
if (protectedProjectPath) {
|
|
801
|
+
try { return unavailableWorkingTreeDigest(protectedProjectPath, "protected-path", fs.lstatSync(absolute)); }
|
|
802
|
+
catch { return unavailableWorkingTreeDigest(protectedProjectPath, "protected-path"); }
|
|
803
|
+
}
|
|
809
804
|
const relative = path.relative(root, absolute);
|
|
810
805
|
if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
811
806
|
return unavailableWorkingTreeDigest(file, "outside-root");
|
|
@@ -866,22 +861,24 @@ function streamedWorkingTreeFileDigest(root, file, hasHead) {
|
|
|
866
861
|
}
|
|
867
862
|
}
|
|
868
863
|
|
|
869
|
-
export function workingTreeSnapshot(cwd) {
|
|
864
|
+
export function workingTreeSnapshot(cwd, options = {}) {
|
|
870
865
|
const snapshot = {};
|
|
866
|
+
const isProtectedProjectPath = typeof options.isProtectedProjectPath === "function" ? options.isProtectedProjectPath : () => false;
|
|
871
867
|
const roots = gitEvidenceRoots(cwd);
|
|
872
868
|
for (const root of roots) {
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
|
|
869
|
+
const protectedAliases = new Set();
|
|
870
|
+
const observeRename = (source, destination) => {
|
|
871
|
+
const paths = [source, destination];
|
|
872
|
+
if (paths.some((file) => file === ".pi/piagent-state" || file.startsWith(".pi/piagent-state/") || isProtectedProjectPath(prefixedGitPath(root.prefix, file))))
|
|
873
|
+
paths.forEach((file) => protectedAliases.add(file));
|
|
874
|
+
};
|
|
875
|
+
const hasHead = gitHasHead(root.cwd); for (const file of workingTreeFilesForGitRoot(root.cwd, observeRename)) {
|
|
876
|
+
const projectPath = prefixedGitPath(root.prefix, file);
|
|
877
|
+
snapshot[projectPath] = streamedWorkingTreeFileDigest(root.cwd, file, hasHead, isProtectedProjectPath(projectPath) || protectedAliases.has(file) ? projectPath : null);
|
|
877
878
|
}
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
snapshot[file] = nonGitWorkspaceFileDigest(cwd, file);
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
return snapshot;
|
|
879
|
+
} if (!isGitWorkingTree(cwd) && roots.length > 0) for (const file of nonGitWorkspaceFiles(cwd, roots.map((root) => root.prefix))) {
|
|
880
|
+
snapshot[file] = nonGitWorkspaceFileDigest(cwd, file, isProtectedProjectPath(file) ? file : null);
|
|
881
|
+
} return snapshot;
|
|
885
882
|
}
|
|
886
883
|
|
|
887
884
|
export function taskStateMigrationStatus(cwd) {
|
|
@@ -68,7 +68,7 @@ export const CATALOG_SERVERS = {
|
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
70
|
figma: {
|
|
71
|
-
description: "Figma remote MCP server.
|
|
71
|
+
description: "Figma remote MCP server. OAuth works only for clients approved in the Figma MCP Catalog.",
|
|
72
72
|
config: {
|
|
73
73
|
url: "https://mcp.figma.com/mcp",
|
|
74
74
|
auth: "oauth",
|
|
@@ -99,7 +99,7 @@ export const CATALOG_PREREQUISITES = {
|
|
|
99
99
|
"export GITHUB_PERSONAL_ACCESS_TOKEN with the scopes you intend to use"
|
|
100
100
|
],
|
|
101
101
|
playwright: ["browsers installed on first use (npx playwright install)"],
|
|
102
|
-
figma: ["
|
|
102
|
+
figma: ["Piagent approved in the Figma MCP Catalog before remote OAuth can register"],
|
|
103
103
|
"figma-desktop": ["Figma desktop running with Dev Mode MCP enabled"]
|
|
104
104
|
};
|
|
105
105
|
|
|
@@ -119,12 +119,12 @@ export const CATALOG_PRESETS = {
|
|
|
119
119
|
docs: ["context7"],
|
|
120
120
|
browser: ["chrome-devtools", "playwright"],
|
|
121
121
|
github: ["github"],
|
|
122
|
-
design: ["figma"],
|
|
122
|
+
design: ["figma-desktop"],
|
|
123
123
|
"design-local": ["figma-desktop"],
|
|
124
124
|
web: ["context7", "chrome-devtools", "playwright"],
|
|
125
125
|
core: ["context7", "chrome-devtools", "github"],
|
|
126
|
-
popular: ["context7", "chrome-devtools", "playwright", "github", "figma"],
|
|
127
|
-
all: ["context7", "chrome-devtools", "playwright", "github", "figma", "figma
|
|
126
|
+
popular: ["context7", "chrome-devtools", "playwright", "github", "figma-desktop"],
|
|
127
|
+
all: ["context7", "chrome-devtools", "playwright", "github", "figma-desktop", "figma"]
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
/** @param {string} name @returns {boolean} */
|
|
@@ -49,7 +49,7 @@ type SessionStartHookDependencies = {
|
|
|
49
49
|
inspectResume: (cwd: string, task: TaskContract, sessionId: string) => ResumeState;
|
|
50
50
|
telemetry: (ctx: ExtensionContext, payload: Record<string, unknown>) => void;
|
|
51
51
|
syncTrajectory?: (ctx: ExtensionContext, task: TaskContract) => TrajectorySyncResult;
|
|
52
|
-
afterStart?: (ctx: ExtensionContext) => void
|
|
52
|
+
afterStart?: (ctx: ExtensionContext) => void | Promise<void>;
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
export function registerSessionStartHook(pi: ExtensionAPI, dependencies: SessionStartHookDependencies): void {
|
|
@@ -227,6 +227,6 @@ export function registerSessionStartHook(pi: ExtensionAPI, dependencies: Session
|
|
|
227
227
|
taskRecovery,
|
|
228
228
|
executionBackend
|
|
229
229
|
});
|
|
230
|
-
dependencies.afterStart?.(ctx);
|
|
230
|
+
await dependencies.afterStart?.(ctx);
|
|
231
231
|
});
|
|
232
232
|
}
|
|
@@ -17,6 +17,7 @@ type ToolCallHookDependencies = {
|
|
|
17
17
|
beforeAuthorize?: (event: ToolCallEvent, ctx: ExtensionContext) => Promise<ToolCallDecision> | ToolCallDecision;
|
|
18
18
|
authorize: (event: ToolCallEvent, ctx: ExtensionContext) => Promise<ToolCallDecision> | ToolCallDecision;
|
|
19
19
|
reviewBudgetDecision?: (event: ToolCallEvent, ctx: ExtensionContext) => ToolCallDecision;
|
|
20
|
+
beforeStart?: (event: ToolCallEvent, ctx: ExtensionContext) => ToolCallDecision;
|
|
20
21
|
afterDecision?: (
|
|
21
22
|
event: ToolCallEvent,
|
|
22
23
|
ctx: ExtensionContext,
|
|
@@ -72,7 +73,9 @@ export function registerToolCallHook(pi: ExtensionAPI, dependencies: ToolCallHoo
|
|
|
72
73
|
const decision = authorizationDecision && typeof authorizationDecision === "object" && authorizationDecision.block
|
|
73
74
|
? authorizationDecision
|
|
74
75
|
: dependencies.reviewBudgetDecision?.(resolvedEvent, ctx);
|
|
75
|
-
const
|
|
76
|
+
const startDecision = !(decision && typeof decision === "object" && decision.block) ? dependencies.beforeStart?.(resolvedEvent, ctx) : undefined;
|
|
77
|
+
const finalDecision = startDecision && typeof startDecision === "object" && startDecision.block ? startDecision : decision;
|
|
78
|
+
const blockedDecision = finalDecision && typeof finalDecision === "object" ? finalDecision : undefined;
|
|
76
79
|
record(ctx, {
|
|
77
80
|
activityId: `decision:${toolCallId}`,
|
|
78
81
|
event: "tool_decision",
|
|
@@ -88,6 +91,6 @@ export function registerToolCallHook(pi: ExtensionAPI, dependencies: ToolCallHoo
|
|
|
88
91
|
observeTrajectorySync(ctx, dependencies.afterAuthorized?.(resolvedEvent, ctx, { toolCallId }), dependencies.telemetry);
|
|
89
92
|
}
|
|
90
93
|
dependencies.afterDecision?.(resolvedEvent, ctx, { toolCallId, allowed: !blockedDecision?.block, reason: blockedDecision?.reason });
|
|
91
|
-
return
|
|
94
|
+
return finalDecision;
|
|
92
95
|
});
|
|
93
96
|
}
|
|
@@ -11,6 +11,7 @@ import { redactForStorage, redactSensitiveText } from "../../extensions/redactio
|
|
|
11
11
|
import { appendObservedBashResult, hashEvidenceCommand, observedBashResultFromToolResultEvent } from "../../extensions/runtime-evidence.js";
|
|
12
12
|
import { workingTreeSnapshot, workingTreeSnapshotHasUnavailableEvidence } from "../../extensions/task-state.js";
|
|
13
13
|
import { workingTreeObservation } from "../../extensions/working-tree-digest.js";
|
|
14
|
+
import { recordMutationResult } from "../inspection/mutation-provenance-recorder.ts";
|
|
14
15
|
import { boundedGitDiffReview } from "../quality/performance-assurance.ts";
|
|
15
16
|
import { currentFileContentDigests } from "../quality/model-mutation-proof.ts";
|
|
16
17
|
import { boundedPerformanceReviewResultText } from "../quality/performance-review-evidence.ts";
|
|
@@ -18,6 +19,7 @@ import { attachToolResultCompactionDetails, compactToolResultDetails, compactToo
|
|
|
18
19
|
import { RuntimeSessionState, type ObservedTaskContext } from "../session/runtime-state.ts";
|
|
19
20
|
import { observeTrajectorySync } from "../trajectory/trajectory-observability.ts";
|
|
20
21
|
import type { TrajectorySyncResult } from "../trajectory/trajectory-runtime.ts";
|
|
22
|
+
import { patchLineStats } from "./tool-result-metadata.ts";
|
|
21
23
|
|
|
22
24
|
type ToolResultEvent = { toolCallId?: string; toolName: string; input?: unknown; content?: unknown; details?: unknown; isError?: boolean; usage?: unknown };
|
|
23
25
|
type ObservedBashResult = NonNullable<ReturnType<typeof observedBashResultFromToolResultEvent>>;
|
|
@@ -50,7 +52,8 @@ type ToolResultHookDependencies = {
|
|
|
50
52
|
ctx: ExtensionContext,
|
|
51
53
|
observed: ObservedVerificationResult,
|
|
52
54
|
pendingContext: ObservedTaskContext[],
|
|
53
|
-
maxManifestFiles: number, shellSnapshotBefore?: Record<string, string>, eventTree?: WorkingTreeObservation
|
|
55
|
+
maxManifestFiles: number, shellSnapshotBefore?: Record<string, string>, eventTree?: WorkingTreeObservation,
|
|
56
|
+
readProtectedPaths?: string[]
|
|
54
57
|
) => unknown;
|
|
55
58
|
extractLikelyPath: (cwd: string, input: Record<string, unknown>) => string | undefined;
|
|
56
59
|
mutationTargets: (cwd: string, toolName: string, input: Record<string, unknown>) => string[];
|
|
@@ -124,20 +127,6 @@ function boundedToolResultText(content: unknown): string {
|
|
|
124
127
|
return text.slice(-20_000);
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
function patchLineStats(details: unknown): { additions?: number; deletions?: number } {
|
|
128
|
-
if (!details || typeof details !== "object") return {};
|
|
129
|
-
const patch = (details as Record<string, unknown>).patch;
|
|
130
|
-
if (typeof patch !== "string" || !patch.trim()) return {};
|
|
131
|
-
let additions = 0;
|
|
132
|
-
let deletions = 0;
|
|
133
|
-
for (const line of patch.split(/\r?\n/)) {
|
|
134
|
-
if (line.startsWith("+++ ") || line.startsWith("--- ")) continue;
|
|
135
|
-
if (line.startsWith("+")) additions += 1;
|
|
136
|
-
else if (line.startsWith("-")) deletions += 1;
|
|
137
|
-
}
|
|
138
|
-
return { additions, deletions };
|
|
139
|
-
}
|
|
140
|
-
|
|
141
130
|
function countChangedStringLeaves(before: unknown, after: unknown): number {
|
|
142
131
|
if (typeof before === "string" && typeof after === "string") return before === after ? 0 : 1;
|
|
143
132
|
if (Array.isArray(before) && Array.isArray(after)) {
|
|
@@ -270,9 +259,9 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
270
259
|
dependencies.recordObservedTaskVerification(
|
|
271
260
|
pi,
|
|
272
261
|
ctx,
|
|
273
|
-
{ ...observed, outputText: boundedToolResultText(event.content) },
|
|
262
|
+
{ ...observed, toolCallId: observed.toolCallId ?? resultToolCallId, outputText: boundedToolResultText(event.content) },
|
|
274
263
|
pendingContext,
|
|
275
|
-
dependencies.maxManifestFiles, shellSnapshotBefore, eventTree
|
|
264
|
+
dependencies.maxManifestFiles, shellSnapshotBefore, eventTree, readProtectedPaths
|
|
276
265
|
);
|
|
277
266
|
}
|
|
278
267
|
|
|
@@ -287,20 +276,32 @@ export function registerToolResultHook(pi: ExtensionAPI, dependencies: ToolResul
|
|
|
287
276
|
const currentSnapshot = taskIdentity ? eventTree?.snapshot : undefined;
|
|
288
277
|
const currentTreeDigest = taskIdentity ? eventTree?.digest : undefined;
|
|
289
278
|
const directMutationResult = modelMutationIdentity && currentSnapshot
|
|
290
|
-
? dependencies.state.
|
|
279
|
+
? dependencies.state.completeAuthorizedModelMutationEvidence(
|
|
291
280
|
modelMutationIdentity,
|
|
292
281
|
resultToolCallId,
|
|
293
282
|
successfulToolResult(event),
|
|
294
283
|
currentSnapshot,
|
|
295
284
|
currentFileContentDigests(ctx.cwd, directMutationTargets)
|
|
296
285
|
)
|
|
297
|
-
: { changedPaths: [] as string[], recordedDigests: {} as Record<string, string
|
|
286
|
+
: { changedPaths: [] as string[], recordedDigests: {} as Record<string, string>, beforeSnapshot: null,
|
|
287
|
+
targetPaths: [] as string[], recordedContentDigests: {} as Record<string, string>, proofModes: {} as Record<string, "full-content" | "exact-replacement"> };
|
|
298
288
|
const shellChangedPaths = taskIdentity && currentSnapshot && shellSnapshotBefore
|
|
299
289
|
? changedSnapshotFiles(shellSnapshotBefore, currentSnapshot)
|
|
300
290
|
: [];
|
|
301
291
|
if (modelMutationIdentity && shellChangedPaths.length > 0) {
|
|
302
292
|
dependencies.state.invalidateSuccessfulModelMutationPaths(modelMutationIdentity, shellChangedPaths);
|
|
303
293
|
}
|
|
294
|
+
if (modelMutationIdentity && currentSnapshot) {
|
|
295
|
+
try {
|
|
296
|
+
recordMutationResult({
|
|
297
|
+
projectRoot: ctx.cwd, identity: modelMutationIdentity, toolCallId: resultToolCallId, toolName: event.toolName,
|
|
298
|
+
recordedAt: dependencies.now(), successful: successfulToolResult(event), currentSnapshot,
|
|
299
|
+
completion: directMutationResult, shellSnapshotBefore, shellChangedPaths, protectedPaths: readProtectedPaths
|
|
300
|
+
});
|
|
301
|
+
} catch (error) {
|
|
302
|
+
ctx.ui.notify(`Piagent could not persist mutation provenance: ${error instanceof Error ? error.message : String(error)}`, "warn");
|
|
303
|
+
}
|
|
304
|
+
}
|
|
304
305
|
const outputText = boundedToolResultText(event.content);
|
|
305
306
|
const performanceReviewOutputText = boundedPerformanceReviewResultText(event.content);
|
|
306
307
|
const observedExitCode = observed?.exitCode
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function patchLineStats(details: unknown): { additions?: number; deletions?: number } {
|
|
2
|
+
if (!details || typeof details !== "object") return {};
|
|
3
|
+
const patch = (details as Record<string, unknown>).patch;
|
|
4
|
+
if (typeof patch !== "string" || !patch.trim()) return {};
|
|
5
|
+
let additions = 0;
|
|
6
|
+
let deletions = 0;
|
|
7
|
+
for (const line of patch.split(/\r?\n/)) {
|
|
8
|
+
if (line.startsWith("+++ ") || line.startsWith("--- ")) continue;
|
|
9
|
+
if (line.startsWith("+")) additions += 1;
|
|
10
|
+
else if (line.startsWith("-")) deletions += 1;
|
|
11
|
+
}
|
|
12
|
+
return { additions, deletions };
|
|
13
|
+
}
|
|
@@ -32,7 +32,7 @@ function escapeRegExp(input: string): string {
|
|
|
32
32
|
return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function
|
|
35
|
+
export function supportedChatImageMimeType(bytes: Buffer): string | undefined {
|
|
36
36
|
if (
|
|
37
37
|
bytes.length >= 24
|
|
38
38
|
&& bytes.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))
|
|
@@ -236,7 +236,7 @@ export function readChatImage(
|
|
|
236
236
|
offset += read;
|
|
237
237
|
}
|
|
238
238
|
if (offset !== bytes.length) return { status: "error", reason: "file changed while it was being read" };
|
|
239
|
-
const mimeType =
|
|
239
|
+
const mimeType = supportedChatImageMimeType(bytes);
|
|
240
240
|
if (!mimeType) return { status: "error", reason: "file bytes are not a supported image" };
|
|
241
241
|
return { status: "ok", absolutePath: canonicalPath, bytes, mimeType };
|
|
242
242
|
} finally {
|