@piagent/platform 1.3.0-ie.9 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -3
- package/README.md +1 -1
- package/docs/capability-packs.md +9 -2
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +7 -7
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/release-install-policy.md +29 -21
- package/docs/security-threat-model.md +1 -1
- package/docs/team-onboarding.md +2 -2
- 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/package.json +2 -3
- package/packages/piagent-core/capabilities/capability-verification-cache.js +280 -0
- package/packages/piagent-core/capabilities/runtime-integrity.js +10 -1
- package/packages/piagent-core/extensions/document-intake.ts +24 -10
- package/packages/piagent-core/extensions/piagent-guard.ts +28 -36
- package/packages/piagent-core/extensions/state-retention.js +77 -44
- package/packages/piagent-core/package.json +1 -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
|
|