@piagent/platform 1.37.1 → 1.38.2
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 +60 -0
- package/README.md +5 -3
- package/README.vi.md +2 -2
- package/catalog/capabilities.json +2 -2
- package/docs/command-reference-vietnamese.md +4 -3
- package/docs/distribution-standard.md +6 -6
- package/docs/en/maintainer-guide.md +8 -0
- package/docs/en/workspace-editor.md +222 -0
- package/docs/languages.json +13 -4
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +3 -0
- package/docs/release-install-policy.md +2 -2
- package/docs/research/ide-m0-spike.md +101 -0
- package/docs/team-onboarding.md +2 -2
- package/docs/vi/maintainer-guide.md +8 -0
- package/docs/vi/workspace-editor.md +217 -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/content/en/index.html +4 -4
- package/docs-site/content/en/whats-new.html +23 -0
- package/docs-site/content/index.html +4 -4
- package/docs-site/content/whats-new.html +23 -0
- package/docs-site/context-engine.html +1 -1
- package/docs-site/ecosystem.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/ecosystem.html +1 -1
- package/docs-site/en/index.html +5 -5
- 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 +24 -1
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +5 -5
- 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 +24 -1
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/fixtures/piagent-webui/checkpoint-v1.invalid.json +21 -0
- package/evals/fixtures/piagent-webui/checkpoint-v1.valid.json +20 -0
- package/evals/fixtures/piagent-webui/terminal-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/terminal-v1.valid.json +16 -0
- package/evals/fixtures/piagent-webui/workspace-file-v1.invalid.json +24 -0
- package/evals/fixtures/piagent-webui/workspace-file-v1.valid.json +23 -0
- package/evals/fixtures/piagent-webui/workspace-paths-v1.invalid.json +24 -0
- package/evals/fixtures/piagent-webui/workspace-paths-v1.valid.json +24 -0
- package/evals/fixtures/piagent-webui/workspace-search-v1.invalid.json +26 -0
- package/evals/fixtures/piagent-webui/workspace-search-v1.valid.json +26 -0
- package/evals/fixtures/piagent-webui/workspace-tree-v1.invalid.json +28 -0
- package/evals/fixtures/piagent-webui/workspace-tree-v1.valid.json +27 -0
- package/evals/fixtures/piagent-webui/workspace-write-v1.invalid.json +19 -0
- package/evals/fixtures/piagent-webui/workspace-write-v1.valid.json +19 -0
- package/evals/long-task-soak-v1/README.md +17 -0
- package/evals/long-task-soak-v1/lib/session-loop.mjs +19 -3
- package/evals/long-task-soak-v1/scenarios/ide-editor-terminal.json +19 -0
- package/evals/long-task-soak-v1/surfaces/webui-gateway.mjs +17 -2
- package/evals/long-task-soak-v1/surfaces/workspace-actions.mjs +95 -0
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +7 -3
- package/packages/piagent-core/extensions/fallback-base-policy.ts +1 -1
- package/packages/piagent-core/extensions/guard-types.ts +3 -1
- package/packages/piagent-core/extensions/path-suggestions.ts +137 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +85 -7
- package/packages/piagent-core/extensions/workspace-inspection.ts +145 -0
- package/packages/piagent-core/extensions/workspace-policy.d.ts +7 -0
- package/packages/piagent-core/extensions/workspace-policy.js +3 -0
- package/packages/piagent-core/extensions/workspace-search-index.mjs +8 -0
- package/packages/piagent-core/extensions/workspace-search.ts +74 -0
- package/packages/piagent-core/extensions/workspace-write.ts +116 -0
- package/packages/piagent-core/managed/background-process.mjs +9 -4
- package/packages/piagent-core/managed/boundary-package-proxy.mjs +21 -0
- package/packages/piagent-core/managed/boundary-queue.mjs +24 -0
- package/packages/piagent-core/managed/checkpoint-git.mjs +16 -0
- package/packages/piagent-core/managed/checkpoints.mjs +17 -64
- package/packages/piagent-core/managed/command-tools.mjs +6 -6
- package/packages/piagent-core/managed/continuation.mjs +3 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/backend-api/profile.json +88 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/be-readonly-fe/profile.json +130 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/data/profile.json +88 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/devops/profile.json +90 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/docs/profile.json +74 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/fullstack/profile.json +92 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/generic/profile.json +95 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/mobile/profile.json +86 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/node-typescript/profile.json +74 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/python/profile.json +75 -0
- package/packages/piagent-core/managed/dist/workspace/assets/adapters/web-frontend/profile.json +99 -0
- package/packages/piagent-core/managed/dist/workspace/assets/local-state-path-BrmghxEZ.js +284 -0
- package/packages/piagent-core/managed/dist/workspace/assets/packages/piagent-core/policies/base-policy.json +351 -0
- package/packages/piagent-core/managed/dist/workspace/manifest.json +65 -0
- package/packages/piagent-core/managed/dist/workspace/worker.mjs +7374 -0
- package/packages/piagent-core/managed/dist/workspace/workspace-search-index.mjs +1007 -0
- package/packages/piagent-core/managed/patch-snapshot.mjs +6 -8
- package/packages/piagent-core/managed/permission.mjs +29 -6
- package/packages/piagent-core/managed/repository-operation.mjs +2 -1
- package/packages/piagent-core/managed/review-snapshot-request.mjs +21 -0
- package/packages/piagent-core/managed/sandbox-diagnostic.mjs +2 -0
- package/packages/piagent-core/managed/session-mutation-observations.mjs +37 -0
- package/packages/piagent-core/managed/session.mjs +2 -2
- package/packages/piagent-core/managed/source-revert-requests.mjs +21 -0
- package/packages/piagent-core/managed/terminal-boundary.mjs +88 -0
- package/packages/piagent-core/managed/terminal-channel.mjs +66 -0
- package/packages/piagent-core/managed/terminal-runtime.mjs +64 -0
- package/packages/piagent-core/managed/terminal-seatbelt.mjs +48 -0
- package/packages/piagent-core/managed/terminal-worker.mjs +70 -0
- package/packages/piagent-core/managed/tool-boundary.mjs +45 -35
- package/packages/piagent-core/managed/tool-freshness.mjs +47 -0
- package/packages/piagent-core/managed/tool-worker.mjs +18 -3
- package/packages/piagent-core/managed/workspace-worker-pins.mjs +26 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +2 -1
- package/packages/piagent-core/runtime/checkpoints/checkpoint-authority.ts +43 -0
- package/packages/piagent-core/runtime/checkpoints/checkpoint-refs.mjs +51 -0
- package/packages/piagent-core/runtime/checkpoints/checkpoint-store.mjs +179 -0
- package/packages/piagent-core/runtime/checkpoints/checkpoint-worker.ts +28 -0
- package/packages/piagent-core/runtime/checkpoints/git-adapter.mjs +36 -0
- package/packages/piagent-core/runtime/checkpoints/persistent-history.mjs +96 -0
- package/packages/piagent-core/runtime/checkpoints/personal-git.ts +272 -0
- package/packages/piagent-core/runtime/checkpoints/personal-runtime.ts +158 -0
- package/packages/piagent-core/runtime/checkpoints/prune.mjs +29 -0
- package/packages/piagent-core/runtime/checkpoints/restore-preview.mjs +46 -0
- package/packages/piagent-core/runtime/checkpoints/turn-checkpoints.mjs +167 -0
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +19 -16
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +3 -0
- package/packages/piagent-core/runtime/hooks/tool-call-hook.ts +2 -2
- package/packages/piagent-core/runtime/inspection/diff-projection.ts +2 -1
- package/packages/piagent-core/runtime/inspection/managed-source-revert.ts +90 -0
- package/packages/piagent-core/runtime/inspection/review-state-projection.ts +19 -8
- package/packages/piagent-core/runtime/inspection/session-diff-projection.ts +47 -0
- package/packages/piagent-core/runtime/inspection/session-hunk-provenance.ts +56 -0
- package/packages/piagent-core/runtime/inspection/session-mutation-observer.ts +125 -0
- package/packages/piagent-core/runtime/inspection/source-action-contract.ts +123 -0
- package/packages/piagent-core/runtime/inspection/source-change-projection.ts +3 -9
- package/packages/piagent-core/runtime/inspection/source-mutation-projection.ts +3 -41
- package/packages/piagent-core/runtime/inspection/source-mutation-store.ts +6 -36
- package/packages/piagent-core/runtime/inspection/source-revert-projection.ts +36 -30
- package/packages/piagent-core/runtime/inspection/text-diff.ts +5 -4
- package/packages/piagent-core/runtime/inspection/workspace-worker.ts +79 -0
- package/packages/piagent-core/runtime/policy/source-index-transaction.ts +3 -10
- package/packages/piagent-core/runtime/policy/source-mutation-guard-binding.ts +27 -2
- package/packages/piagent-core/runtime/policy/source-mutation-guard.ts +42 -12
- package/packages/piagent-core/runtime/policy/source-worktree-transaction.ts +10 -5
- package/packages/piagent-core/runtime/policy/workspace-write-authority.ts +51 -0
- package/packages/piagent-core/runtime/process-owner.mjs +20 -0
- package/packages/piagent-core/runtime/process-tree-watch.mjs +46 -0
- package/packages/piagent-core/runtime/quality/model-mutation-proof.ts +8 -5
- package/packages/piagent-core/runtime/registration/permission-commands.ts +10 -2
- package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +1 -1
- package/packages/piagent-core/runtime/session/workspace-member-edits.d.mts +7 -0
- package/packages/piagent-core/runtime/session/workspace-member-edits.mjs +41 -0
- package/packages/piagent-core/security/workspace-file-reader.ts +2 -1
- package/packages/piagent-webui/dist/client/assets/Alert-DGWhP2MM.js +1 -0
- package/packages/piagent-webui/dist/client/assets/Button-QItfZUNj.js +1 -0
- package/packages/piagent-webui/dist/client/assets/DialogTitle-CyaFPaiW.js +3 -0
- package/packages/piagent-webui/dist/client/assets/MarkdownMessage-DtEwqme0.js +29 -0
- package/packages/piagent-webui/dist/client/assets/TerminalPanel-C3vkzjQ6.js +2 -0
- package/packages/piagent-webui/dist/client/assets/TerminalView-TNjhBGfa.js +9 -0
- package/packages/piagent-webui/dist/client/assets/TerminalView-kHJ-D0s7.css +1 -0
- package/packages/piagent-webui/dist/client/assets/Typography-B9om7Evq.js +1 -0
- package/packages/piagent-webui/dist/client/assets/WorkspaceCodeEditor-BAhYwQNM.js +2 -0
- package/packages/piagent-webui/dist/client/assets/WorkspaceExplorer-Coh_GWCO.js +5 -0
- package/packages/piagent-webui/dist/client/assets/WorkspaceQuickOpen-Bn4miZq4.js +1 -0
- package/packages/piagent-webui/dist/client/assets/bootstrap-CedkNtRG.js +1 -0
- package/packages/piagent-webui/dist/client/assets/dist-B5MCHZv5.js +9 -0
- package/packages/piagent-webui/dist/client/assets/dist-BM_LL1o9.js +1 -0
- package/packages/piagent-webui/dist/client/assets/dist-C2TMLUTo.js +23 -0
- package/packages/piagent-webui/dist/client/assets/dist-CqL3udNu.js +1 -0
- package/packages/piagent-webui/dist/client/assets/dist-D4df0jLQ.js +1 -0
- package/packages/piagent-webui/dist/client/assets/dist-D6i5SpQu.js +10 -0
- package/packages/piagent-webui/dist/client/assets/dist-DfjUS8LS.js +6 -0
- package/packages/piagent-webui/dist/client/assets/dist-IRUjhd1P.js +1 -0
- package/packages/piagent-webui/dist/client/assets/dist-sNhoSseW.js +1 -0
- package/packages/piagent-webui/dist/client/assets/index-Brs_HjIo.css +1 -0
- package/packages/piagent-webui/dist/client/assets/index-a-perpOX.js +102 -0
- package/packages/piagent-webui/dist/client/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/packages/piagent-webui/dist/client/assets/tab-close-nAxrK7Lq.js +1 -0
- package/packages/piagent-webui/dist/client/assets/ui-preferences-BO3AV7QE.js +10 -0
- package/packages/piagent-webui/dist/client/assets/useId-DEefKUAT.js +124 -0
- package/packages/piagent-webui/dist/client/assets/workspace-api-O7auv_td.js +1 -0
- package/packages/piagent-webui/dist/client/index.html +10 -2
- package/packages/piagent-webui/extension/same-session-bridge.ts +2 -3
- package/packages/piagent-webui/extension/source-revert-controller.ts +14 -208
- package/packages/piagent-webui/gateway/checkpoint-sessions.ts +99 -0
- package/packages/piagent-webui/gateway/checkpoint-store.ts +6 -0
- package/packages/piagent-webui/gateway/company-relay.ts +5 -0
- package/packages/piagent-webui/gateway/company-terminal-cache.ts +54 -0
- package/packages/piagent-webui/gateway/company-terminal-channel.ts +117 -0
- package/packages/piagent-webui/gateway/company-terminal-relay.ts +75 -0
- package/packages/piagent-webui/gateway/gateway-protocol-service.ts +12 -0
- package/packages/piagent-webui/gateway/gateway-service.ts +48 -4
- package/packages/piagent-webui/gateway/managed-gateway.mjs +30 -3
- package/packages/piagent-webui/gateway/managed-projection.ts +5 -3
- package/packages/piagent-webui/gateway/managed-source-revert-controller.ts +45 -0
- package/packages/piagent-webui/gateway/managed-source-revert-sessions.ts +74 -0
- package/packages/piagent-webui/gateway/managed-terminal-binding.ts +30 -0
- package/packages/piagent-webui/gateway/managed-terminal-processes.ts +74 -0
- package/packages/piagent-webui/gateway/managed-terminal-sessions.ts +9 -0
- package/packages/piagent-webui/gateway/path-suggestions.ts +1 -124
- package/packages/piagent-webui/gateway/personal-terminal-binding.ts +36 -0
- package/packages/piagent-webui/gateway/personal-terminal-diagnostic.mjs +16 -0
- package/packages/piagent-webui/gateway/personal-terminal-processes.ts +119 -0
- package/packages/piagent-webui/gateway/personal-terminal-sessions.ts +22 -0
- package/packages/piagent-webui/gateway/runtime-session-controls.ts +2 -1
- package/packages/piagent-webui/gateway/session-catalog.ts +3 -2
- package/packages/piagent-webui/gateway/session-command-controller.ts +4 -4
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +29 -5
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +8 -8
- package/packages/piagent-webui/gateway/signed-session-journal.ts +77 -0
- package/packages/piagent-webui/gateway/source-revert-sessions.ts +74 -0
- package/packages/piagent-webui/gateway/source-revert-store.ts +47 -0
- package/packages/piagent-webui/gateway/terminal-command-ledger.ts +33 -0
- package/packages/piagent-webui/gateway/terminal-mentions.ts +29 -0
- package/packages/piagent-webui/gateway/terminal-output-buffer.ts +58 -0
- package/packages/piagent-webui/gateway/terminal-router.ts +12 -0
- package/packages/piagent-webui/gateway/terminal-sessions.ts +247 -0
- package/packages/piagent-webui/gateway/workspace-write-sessions.ts +69 -0
- package/packages/piagent-webui/gateway/workspace-write-store.ts +6 -0
- package/packages/piagent-webui/package.json +15 -2
- package/packages/piagent-webui/server/checkpoint-command.ts +30 -0
- package/packages/piagent-webui/server/checkpoint-controller.ts +87 -0
- package/packages/piagent-webui/server/checkpoint-routes.ts +38 -0
- package/packages/piagent-webui/server/control-command-digest.ts +5 -0
- package/packages/piagent-webui/server/core-inspection-provider.ts +16 -22
- package/packages/piagent-webui/server/gateway-websocket.ts +36 -8
- package/packages/piagent-webui/server/loopback-server.ts +9 -0
- package/packages/piagent-webui/server/read-model-provider.ts +3 -0
- package/packages/piagent-webui/server/read-only-router.ts +3 -0
- package/packages/piagent-webui/server/session-auth.ts +4 -1
- package/packages/piagent-webui/server/session-source-revert-routes.ts +54 -0
- package/packages/piagent-webui/server/source-projection-binding.ts +18 -0
- package/packages/piagent-webui/server/source-revert-controller.ts +225 -0
- package/packages/piagent-webui/server/terminal-command.ts +35 -0
- package/packages/piagent-webui/server/terminal-websocket-stream.ts +56 -0
- package/packages/piagent-webui/server/transcript-projection.ts +2 -1
- package/packages/piagent-webui/server/workspace-read-routes.ts +32 -0
- package/packages/piagent-webui/server/workspace-reader.ts +23 -0
- package/packages/piagent-webui/server/workspace-write-controller.ts +118 -0
- package/packages/piagent-webui/server/workspace-write-routes.ts +38 -0
- package/packages/piagent-webui/shared/terminal-message.ts +23 -0
- package/schemas/piagent-webui/catalog-v1.json +63 -0
- package/schemas/piagent-webui/checkpoint-v1.schema.json +466 -0
- package/schemas/piagent-webui/control-command-v1.schema.json +68 -3
- package/schemas/piagent-webui/diff-v1.schema.json +4 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +123 -3
- package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
- package/schemas/piagent-webui/session-command-v1.schema.json +1 -1
- package/schemas/piagent-webui/snapshot-v1.schema.json +2 -1
- package/schemas/piagent-webui/source-revert-v1.schema.json +22 -3
- package/schemas/piagent-webui/terminal-v1.schema.json +495 -0
- package/schemas/piagent-webui/workspace-file-v1.schema.json +79 -0
- package/schemas/piagent-webui/workspace-paths-v1.schema.json +71 -0
- package/schemas/piagent-webui/workspace-search-v1.schema.json +80 -0
- package/schemas/piagent-webui/workspace-tree-v1.schema.json +87 -0
- package/schemas/piagent-webui/workspace-write-v1.schema.json +276 -0
- package/schemas/project-profile.schema.json +8 -3
- package/scripts/company-terminal.mjs +16 -11
- package/scripts/pi-auto.sh +5 -1
- package/scripts/team-doctor.sh +8 -0
- package/packages/piagent-webui/dist/client/assets/MarkdownMessage-CfgyQQbW.js +0 -29
- package/packages/piagent-webui/dist/client/assets/index-HyzWKvDu.js +0 -234
- package/packages/piagent-webui/dist/client/assets/index-_0nGP2n3.css +0 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { inspectWorkspaceFile, type WorkspacePolicy } from "../../extensions/workspace-inspection.ts";
|
|
4
|
+
import { expectedModelMutationProof } from "../quality/model-mutation-proof.ts";
|
|
5
|
+
|
|
6
|
+
type FileImage = { content: string; digest: string };
|
|
7
|
+
export type SessionMutationObservation = {
|
|
8
|
+
path: string; baseline: FileImage; currentDigest: string; toolCallId: string; evidenceRef: string;
|
|
9
|
+
};
|
|
10
|
+
type Pending = { toolName: string; files: Map<string, { before: FileImage; expected: string[] }> };
|
|
11
|
+
type Session = { pending: Map<string, Pending>; files: Map<string, SessionMutationObservation> };
|
|
12
|
+
const MAX_SESSIONS = 64, MAX_FILES = 32, MAX_PENDING = 8, MAX_BYTES = 256 * 1024;
|
|
13
|
+
const hash = (value: string) => createHash("sha256").update(value).digest("hex");
|
|
14
|
+
|
|
15
|
+
// This observation proves the exact tool result, not authorship of every line
|
|
16
|
+
// in the file. A member edit breaks the chain and becomes the next baseline.
|
|
17
|
+
export class SessionMutationObserver {
|
|
18
|
+
readonly #sessions = new Map<string, Session>();
|
|
19
|
+
#key(cwd: string, sessionId: string) { return `${path.resolve(cwd)}\0${sessionId}`; }
|
|
20
|
+
#session(cwd: string, sessionId: string): Session {
|
|
21
|
+
const key = this.#key(cwd, sessionId);
|
|
22
|
+
let state = this.#sessions.get(key);
|
|
23
|
+
if (!state) {
|
|
24
|
+
state = { pending: new Map(), files: new Map() }; this.#sessions.set(key, state);
|
|
25
|
+
while (this.#sessions.size > MAX_SESSIONS) this.#sessions.delete(this.#sessions.keys().next().value!);
|
|
26
|
+
}
|
|
27
|
+
return state;
|
|
28
|
+
}
|
|
29
|
+
#trim(): void {
|
|
30
|
+
let bytes = 0;
|
|
31
|
+
for (const state of [...this.#sessions.values()].reverse()) {
|
|
32
|
+
for (const item of state.files.values()) bytes += Buffer.byteLength(item.baseline.content);
|
|
33
|
+
for (const pending of state.pending.values()) for (const item of pending.files.values()) bytes += Buffer.byteLength(item.before.content);
|
|
34
|
+
}
|
|
35
|
+
while (bytes > 8 * 1024 * 1024 && this.#sessions.size) {
|
|
36
|
+
const key = this.#sessions.keys().next().value!, state = this.#sessions.get(key)!;
|
|
37
|
+
for (const item of state.files.values()) bytes -= Buffer.byteLength(item.baseline.content);
|
|
38
|
+
for (const pending of state.pending.values()) for (const item of pending.files.values()) bytes -= Buffer.byteLength(item.before.content);
|
|
39
|
+
this.#sessions.delete(key);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
#read(cwd: string, file: string, policy: WorkspacePolicy, allowAbsent = false): FileImage | null {
|
|
43
|
+
try {
|
|
44
|
+
const value = inspectWorkspaceFile(cwd, file, policy);
|
|
45
|
+
if (value.protected || value.content === null || !value.digest || Buffer.byteLength(value.content) > MAX_BYTES) return null;
|
|
46
|
+
return { content: value.content, digest: value.digest };
|
|
47
|
+
} catch (error) {
|
|
48
|
+
return allowAbsent && (error as NodeJS.ErrnoException).code === "ENOENT" ? { content: "", digest: "absent" } : null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
begin(cwd: string, sessionId: string, toolCallId: string, toolName: string,
|
|
52
|
+
input: Record<string, unknown>, targets: string[], policy: WorkspacePolicy): void {
|
|
53
|
+
const state = this.#session(cwd, sessionId);
|
|
54
|
+
state.pending.delete(toolCallId);
|
|
55
|
+
if (!["write", "edit", "apply_patch"].includes(toolName) || targets.length > MAX_FILES) return;
|
|
56
|
+
const before = new Map<string, FileImage>();
|
|
57
|
+
for (const target of new Set(targets)) {
|
|
58
|
+
const file = path.relative(cwd, path.resolve(cwd, target)).split(path.sep).join("/");
|
|
59
|
+
const image = this.#read(cwd, file, policy, true);
|
|
60
|
+
// Only validated paths may enter the legacy exact-replacement prover.
|
|
61
|
+
if (image) before.set(file, image);
|
|
62
|
+
}
|
|
63
|
+
const proof = expectedModelMutationProof(cwd, toolName, input, [...before.keys()], (_root, file) => this.#read(cwd, file, policy) ?? undefined);
|
|
64
|
+
const files: Pending["files"] = new Map();
|
|
65
|
+
for (const [file, image] of before) {
|
|
66
|
+
const expected = proof.expectedContentDigests[file];
|
|
67
|
+
if (expected?.length && this.#read(cwd, file, policy, true)?.digest === image.digest
|
|
68
|
+
&& (!proof.preContentDigests[file] || proof.preContentDigests[file] === image.digest)) {
|
|
69
|
+
files.set(file, { before: image, expected });
|
|
70
|
+
} else state.files.delete(file);
|
|
71
|
+
}
|
|
72
|
+
if (files.size) state.pending.set(toolCallId, { toolName, files });
|
|
73
|
+
while (state.pending.size > MAX_PENDING) state.pending.delete(state.pending.keys().next().value!);
|
|
74
|
+
this.#trim();
|
|
75
|
+
}
|
|
76
|
+
finish(cwd: string, sessionId: string, toolCallId: string, toolName: string, successful: boolean, policy: WorkspacePolicy): void {
|
|
77
|
+
const state = this.#sessions.get(this.#key(cwd, sessionId)), pending = state?.pending.get(toolCallId);
|
|
78
|
+
if (!state || !pending) return;
|
|
79
|
+
state.pending.delete(toolCallId);
|
|
80
|
+
for (const [file, candidate] of pending.files) {
|
|
81
|
+
const current = successful && toolName === pending.toolName ? this.#read(cwd, file, policy) : null;
|
|
82
|
+
const previous = state.files.get(file); state.files.delete(file);
|
|
83
|
+
if (!current || current.digest === candidate.before.digest || !candidate.expected.includes(current.digest)) continue;
|
|
84
|
+
const baseline = previous?.currentDigest === candidate.before.digest ? previous.baseline : candidate.before;
|
|
85
|
+
if (baseline.digest === current.digest) continue;
|
|
86
|
+
const evidenceRef = `session-mutation.${hash(JSON.stringify([sessionId, toolCallId, file, baseline.digest, current.digest]))}`;
|
|
87
|
+
state.files.set(file, { path: file, baseline, currentDigest: current.digest, toolCallId, evidenceRef });
|
|
88
|
+
}
|
|
89
|
+
while (state.files.size > MAX_FILES) state.files.delete(state.files.keys().next().value!);
|
|
90
|
+
this.#trim();
|
|
91
|
+
}
|
|
92
|
+
// Called only with the digest of bytes committed by the guarded executor.
|
|
93
|
+
reverted(cwd: string, sessionId: string, file: string, beforeDigest: string, committedDigest: string, policy: WorkspacePolicy): void {
|
|
94
|
+
const state = this.#sessions.get(this.#key(cwd, sessionId)), prior = state?.files.get(file);
|
|
95
|
+
if (!state || !prior) return;
|
|
96
|
+
state.files.delete(file);
|
|
97
|
+
const current = this.#read(cwd, file, policy);
|
|
98
|
+
if (prior.currentDigest !== beforeDigest || current?.digest !== committedDigest || current.digest === prior.baseline.digest) return;
|
|
99
|
+
state.files.set(file, { ...prior, currentDigest: current.digest,
|
|
100
|
+
evidenceRef: `session-mutation.${hash(JSON.stringify([prior.evidenceRef, "revert", current.digest]))}` });
|
|
101
|
+
}
|
|
102
|
+
current(cwd: string, sessionId: string, file: string, policy: WorkspacePolicy): SessionMutationObservation | null {
|
|
103
|
+
const state = this.#sessions.get(this.#key(cwd, sessionId)), observation = state?.files.get(file);
|
|
104
|
+
if (!observation) return null;
|
|
105
|
+
if (this.#read(cwd, file, policy)?.digest !== observation.currentDigest) { state!.files.delete(file); return null; }
|
|
106
|
+
return structuredClone(observation);
|
|
107
|
+
}
|
|
108
|
+
// Only the trusted runtime may restore this between sandbox invocations.
|
|
109
|
+
// Revalidate the path policy and live digest before the next tool starts.
|
|
110
|
+
restore(cwd: string, sessionId: string, file: string, value: unknown, policy: WorkspacePolicy): void {
|
|
111
|
+
const item = value as SessionMutationObservation | null;
|
|
112
|
+
if (!item || item.path !== file || typeof item.baseline?.content !== "string"
|
|
113
|
+
|| Buffer.byteLength(item.baseline.content) > MAX_BYTES
|
|
114
|
+
|| (item.baseline.digest === "absent" ? item.baseline.content !== "" : hash(item.baseline.content) !== item.baseline.digest)
|
|
115
|
+
|| !/^[a-f0-9]{64}$/.test(item.currentDigest) || typeof item.toolCallId !== "string" || item.toolCallId.length > 256
|
|
116
|
+
|| !/^session-mutation\.[a-f0-9]{64}$/.test(item.evidenceRef)
|
|
117
|
+
|| this.#read(cwd, file, policy)?.digest !== item.currentDigest) return;
|
|
118
|
+
this.#session(cwd, sessionId).files.set(file, structuredClone(item)); this.#trim();
|
|
119
|
+
}
|
|
120
|
+
clear(cwd: string, sessionId: string): void { this.#sessions.delete(this.#key(cwd, sessionId)); }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const KEY = Symbol.for("piagent.session-mutation-observer.v1");
|
|
124
|
+
const shared = globalThis as typeof globalThis & { [KEY]?: SessionMutationObserver };
|
|
125
|
+
export const sessionMutationObserver = shared[KEY] ??= new SessionMutationObserver();
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// Shared source-action data without filesystem or runtime dependencies.
|
|
2
|
+
|
|
3
|
+
export type WebUiIdentity = {
|
|
4
|
+
projectRef: string;
|
|
5
|
+
runtimeInstanceId: string;
|
|
6
|
+
sessionRef: string;
|
|
7
|
+
taskId: string | null;
|
|
8
|
+
taskRunId: string | null;
|
|
9
|
+
agentOperationId: string | null;
|
|
10
|
+
toolCallId: string | null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ExactGitPatchAuthority = { header: Buffer; hunks: Array<{ hunkRef: string; bytes: Buffer }> };
|
|
14
|
+
|
|
15
|
+
export type SourceRevertTarget = {
|
|
16
|
+
view: "working-tree"; repoRef: string; fileRef: string; diffRef: string; status: "M"; path: string; oldPath: null;
|
|
17
|
+
effect: "restore-worktree-from-index"; taskRevision: string | null; workspaceRevision: string; indexRevision: string;
|
|
18
|
+
viewRevision: string; fileRevision: string; workspacePreimage: string; indexPreimage: string; patchPreimage: string;
|
|
19
|
+
contentDigest: string; hunkRefs: string[]; previewRef: string; confirmedPreviewDigest: string; expiresAt: string;
|
|
20
|
+
summary: { files: 1; hunks: number; additionsDiscarded: number; deletionsRestored: number;
|
|
21
|
+
effect: "discard-unstaged-keep-index"; recovery: "not-guaranteed" };
|
|
22
|
+
};
|
|
23
|
+
export type SourceRevertProjection = {
|
|
24
|
+
schemaVersion: 1; version: "piagent-webui-source-revert-v1"; generatedAt: string; identity: WebUiIdentity;
|
|
25
|
+
action: "source.revert"; state: "ready" | "unavailable"; target: SourceRevertTarget | null;
|
|
26
|
+
preview: { basis: "index-to-working-tree"; hunks: RevertPreviewHunk[]; truncated: false } | null; reasonCode: string | null;
|
|
27
|
+
health: { state: "ok" | "unavailable" | "error"; reasonCode: string | null; message: string | null };
|
|
28
|
+
};
|
|
29
|
+
export type RevertPreviewLine = { kind: "added" | "deleted" | "context" | "meta"; marker: "+" | "-" | " " | "\\"; text: string };
|
|
30
|
+
export type RevertPreviewHunk = { hunkRef: string; header: string; lines: RevertPreviewLine[] };
|
|
31
|
+
export type SourceRevertAuthority = {
|
|
32
|
+
repoRoot: string; repoPath: string; indexObject: string; indexMode: "100644" | "100755";
|
|
33
|
+
target: SourceRevertTarget; patchAuthority: ExactGitPatchAuthority;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type SourceMutationAction = "source.stage" | "source.unstage";
|
|
37
|
+
export type SourceMutationTarget = {
|
|
38
|
+
view: "working-tree" | "staged";
|
|
39
|
+
repoRef: string;
|
|
40
|
+
fileRef: string;
|
|
41
|
+
diffRef: string;
|
|
42
|
+
status: "A" | "M" | "D" | "R" | "U";
|
|
43
|
+
path: string;
|
|
44
|
+
oldPath: string | null;
|
|
45
|
+
effect: "copy-worktree-to-index" | "restore-index-from-head";
|
|
46
|
+
taskRevision: string;
|
|
47
|
+
workspaceRevision: string;
|
|
48
|
+
indexRevision: string;
|
|
49
|
+
viewRevision: string;
|
|
50
|
+
fileRevision: string;
|
|
51
|
+
workspacePreimage: string;
|
|
52
|
+
indexPreimage: string;
|
|
53
|
+
patchPreimage: string;
|
|
54
|
+
contentDigest: string;
|
|
55
|
+
hunkRefs: string[];
|
|
56
|
+
};
|
|
57
|
+
export type SourceMutationProjection = {
|
|
58
|
+
schemaVersion: 1;
|
|
59
|
+
version: "piagent-webui-source-mutation-v1";
|
|
60
|
+
generatedAt: string;
|
|
61
|
+
identity: WebUiIdentity;
|
|
62
|
+
action: SourceMutationAction;
|
|
63
|
+
state: "ready" | "unavailable";
|
|
64
|
+
target: SourceMutationTarget | null;
|
|
65
|
+
reasonCode: string | null;
|
|
66
|
+
health: { state: "ok" | "unavailable" | "error"; reasonCode: string | null; message: string | null };
|
|
67
|
+
};
|
|
68
|
+
export type SourceMutationAuthority = {
|
|
69
|
+
repoRoot: string;
|
|
70
|
+
repoPaths: string[];
|
|
71
|
+
headOid: string | null;
|
|
72
|
+
indexStatus: string;
|
|
73
|
+
worktreeStatus: string;
|
|
74
|
+
target: SourceMutationTarget;
|
|
75
|
+
patchAuthority: ExactGitPatchAuthority | null;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export const SOURCE_MUTATION_EVIDENCE_VERSION = "piagent-webui-source-mutation-evidence-v1";
|
|
79
|
+
|
|
80
|
+
export type SourceMutationEvidenceRecord = {
|
|
81
|
+
schemaVersion: 1;
|
|
82
|
+
version: typeof SOURCE_MUTATION_EVIDENCE_VERSION;
|
|
83
|
+
recordId: string;
|
|
84
|
+
evidenceRef: string;
|
|
85
|
+
sequence: number;
|
|
86
|
+
phase: "requested" | "settled" | "rejected" | "uncertain";
|
|
87
|
+
resultCode: "mutation-requested" | "staged" | "unstaged" | "reverted" | "mutation-rejected" | "effect-unknown";
|
|
88
|
+
failureCode: string | null;
|
|
89
|
+
projectRef: string;
|
|
90
|
+
runtimeInstanceId: string;
|
|
91
|
+
sessionRef: string;
|
|
92
|
+
taskId: string;
|
|
93
|
+
taskRunId: string;
|
|
94
|
+
commandId: string;
|
|
95
|
+
idempotencyKeyDigest: string;
|
|
96
|
+
actionDigest: string;
|
|
97
|
+
action: SourceMutationAction | "source.revert";
|
|
98
|
+
target: SourceMutationTarget | SourceRevertTarget;
|
|
99
|
+
selectedHunkRefs: string[];
|
|
100
|
+
requestedAt: string;
|
|
101
|
+
recordedAt: string;
|
|
102
|
+
retentionUntil: string;
|
|
103
|
+
beforeIndexPreimage: string;
|
|
104
|
+
afterIndexPreimage: string | null;
|
|
105
|
+
beforeWorkspacePreimage: string;
|
|
106
|
+
afterWorkspacePreimage: string | null;
|
|
107
|
+
observedRevisionsBefore: Record<string, string | null>;
|
|
108
|
+
observedRevisionsAfter: Record<string, string | null> | null;
|
|
109
|
+
integrityDigest: string;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export type SourceMutationAppendOptions = Omit<SourceMutationEvidenceRecord, "schemaVersion" | "version" | "recordId" | "evidenceRef" | "sequence" | "retentionUntil" | "integrityDigest"> & { projectRoot: string };
|
|
113
|
+
|
|
114
|
+
export type SourceIndexTransactionResult = {
|
|
115
|
+
state: "settled" | "rejected" | "uncertain";
|
|
116
|
+
reasonCode: string | null;
|
|
117
|
+
beforeIndexPreimage: string;
|
|
118
|
+
afterIndexPreimage: string | null;
|
|
119
|
+
beforeWorkspacePreimage: string;
|
|
120
|
+
afterWorkspacePreimage: string | null;
|
|
121
|
+
executor: "pi-guard";
|
|
122
|
+
directExecution: false;
|
|
123
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { WebUiIdentity } from "./source-action-contract.ts";
|
|
2
|
+
export type { WebUiIdentity } from "./source-action-contract.ts";
|
|
3
|
+
|
|
1
4
|
import { createHash } from "node:crypto";
|
|
2
5
|
import path from "node:path";
|
|
3
6
|
import {
|
|
@@ -15,15 +18,6 @@ import { sourceInspectionPlan } from "./source-inspection-plan.ts";
|
|
|
15
18
|
const DEFAULT_PAGE_LIMIT = 300;
|
|
16
19
|
const MAX_PAGE_LIMIT = 2_000;
|
|
17
20
|
const MAX_TEXT_BYTES = 4 * 1024 * 1024;
|
|
18
|
-
export type WebUiIdentity = {
|
|
19
|
-
projectRef: string;
|
|
20
|
-
runtimeInstanceId: string;
|
|
21
|
-
sessionRef: string;
|
|
22
|
-
taskId: string | null;
|
|
23
|
-
taskRunId: string | null;
|
|
24
|
-
agentOperationId: string | null;
|
|
25
|
-
toolCallId: string | null;
|
|
26
|
-
};
|
|
27
21
|
export type SourceProjectionOptions = {
|
|
28
22
|
cwd: string;
|
|
29
23
|
identity: WebUiIdentity;
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { SourceMutationAction, SourceMutationTarget, SourceMutationProjection, SourceMutationAuthority } from "./source-action-contract.ts";
|
|
2
|
+
export type { SourceMutationAction, SourceMutationTarget, SourceMutationProjection, SourceMutationAuthority } from "./source-action-contract.ts";
|
|
3
|
+
|
|
1
4
|
import { createHash } from "node:crypto";
|
|
2
5
|
|
|
3
6
|
import { runReadOnlyGit } from "./git-status-adapter.ts";
|
|
@@ -7,47 +10,6 @@ import { collectSelectedSourceFileState, type SourceChangeDocument, type SourceP
|
|
|
7
10
|
import type { DiffDocument } from "./diff-projection.ts";
|
|
8
11
|
import { hashWorkspaceFile, inspectWorkspaceEntry } from "./workspace-file-reader.ts";
|
|
9
12
|
|
|
10
|
-
export type SourceMutationAction = "source.stage" | "source.unstage";
|
|
11
|
-
export type SourceMutationTarget = {
|
|
12
|
-
view: "working-tree" | "staged";
|
|
13
|
-
repoRef: string;
|
|
14
|
-
fileRef: string;
|
|
15
|
-
diffRef: string;
|
|
16
|
-
status: "A" | "M" | "D" | "R" | "U";
|
|
17
|
-
path: string;
|
|
18
|
-
oldPath: string | null;
|
|
19
|
-
effect: "copy-worktree-to-index" | "restore-index-from-head";
|
|
20
|
-
taskRevision: string;
|
|
21
|
-
workspaceRevision: string;
|
|
22
|
-
indexRevision: string;
|
|
23
|
-
viewRevision: string;
|
|
24
|
-
fileRevision: string;
|
|
25
|
-
workspacePreimage: string;
|
|
26
|
-
indexPreimage: string;
|
|
27
|
-
patchPreimage: string;
|
|
28
|
-
contentDigest: string;
|
|
29
|
-
hunkRefs: string[];
|
|
30
|
-
};
|
|
31
|
-
export type SourceMutationProjection = {
|
|
32
|
-
schemaVersion: 1;
|
|
33
|
-
version: "piagent-webui-source-mutation-v1";
|
|
34
|
-
generatedAt: string;
|
|
35
|
-
identity: WebUiIdentity;
|
|
36
|
-
action: SourceMutationAction;
|
|
37
|
-
state: "ready" | "unavailable";
|
|
38
|
-
target: SourceMutationTarget | null;
|
|
39
|
-
reasonCode: string | null;
|
|
40
|
-
health: { state: "ok" | "unavailable" | "error"; reasonCode: string | null; message: string | null };
|
|
41
|
-
};
|
|
42
|
-
export type SourceMutationAuthority = {
|
|
43
|
-
repoRoot: string;
|
|
44
|
-
repoPaths: string[];
|
|
45
|
-
headOid: string | null;
|
|
46
|
-
indexStatus: string;
|
|
47
|
-
worktreeStatus: string;
|
|
48
|
-
target: SourceMutationTarget;
|
|
49
|
-
patchAuthority: ExactGitPatchAuthority | null;
|
|
50
|
-
};
|
|
51
13
|
const REF = /^[A-Za-z0-9][A-Za-z0-9._:@~-]{0,159}$/, REVISION = /^[A-Za-z0-9][A-Za-z0-9._:~-]{0,159}$/,
|
|
52
14
|
DIGEST = /^sha256:[a-f0-9]{64}$/, WORKSPACE = /^wt-content-v2:[a-f0-9]{64}$/,
|
|
53
15
|
SAFE_PATH = /^(?!\/)(?![A-Za-z]:)(?!.*(?:^|\/)\.\.(?:\/|$))[^\u0000-\u001F\u007F\\]{1,1024}$/;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import type { SourceMutationEvidenceRecord, SourceMutationAppendOptions } from "./source-action-contract.ts";
|
|
2
|
+
export type { SourceMutationEvidenceRecord, SourceMutationAppendOptions } from "./source-action-contract.ts";
|
|
3
|
+
import { SOURCE_MUTATION_EVIDENCE_VERSION } from "./source-action-contract.ts";
|
|
4
|
+
export { SOURCE_MUTATION_EVIDENCE_VERSION } from "./source-action-contract.ts";
|
|
5
|
+
|
|
1
6
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
7
|
import fs from "node:fs";
|
|
3
8
|
import path from "node:path";
|
|
@@ -12,41 +17,6 @@ const MAX_RECORDS = 4_000, MAX_RECORD_BYTES = 96 * 1024;
|
|
|
12
17
|
const REF = /^[A-Za-z0-9][A-Za-z0-9._:@~-]{0,159}$/, DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
13
18
|
const REVISION = /^[A-Za-z0-9][A-Za-z0-9._:~-]{0,159}$/, REVISION_KEYS = ["runtimeRevision", "taskRevision", "controlRevision",
|
|
14
19
|
"workspaceRevision", "indexRevision", "approvalRevision", "sessionOptionRevision", "queueRevision"];
|
|
15
|
-
export const SOURCE_MUTATION_EVIDENCE_VERSION = "piagent-webui-source-mutation-evidence-v1";
|
|
16
|
-
|
|
17
|
-
export type SourceMutationEvidenceRecord = {
|
|
18
|
-
schemaVersion: 1;
|
|
19
|
-
version: typeof SOURCE_MUTATION_EVIDENCE_VERSION;
|
|
20
|
-
recordId: string;
|
|
21
|
-
evidenceRef: string;
|
|
22
|
-
sequence: number;
|
|
23
|
-
phase: "requested" | "settled" | "rejected" | "uncertain";
|
|
24
|
-
resultCode: "mutation-requested" | "staged" | "unstaged" | "reverted" | "mutation-rejected" | "effect-unknown";
|
|
25
|
-
failureCode: string | null;
|
|
26
|
-
projectRef: string;
|
|
27
|
-
runtimeInstanceId: string;
|
|
28
|
-
sessionRef: string;
|
|
29
|
-
taskId: string;
|
|
30
|
-
taskRunId: string;
|
|
31
|
-
commandId: string;
|
|
32
|
-
idempotencyKeyDigest: string;
|
|
33
|
-
actionDigest: string;
|
|
34
|
-
action: SourceMutationAction | "source.revert";
|
|
35
|
-
target: SourceMutationTarget | SourceRevertTarget;
|
|
36
|
-
selectedHunkRefs: string[];
|
|
37
|
-
requestedAt: string;
|
|
38
|
-
recordedAt: string;
|
|
39
|
-
retentionUntil: string;
|
|
40
|
-
beforeIndexPreimage: string;
|
|
41
|
-
afterIndexPreimage: string | null;
|
|
42
|
-
beforeWorkspacePreimage: string;
|
|
43
|
-
afterWorkspacePreimage: string | null;
|
|
44
|
-
observedRevisionsBefore: Record<string, string | null>;
|
|
45
|
-
observedRevisionsAfter: Record<string, string | null> | null;
|
|
46
|
-
integrityDigest: string;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
type AppendOptions = Omit<SourceMutationEvidenceRecord, "schemaVersion" | "version" | "recordId" | "evidenceRef" | "sequence" | "retentionUntil" | "integrityDigest"> & { projectRoot: string };
|
|
50
20
|
|
|
51
21
|
function sha(value: string): string { return createHash("sha256").update(value).digest("hex"); }
|
|
52
22
|
function evidenceDigest(value: unknown): string { return `sha256:${sha(canonicalReviewValue(value))}`; }
|
|
@@ -132,7 +102,7 @@ export function readSourceMutationEvidence(projectRoot: string, taskRunId: strin
|
|
|
132
102
|
return { records: corruptions.length ? [] : records.sort((left, right) => left.sequence - right.sequence), corruptions };
|
|
133
103
|
}
|
|
134
104
|
|
|
135
|
-
export function appendSourceMutationEvidence(options:
|
|
105
|
+
export function appendSourceMutationEvidence(options: SourceMutationAppendOptions): SourceMutationEvidenceRecord {
|
|
136
106
|
const manifest = readTaskBaselineManifest(options.projectRoot, options.taskRunId);
|
|
137
107
|
if (!manifest || manifest.taskId !== options.taskId || manifest.captureState !== "current"
|
|
138
108
|
|| taskBaselineRetentionState(manifest, new Date(options.recordedAt)) !== "active") throw new Error("mutation-baseline-unavailable");
|
|
@@ -1,8 +1,14 @@
|
|
|
1
|
+
import { readWorkspaceFile } from "./workspace-file-reader.ts";
|
|
2
|
+
import { sessionAuthoredHunkRefs } from "./session-hunk-provenance.ts";
|
|
3
|
+
import type { SessionMutationObservation } from "./session-mutation-observer.ts";
|
|
4
|
+
import type { SourceRevertTarget, SourceRevertProjection, SourceRevertAuthority, RevertPreviewHunk, RevertPreviewLine } from "./source-action-contract.ts";
|
|
5
|
+
export type { SourceRevertTarget, SourceRevertProjection, SourceRevertAuthority, RevertPreviewHunk, RevertPreviewLine } from "./source-action-contract.ts";
|
|
6
|
+
|
|
1
7
|
import { createHmac } from "node:crypto";
|
|
2
8
|
|
|
3
9
|
import { canonicalReviewValue } from "./review-state-contract.ts";
|
|
4
10
|
import { collectExactGitPatchAuthority, type DiffDocument, type ExactGitPatchAuthority } from "./diff-projection.ts";
|
|
5
|
-
import {
|
|
11
|
+
import { deriveSourceDiffTarget } from "./review-state-projection.ts";
|
|
6
12
|
import { collectSelectedSourceFileState, type SourceChangeDocument, type SourceProjectionOptions, type WebUiIdentity } from "./source-change-projection.ts";
|
|
7
13
|
import { collectIndexPreimage, collectSelectedWorkspacePreimage } from "./source-mutation-projection.ts";
|
|
8
14
|
|
|
@@ -10,29 +16,9 @@ const REF = /^[A-Za-z0-9][A-Za-z0-9._:@~-]{0,159}$/, REVISION = /^[A-Za-z0-9][A-
|
|
|
10
16
|
DIGEST = /^sha256:[a-f0-9]{64}$/, WORKSPACE = /^wt-content-v2:[a-f0-9]{64}$/,
|
|
11
17
|
SAFE_PATH = /^(?!\/)(?![A-Za-z]:)(?!.*(?:^|\/)\.\.(?:\/|$))[^\u0000-\u001F\u007F\\]{1,1024}$/;
|
|
12
18
|
|
|
13
|
-
export type SourceRevertTarget = {
|
|
14
|
-
view: "working-tree"; repoRef: string; fileRef: string; diffRef: string; status: "M"; path: string; oldPath: null;
|
|
15
|
-
effect: "restore-worktree-from-index"; taskRevision: string; workspaceRevision: string; indexRevision: string;
|
|
16
|
-
viewRevision: string; fileRevision: string; workspacePreimage: string; indexPreimage: string; patchPreimage: string;
|
|
17
|
-
contentDigest: string; hunkRefs: string[]; previewRef: string; confirmedPreviewDigest: string; expiresAt: string;
|
|
18
|
-
summary: { files: 1; hunks: number; additionsDiscarded: number; deletionsRestored: number;
|
|
19
|
-
effect: "discard-unstaged-keep-index"; recovery: "not-guaranteed" };
|
|
20
|
-
};
|
|
21
|
-
export type SourceRevertProjection = {
|
|
22
|
-
schemaVersion: 1; version: "piagent-webui-source-revert-v1"; generatedAt: string; identity: WebUiIdentity;
|
|
23
|
-
action: "source.revert"; state: "ready" | "unavailable"; target: SourceRevertTarget | null;
|
|
24
|
-
preview: { basis: "index-to-working-tree"; hunks: RevertPreviewHunk[]; truncated: false } | null; reasonCode: string | null;
|
|
25
|
-
health: { state: "ok" | "unavailable" | "error"; reasonCode: string | null; message: string | null };
|
|
26
|
-
};
|
|
27
|
-
type RevertPreviewLine = { kind: "added" | "deleted" | "context" | "meta"; marker: "+" | "-" | " " | "\\"; text: string };
|
|
28
|
-
type RevertPreviewHunk = { hunkRef: string; header: string; lines: RevertPreviewLine[] };
|
|
29
|
-
export type SourceRevertAuthority = {
|
|
30
|
-
repoRoot: string; repoPath: string; indexObject: string; indexMode: "100644" | "100755";
|
|
31
|
-
target: SourceRevertTarget; patchAuthority: ExactGitPatchAuthority;
|
|
32
|
-
};
|
|
33
19
|
type CollectOptions = {
|
|
34
20
|
cwd: string; identity: WebUiIdentity; sourceView: SourceChangeDocument; taskView: SourceChangeDocument | null; diff: DiffDocument;
|
|
35
|
-
taskRevision: string; workspaceRevision: string; indexRevision: string; selectedHunkRefs?: string[]; generatedAt?: string;
|
|
21
|
+
taskRevision: string | null; sessionObservation?: SessionMutationObservation; workspaceRevision: string; indexRevision: string; selectedHunkRefs?: string[]; generatedAt?: string;
|
|
36
22
|
confirmationKey: Buffer;
|
|
37
23
|
isProtectedPath?: SourceProjectionOptions["isProtectedPath"]; guardAvailable?: boolean; timeoutMs?: number; maxGitBytes?: number;
|
|
38
24
|
};
|
|
@@ -69,7 +55,8 @@ export function validateSourceRevertTarget(value: unknown): SourceRevertTarget {
|
|
|
69
55
|
if (canonicalReviewValue(Object.keys(target).sort()) !== canonicalReviewValue(keys.sort()) || target.view !== "working-tree" || target.status !== "M"
|
|
70
56
|
|| target.oldPath !== null || target.effect !== "restore-worktree-from-index" || !SAFE_PATH.test(target.path)
|
|
71
57
|
|| ![target.repoRef, target.fileRef, target.diffRef, target.previewRef].every((item) => REF.test(item))
|
|
72
|
-
||
|
|
58
|
+
|| (target.taskRevision !== null && !REVISION.test(target.taskRevision))
|
|
59
|
+
|| ![target.workspaceRevision, target.indexRevision, target.viewRevision, target.fileRevision].every((item) => REVISION.test(item))
|
|
73
60
|
|| !WORKSPACE.test(target.workspacePreimage) || ![target.indexPreimage, target.patchPreimage, target.contentDigest, target.confirmedPreviewDigest].every((item) => DIGEST.test(item))
|
|
74
61
|
|| !Array.isArray(target.hunkRefs) || target.hunkRefs.length > 1 || new Set(target.hunkRefs).size !== target.hunkRefs.length
|
|
75
62
|
|| target.hunkRefs.some((item) => !REF.test(item)) || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(target.expiresAt)
|
|
@@ -82,20 +69,23 @@ export function validateSourceRevertTarget(value: unknown): SourceRevertTarget {
|
|
|
82
69
|
|
|
83
70
|
export async function collectSourceRevertPreview(options: CollectOptions): Promise<{ projection: SourceRevertProjection; authority: SourceRevertAuthority | null }> {
|
|
84
71
|
const selectedHunkRefs = options.selectedHunkRefs ?? [];
|
|
72
|
+
const sessionBound = options.identity.taskId === null && options.identity.taskRunId === null && options.taskRevision === null;
|
|
73
|
+
const bindingAvailable = sessionBound ? !!options.sessionObservation : !!options.identity.taskId && !!options.identity.taskRunId && !!options.taskRevision;
|
|
85
74
|
if (!Buffer.isBuffer(options.confirmationKey) || options.confirmationKey.length < 32) return unavailable(options, "revert-confirmation-authority-unavailable");
|
|
86
75
|
if (options.guardAvailable !== true) return unavailable(options, "mutation-guard-unavailable");
|
|
87
|
-
if (options.sourceView.view !== "working-tree" || options.sourceView.availability.state !== "current" || !
|
|
88
|
-
|| !options.
|
|
76
|
+
if (options.sourceView.view !== "working-tree" || options.sourceView.availability.state !== "current" || !bindingAvailable
|
|
77
|
+
|| !options.workspaceRevision || !options.indexRevision || selectedHunkRefs.length > 1 || new Set(selectedHunkRefs).size !== selectedHunkRefs.length)
|
|
89
78
|
return unavailable(options, "revert-authority-unavailable");
|
|
90
79
|
const file = options.sourceView.files.find((candidate: any) => candidate.fileRef === (options.diff as any).file?.fileRef) as Record<string, any> | undefined;
|
|
91
80
|
if (!file || file.status !== "M" || file.git?.worktreeStatus !== "M" || file.oldPath !== null || file.pathDisplay !== "exact-safe"
|
|
92
81
|
|| file.content?.kind !== "text" || file.content?.access !== "available") return unavailable(options, "revert-target-unavailable");
|
|
93
82
|
const taskFile = options.taskView?.files.find((candidate: any) => candidate.path === file.path && candidate.currentDigest === file.currentDigest) as Record<string, any> | undefined;
|
|
94
|
-
if (!taskFile || taskFile.provenance?.classification !== "runtime-observed-agent" || taskFile.provenance?.evidence !== "exact"
|
|
95
|
-
|| !Array.isArray(taskFile.provenance?.mutationEvidenceRefs) || taskFile.provenance.mutationEvidenceRefs.length === 0)
|
|
83
|
+
if (!sessionBound && (!taskFile || taskFile.provenance?.classification !== "runtime-observed-agent" || taskFile.provenance?.evidence !== "exact"
|
|
84
|
+
|| !Array.isArray(taskFile.provenance?.mutationEvidenceRefs) || taskFile.provenance.mutationEvidenceRefs.length === 0))
|
|
96
85
|
return unavailable(options, "revert-provenance-unavailable");
|
|
97
|
-
const review =
|
|
98
|
-
taskRevision: options.taskRevision, workspaceRevision: options.workspaceRevision, indexRevision: options.indexRevision
|
|
86
|
+
const review = deriveSourceDiffTarget({ diff: options.diff, taskId: options.identity.taskId, taskRunId: options.identity.taskRunId,
|
|
87
|
+
taskRevision: options.taskRevision, workspaceRevision: options.workspaceRevision, indexRevision: options.indexRevision,
|
|
88
|
+
...(sessionBound ? { sessionIdentity: options.identity } : {}) });
|
|
99
89
|
if (!review || review.view !== "working-tree" || review.fileRef !== file.fileRef || (options.diff as any).truncation?.truncated !== false
|
|
100
90
|
|| (options.diff as any).redaction?.applied !== false) return unavailable(options, "revert-preview-incomplete");
|
|
101
91
|
const selected = await collectSelectedSourceFileState({ cwd: options.cwd, identity: options.identity, taskRevision: options.taskRevision,
|
|
@@ -108,6 +98,8 @@ export async function collectSourceRevertPreview(options: CollectOptions): Promi
|
|
|
108
98
|
const repoPath = selected.record.path.value;
|
|
109
99
|
if (!repoPath || options.isProtectedPath?.(selected.snapshot.repoRoot, repoPath) === true) return unavailable(options, "protected-path");
|
|
110
100
|
try {
|
|
101
|
+
const initialWorkspacePreimage = collectSelectedWorkspacePreimage(selected.snapshot.repoRoot, [repoPath]);
|
|
102
|
+
const initialIndexPreimage = await collectIndexPreimage(selected.snapshot.repoRoot, options.timeoutMs, options.maxGitBytes);
|
|
111
103
|
const patchAuthority = await collectExactGitPatchAuthority({ snapshot: selected.snapshot, record: selected.record, view: "unstaged",
|
|
112
104
|
fileRef: String(file.fileRef), timeoutMs: options.timeoutMs, maxBytes: options.maxGitBytes });
|
|
113
105
|
if (!patchAuthority || patchAuthority.hunks.length === 0 || patchAuthority.hunks.length > 128
|
|
@@ -115,14 +107,28 @@ export async function collectSourceRevertPreview(options: CollectOptions): Promi
|
|
|
115
107
|
const selectedAuthorityHunks = patchAuthority.hunks.filter((hunk) => selectedHunkRefs.length === 0 || selectedHunkRefs.includes(hunk.hunkRef));
|
|
116
108
|
const selectedRefs = selectedAuthorityHunks.map((hunk) => hunk.hunkRef);
|
|
117
109
|
if (selectedHunkRefs.length && canonicalReviewValue(selectedRefs) !== canonicalReviewValue(selectedHunkRefs)) return unavailable(options, "revert-hunk-unavailable");
|
|
110
|
+
if (sessionBound) {
|
|
111
|
+
const observation = options.sessionObservation!;
|
|
112
|
+
if (observation.path !== file.path) return unavailable(options, "revert-provenance-unavailable");
|
|
113
|
+
const current = readWorkspaceFile(selected.snapshot.repoRoot, repoPath, 256 * 1024).toString("utf8");
|
|
114
|
+
const authored = sessionAuthoredHunkRefs(observation, current, patchAuthority);
|
|
115
|
+
if (selectedRefs.some(ref => !authored.includes(ref))) return unavailable(options, "revert-provenance-unavailable");
|
|
116
|
+
}
|
|
118
117
|
const previewHunks = selectedAuthorityHunks.map(previewHunk);
|
|
119
118
|
if (previewHunks.some((hunk) => !hunk) || previewHunks.reduce((count, hunk) => count + (hunk?.lines.length ?? 0), 0) > 5_000)
|
|
120
119
|
return unavailable(options, "revert-preview-incomplete");
|
|
121
120
|
const exactPreviewHunks = previewHunks as RevertPreviewHunk[];
|
|
122
121
|
const workspacePreimage = collectSelectedWorkspacePreimage(selected.snapshot.repoRoot, [repoPath]);
|
|
123
122
|
const indexPreimage = await collectIndexPreimage(selected.snapshot.repoRoot, options.timeoutMs, options.maxGitBytes);
|
|
123
|
+
const finalSelected = await collectSelectedSourceFileState({ cwd: options.cwd, identity: options.identity, taskRevision: options.taskRevision,
|
|
124
|
+
generatedAt: options.generatedAt, isProtectedPath: options.isProtectedPath, timeoutMs: options.timeoutMs, maxGitBytes: options.maxGitBytes },
|
|
125
|
+
"working-tree", file.fileRef, String(file.basisRef), [file.path]);
|
|
126
|
+
if (!finalSelected || finalSelected.file.fileRevision !== file.fileRevision
|
|
127
|
+
|| workspacePreimage !== initialWorkspacePreimage || indexPreimage !== initialIndexPreimage
|
|
128
|
+
|| collectSelectedWorkspacePreimage(selected.snapshot.repoRoot, [repoPath]) !== workspacePreimage)
|
|
129
|
+
return unavailable(options, "revert-target-stale");
|
|
124
130
|
const generatedAt = options.generatedAt ?? new Date().toISOString(), expiresAt = new Date(Date.parse(generatedAt) + 5 * 60_000).toISOString();
|
|
125
|
-
const confirmation = { projectRef: options.identity.projectRef, sessionRef: options.identity.sessionRef, taskRunId: options.identity.taskRunId,
|
|
131
|
+
const confirmation = { projectRef: options.identity.projectRef, runtimeInstanceId: options.identity.runtimeInstanceId, sessionRef: options.identity.sessionRef, taskRunId: options.identity.taskRunId,
|
|
126
132
|
fileRef: file.fileRef, hunkRefs: selectedHunkRefs, taskRevision: options.taskRevision, workspaceRevision: options.workspaceRevision,
|
|
127
133
|
indexRevision: options.indexRevision, viewRevision: review.viewRevision, fileRevision: review.fileRevision,
|
|
128
134
|
workspacePreimage, indexPreimage, patchPreimage: review.patchPreimage, contentDigest: review.contentDigest };
|
|
@@ -8,19 +8,20 @@ export type TextDiffResult = {
|
|
|
8
8
|
reasonCode: string | null;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
function lines(content: Buffer): string[] | null {
|
|
11
|
+
function lines(content: Buffer, preserveLineEndings: boolean): string[] | null {
|
|
12
12
|
if (content.includes(0)) return null;
|
|
13
13
|
const text = content.toString("utf8");
|
|
14
14
|
if (Buffer.from(text, "utf8").compare(content) !== 0) return null;
|
|
15
15
|
if (!text) return [];
|
|
16
|
+
if (preserveLineEndings) return text.match(/[^\n]*\n|[^\n]+$/g) ?? [];
|
|
16
17
|
const result = text.split("\n");
|
|
17
18
|
if (text.endsWith("\n")) result.pop();
|
|
18
19
|
return result.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
export function diffTextBuffers(base: Buffer, current: Buffer, maxCells = 4_000_000): TextDiffResult {
|
|
22
|
-
const before = lines(base);
|
|
23
|
-
const after = lines(current);
|
|
22
|
+
export function diffTextBuffers(base: Buffer, current: Buffer, maxCells = 4_000_000, preserveLineEndings = false): TextDiffResult {
|
|
23
|
+
const before = lines(base, preserveLineEndings);
|
|
24
|
+
const after = lines(current, preserveLineEndings);
|
|
24
25
|
if (!before || !after) return { operations: [], additions: 0, deletions: 0, exact: false, reasonCode: "binary-content" };
|
|
25
26
|
const cells = (before.length + 1) * (after.length + 1);
|
|
26
27
|
if (!Number.isSafeInteger(cells) || cells > maxCells) {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { createManagedSourceRevert, type ManagedSourceContext } from "./managed-source-revert.ts";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { SessionMutationObserver } from "./session-mutation-observer.ts";
|
|
4
|
+
import { writeWorkspaceFile, WorkspaceWriteError, type WorkspaceWriteInput } from "../../extensions/workspace-write.ts";
|
|
5
|
+
import { searchWorkspace } from "../../extensions/workspace-search.ts";
|
|
6
|
+
import { loadProjectContextIndexPolicy } from "../../extensions/workspace-policy.js";
|
|
7
|
+
import { inspectWorkspaceFile, inspectWorkspaceTree, inspectWorkspacePaths } from "../../extensions/workspace-inspection.ts";
|
|
8
|
+
import { inspectWorkspaceEntry } from "../../security/workspace-file-reader.ts";
|
|
9
|
+
import { managedCheckpointOperation } from "../checkpoints/checkpoint-worker.ts";
|
|
10
|
+
|
|
11
|
+
// Bundled with its policy assets under managed/dist. The worker runs only
|
|
12
|
+
// after the tool boundary has entered the conversation's sandbox.
|
|
13
|
+
export async function inspectManagedWorkspace(cwd: string, assets: string, request: unknown, readOnly = false) {
|
|
14
|
+
const input = request as { kind?: unknown; path?: unknown; payload?: unknown } | null;
|
|
15
|
+
if (input?.kind === "checkpoint") return managedCheckpointOperation(cwd, () => managedWorkspacePolicy(cwd, assets, readOnly), request);
|
|
16
|
+
const writing = input?.kind === "write", state = input?.kind === "write-state";
|
|
17
|
+
if (!input || (!writing && !state && (!["tree", "file", "paths", "search"].includes(String(input.kind)) || typeof input.path !== "string"))
|
|
18
|
+
|| Object.keys(input).some(key => key !== "kind" && key !== (writing ? "payload" : state ? "kind" : "path"))) throw new Error("workspace-request-invalid");
|
|
19
|
+
const context = managedWorkspacePolicy(cwd, assets, readOnly);
|
|
20
|
+
if (state) return { writable: !context.readOnly };
|
|
21
|
+
if (writing) {
|
|
22
|
+
try { return { state: "saved", ...writeWorkspaceFile(cwd, input.payload as WorkspaceWriteInput, context) }; }
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error instanceof WorkspaceWriteError) return { state: "rejected", code: error.code, currentDigest: error.currentDigest };
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const relative = input.path as string;
|
|
29
|
+
return input.kind === "search" ? await searchWorkspace(cwd, relative, context) : input.kind === "paths" ? await inspectWorkspacePaths(cwd, relative, context) : input.kind === "tree" ? inspectWorkspaceTree(cwd, relative, context) : inspectWorkspaceFile(cwd, relative, context);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function managedWorkspacePolicy(cwd: string, assets: string, readOnly: boolean) {
|
|
33
|
+
try {
|
|
34
|
+
const stat = inspectWorkspaceEntry(cwd, ".pi/piagent-profile.json");
|
|
35
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1_048_576) throw new Error("workspace-policy-unavailable");
|
|
36
|
+
} catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; }
|
|
37
|
+
const { policy, profile } = loadProjectContextIndexPolicy(assets, cwd);
|
|
38
|
+
return { policy, profile, readOnly: readOnly || profile.permissionProfile === "read-only" };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function beginManagedMutationObservation(cwd: string, assets: string, request: {
|
|
42
|
+
toolName: string; args: Record<string, unknown>; sessionId: string; toolCallId: string; prior: unknown;
|
|
43
|
+
}, readOnly: boolean) {
|
|
44
|
+
const observer = new SessionMutationObserver();
|
|
45
|
+
if (!["edit", "write"].includes(request.toolName) || typeof request.args.path !== "string") return () => null;
|
|
46
|
+
const file = path.relative(cwd, path.resolve(cwd, request.args.path)).split(path.sep).join("/");
|
|
47
|
+
const policy = managedWorkspacePolicy(cwd, assets, readOnly);
|
|
48
|
+
observer.restore(cwd, request.sessionId, file, request.prior, policy);
|
|
49
|
+
observer.begin(cwd, request.sessionId, request.toolCallId, request.toolName, request.args, [file], policy);
|
|
50
|
+
return () => {
|
|
51
|
+
// Profile changes during execution must also take effect before disclosure.
|
|
52
|
+
const currentPolicy = managedWorkspacePolicy(cwd, assets, readOnly);
|
|
53
|
+
observer.finish(cwd, request.sessionId, request.toolCallId, request.toolName, true, currentPolicy);
|
|
54
|
+
return observer.current(cwd, request.sessionId, file, currentPolicy);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function managedSourceRevert(cwd: string, assets: string, context: Omit<ManagedSourceContext, "cwd" | "policy">, readOnly: boolean) {
|
|
59
|
+
return createManagedSourceRevert({ ...context, cwd, policy: () => managedWorkspacePolicy(cwd, assets, readOnly) });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Only tool-worker supplies this context, separately from model/browser args.
|
|
63
|
+
export async function inspectManagedSource(cwd: string, assets: string, request: any, trusted: any, readOnly: boolean) {
|
|
64
|
+
if (!trusted || !/^[a-f0-9]{64}$/.test(trusted.confirmationKey) || !Array.isArray(trusted.observations)
|
|
65
|
+
|| trusted.observations.length > 32) throw new Error("source-boundary-authority-required");
|
|
66
|
+
const observations = new Map<string, unknown>(trusted.observations);
|
|
67
|
+
const adapter = managedSourceRevert(cwd, assets, { identity: trusted.identity, rawSessionId: trusted.rawSessionId,
|
|
68
|
+
controlRevision: trusted.controlRevision, confirmationKey: Buffer.from(trusted.confirmationKey, "hex"),
|
|
69
|
+
idle: () => true, observation: file => observations.get(file) }, readOnly);
|
|
70
|
+
if (request?.kind === "diff" && typeof request.fileRef === "string") return { result: await adapter.diff(request.fileRef) };
|
|
71
|
+
if (request?.kind === "sources") return { result: await adapter.sources() };
|
|
72
|
+
if (request?.kind === "preview" && typeof request.fileRef === "string" && Array.isArray(request.hunkRefs))
|
|
73
|
+
return { result: await adapter.preview(request.fileRef, request.hunkRefs) };
|
|
74
|
+
if (request?.kind === "execute") {
|
|
75
|
+
const result = await adapter.execute(request.target);
|
|
76
|
+
return { result, sourceObservation: { path: request.target.path, observation: adapter.observation(request.target.path) } };
|
|
77
|
+
}
|
|
78
|
+
throw new Error("source-request-invalid");
|
|
79
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { SourceIndexTransactionResult } from "../inspection/source-action-contract.ts";
|
|
2
|
+
export type { SourceIndexTransactionResult } from "../inspection/source-action-contract.ts";
|
|
3
|
+
|
|
1
4
|
import { spawn } from "node:child_process";
|
|
2
5
|
import { randomBytes } from "node:crypto";
|
|
3
6
|
import fs from "node:fs";
|
|
@@ -14,16 +17,6 @@ import { localFilterDisableArgs } from "../inspection/git-filter-safety.ts";
|
|
|
14
17
|
|
|
15
18
|
const MAX_INDEX_BYTES = 64 * 1024 * 1024;
|
|
16
19
|
|
|
17
|
-
export type SourceIndexTransactionResult = {
|
|
18
|
-
state: "settled" | "rejected" | "uncertain";
|
|
19
|
-
reasonCode: string | null;
|
|
20
|
-
beforeIndexPreimage: string;
|
|
21
|
-
afterIndexPreimage: string | null;
|
|
22
|
-
beforeWorkspacePreimage: string;
|
|
23
|
-
afterWorkspacePreimage: string | null;
|
|
24
|
-
executor: "pi-guard";
|
|
25
|
-
directExecution: false;
|
|
26
|
-
};
|
|
27
20
|
|
|
28
21
|
function result(value: Omit<SourceIndexTransactionResult, "executor" | "directExecution">): SourceIndexTransactionResult {
|
|
29
22
|
return { ...value, executor: "pi-guard", directExecution: false };
|