@piagent/platform 1.4.0 → 1.5.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 +151 -0
- package/README.md +12 -2
- package/README.vi.md +47 -0
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +13 -0
- package/docs/languages.json +20 -4
- package/docs/mcp-and-tools.md +112 -1
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +42 -0
- package/docs/release-install-policy.md +21 -8
- package/docs/runtime-harness-standard.md +1 -1
- package/docs/security-threat-model.md +1 -1
- package/docs/task-implementation-contract.md +11 -4
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +70 -0
- package/docs/vi/architecture.md +13 -0
- package/docs-site/architecture.html +5 -1
- package/docs-site/benchmark.html +5 -1
- package/docs-site/commands.html +5 -1
- package/docs-site/content/en/index.html +49 -0
- package/docs-site/content/en/runtime.html +13 -0
- package/docs-site/content/index.html +49 -0
- package/docs-site/content/runtime.html +28 -0
- package/docs-site/context-engine.html +5 -1
- package/docs-site/en/architecture.html +5 -1
- package/docs-site/en/benchmark.html +5 -1
- package/docs-site/en/commands.html +5 -1
- package/docs-site/en/context-engine.html +5 -1
- package/docs-site/en/index.html +54 -1
- package/docs-site/en/mcp-approval.html +5 -1
- package/docs-site/en/mcp-auth.html +5 -1
- package/docs-site/en/mcp-servers.html +5 -1
- package/docs-site/en/mcp.html +5 -1
- package/docs-site/en/permissions.html +5 -1
- package/docs-site/en/profiles.html +5 -1
- package/docs-site/en/quickstart.html +5 -1
- package/docs-site/en/runtime.html +18 -1
- package/docs-site/en/scope.html +5 -1
- package/docs-site/en/security.html +5 -1
- package/docs-site/en/team.html +5 -1
- package/docs-site/en/whats-new.html +5 -1
- package/docs-site/en/workflows.html +5 -1
- package/docs-site/index.html +54 -1
- package/docs-site/mcp-approval.html +5 -1
- package/docs-site/mcp-auth.html +5 -1
- package/docs-site/mcp-servers.html +5 -1
- package/docs-site/mcp.html +5 -1
- package/docs-site/permissions.html +5 -1
- package/docs-site/profiles.html +5 -1
- package/docs-site/quickstart.html +5 -1
- package/docs-site/runtime.html +33 -1
- package/docs-site/scope.html +5 -1
- package/docs-site/security.html +5 -1
- package/docs-site/team.html +5 -1
- package/docs-site/whats-new.html +5 -1
- package/docs-site/workflows.html +5 -1
- package/evals/fixtures/piagent-webui/document-workspace-v1.invalid.json +17 -0
- package/evals/fixtures/piagent-webui/document-workspace-v1.valid.json +42 -0
- package/evals/fixtures/piagent-webui/session-command-v1.valid.json +2 -1
- package/package.json +14 -2
- package/packages/piagent-core/README.md +3 -2
- package/packages/piagent-core/capabilities/capability-verification-cache.js +17 -7
- package/packages/piagent-core/capabilities/runtime-integrity.js +51 -4
- package/packages/piagent-core/extensions/document-intake.ts +6 -3
- package/packages/piagent-core/extensions/piagent-guard.ts +20 -237
- package/packages/piagent-core/extensions/runtime-evidence.js +12 -1
- package/packages/piagent-core/extensions/shell-reach.ts +250 -0
- package/packages/piagent-core/mcp/mcp-command-actions.js +1 -1
- package/packages/piagent-core/mcp/mcp-server-catalog.js +135 -5
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/runtime/hooks/tool-call-hook.ts +9 -2
- package/packages/piagent-core/runtime/input/attachment-document-command.ts +39 -0
- package/packages/piagent-core/runtime/input/attachment-reservation.ts +36 -0
- package/packages/{piagent-webui/extension → piagent-core/runtime/input}/attachment-store.ts +221 -44
- package/packages/piagent-core/runtime/inspection/document-workspace-projection.ts +276 -0
- package/packages/piagent-core/runtime/inspection/project-runtime-inspection.ts +20 -5
- package/packages/piagent-core/runtime/inspection/session-identity.ts +20 -0
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +25 -2
- package/packages/piagent-core/runtime/policy/block-remedy.ts +139 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +9 -13
- package/packages/piagent-core/runtime/registration/workflow-commands.ts +5 -4
- package/packages/piagent-core/runtime/session/token-economics.js +34 -0
- package/packages/piagent-core/runtime/tools/phase-tool-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/input-routing.ts +13 -2
- package/packages/piagent-core/runtime/workflows/task-intake.ts +5 -0
- package/packages/piagent-core/security/sensitive-data.js +7 -0
- package/packages/piagent-webui/dist/client/assets/MarkdownMessage-Uouhx1QO.js +29 -0
- package/packages/piagent-webui/dist/client/assets/index-BV-_03yl.css +1 -0
- package/packages/piagent-webui/dist/client/assets/index-MBZq8XLV.js +174 -0
- package/packages/piagent-webui/dist/client/index.html +2 -2
- package/packages/piagent-webui/extension/lifecycle-controller.ts +15 -0
- package/packages/piagent-webui/extension/piagent-webui.ts +19 -6
- package/packages/piagent-webui/extension/same-session-bridge.ts +12 -12
- package/packages/piagent-webui/gateway/ai-capability-inspection.ts +53 -0
- package/packages/piagent-webui/gateway/extension-authority.ts +47 -0
- package/packages/piagent-webui/gateway/gateway-service.ts +23 -3
- package/packages/piagent-webui/gateway/native-project-picker.ts +63 -7
- package/packages/piagent-webui/gateway/session-attachment-registry.ts +165 -0
- package/packages/piagent-webui/gateway/session-catalog.ts +62 -9
- package/packages/piagent-webui/gateway/session-command-controller.ts +47 -14
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +67 -12
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +24 -11
- package/packages/piagent-webui/package.json +3 -1
- package/packages/piagent-webui/server/core-inspection-provider.ts +52 -15
- package/packages/piagent-webui/server/ipc-read-model-client.ts +2 -0
- package/packages/piagent-webui/server/loopback-server.ts +63 -25
- package/packages/piagent-webui/server/read-model-provider.ts +2 -0
- package/packages/piagent-webui/server/read-only-router.ts +6 -0
- package/packages/piagent-webui/server/transcript-projection.ts +58 -2
- package/schemas/piagent-webui/attachment-v1.schema.json +24 -4
- package/schemas/piagent-webui/capabilities-v1.schema.json +2 -2
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/document-workspace-v1.schema.json +155 -0
- package/schemas/piagent-webui/session-command-v1.schema.json +14 -3
- package/schemas/piagent-webui/transcript-v1.schema.json +27 -0
- package/scripts/build-docs-site.mjs +21 -1
- package/scripts/explain-command.mjs +240 -0
- package/scripts/install-global.sh +104 -3
- package/scripts/mcp-manage.mjs +17 -4
- package/scripts/pi-session-stats.sh +10 -2
- package/scripts/piagent-cli.mjs +14 -1
- package/scripts/piagent-dashboard.mjs +55 -2
- package/scripts/setup.sh +69 -33
- package/scripts/traffic-snapshot.mjs +214 -0
- package/scripts/typescript-loader.mjs +20 -4
- package/scripts/uninstall-global.sh +53 -2
- package/scripts/update-global.mjs +33 -1
- package/scripts/verify-local.sh +20 -1
- package/scripts/verify-translation-pairing.mjs +121 -0
- package/packages/piagent-webui/dist/client/assets/index-DjMntaK5.js +0 -172
- package/packages/piagent-webui/dist/client/assets/index-DwKCWbwd.css +0 -1
|
@@ -80,6 +80,46 @@ run_cmd() {
|
|
|
80
80
|
fi
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
# The helper can live under a non-default npm prefix: install falls back to a
|
|
84
|
+
# user-writable root when the configured one is not writable, and --npm-prefix
|
|
85
|
+
# picks one deliberately. A bare `npm uninstall -g` resolves against the
|
|
86
|
+
# configured prefix instead, so it removes nothing and still reports success.
|
|
87
|
+
# Derive the prefix from where this script actually runs, the same way
|
|
88
|
+
# update-global.mjs does, and only add --prefix when it differs.
|
|
89
|
+
installed_helper_prefix() {
|
|
90
|
+
local root="$ROOT"
|
|
91
|
+
[[ "$(basename "$root")" == "platform" ]] || return 0
|
|
92
|
+
local scope_root node_modules_root parent
|
|
93
|
+
scope_root="$(dirname "$root")"
|
|
94
|
+
[[ "$(basename "$scope_root")" == "@piagent" ]] || return 0
|
|
95
|
+
node_modules_root="$(dirname "$scope_root")"
|
|
96
|
+
[[ "$(basename "$node_modules_root")" == "node_modules" ]] || return 0
|
|
97
|
+
parent="$(dirname "$node_modules_root")"
|
|
98
|
+
# An npx run lives in a prunable cache that owns no bin on anyone's PATH;
|
|
99
|
+
# uninstalling from there would claim to remove an install nobody has.
|
|
100
|
+
case "$parent" in
|
|
101
|
+
*/_npx/*|*/_npx) return 0 ;;
|
|
102
|
+
esac
|
|
103
|
+
if [[ "$(basename "$parent")" == "lib" ]]; then
|
|
104
|
+
dirname "$parent"
|
|
105
|
+
else
|
|
106
|
+
printf '%s' "$parent"
|
|
107
|
+
fi
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
# The prefix npm has to be pointed at, or empty when the helper already lives
|
|
111
|
+
# under the configured one and the plain command is correct. Returns a single
|
|
112
|
+
# value rather than an argument list: macOS still ships bash 3.2, where
|
|
113
|
+
# expanding an empty array under `set -u` aborts the script.
|
|
114
|
+
npm_prefix_override() {
|
|
115
|
+
local helper_prefix configured
|
|
116
|
+
helper_prefix="$(installed_helper_prefix)"
|
|
117
|
+
[[ -n "$helper_prefix" ]] || return 0
|
|
118
|
+
configured="$(npm prefix -g 2>/dev/null || true)"
|
|
119
|
+
[[ "$helper_prefix" != "$configured" ]] || return 0
|
|
120
|
+
printf '%s' "$helper_prefix"
|
|
121
|
+
}
|
|
122
|
+
|
|
83
123
|
# Reads the package sources Pi has registered, so removal targets what is
|
|
84
124
|
# actually installed rather than what this version happens to install today. An
|
|
85
125
|
# older release registered a different source shape, and it still has to come
|
|
@@ -174,7 +214,13 @@ if [[ "$WITH_HOST" == true ]]; then
|
|
|
174
214
|
echo
|
|
175
215
|
if command -v npm >/dev/null 2>&1; then
|
|
176
216
|
echo "Uninstalling Pi host:"
|
|
177
|
-
|
|
217
|
+
HOST_PREFIX="$(npm_prefix_override)"
|
|
218
|
+
if [[ -n "$HOST_PREFIX" ]]; then
|
|
219
|
+
echo " (prefix $HOST_PREFIX, where this helper is installed)"
|
|
220
|
+
run_cmd npm uninstall -g --prefix "$HOST_PREFIX" "$PI_HOST_PACKAGE"
|
|
221
|
+
else
|
|
222
|
+
run_cmd npm uninstall -g "$PI_HOST_PACKAGE"
|
|
223
|
+
fi
|
|
178
224
|
else
|
|
179
225
|
echo "WARN: npm is not on PATH; cannot uninstall $PI_HOST_PACKAGE." >&2
|
|
180
226
|
fi
|
|
@@ -231,7 +277,12 @@ echo
|
|
|
231
277
|
if [[ "$APPLY" == true ]]; then
|
|
232
278
|
echo "Uninstall complete."
|
|
233
279
|
echo "The npm-global helper is separate and stays until you remove it:"
|
|
234
|
-
|
|
280
|
+
HELPER_PREFIX="$(npm_prefix_override)"
|
|
281
|
+
if [[ -n "$HELPER_PREFIX" ]]; then
|
|
282
|
+
echo " npm uninstall -g --prefix $HELPER_PREFIX $PLATFORM_PACKAGE"
|
|
283
|
+
else
|
|
284
|
+
echo " npm uninstall -g $PLATFORM_PACKAGE"
|
|
285
|
+
fi
|
|
235
286
|
else
|
|
236
287
|
echo "Dry run only. Nothing was removed. Re-run with --apply to perform it."
|
|
237
288
|
fi
|
|
@@ -213,8 +213,21 @@ function binForPrefix(prefix) {
|
|
|
213
213
|
return process.platform === "win32" ? prefix : path.join(prefix, "bin");
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
// The npx cache is a `node_modules` tree like any other, so the documented
|
|
217
|
+
// bootstrap -- `npm exec -y --package @piagent/platform@X.Y.Z -- piagent-update`
|
|
218
|
+
// -- ran the helper from `~/.npm/_npx/<hash>/node_modules/@piagent/platform` and
|
|
219
|
+
// this function happily reported that hash directory as the prefix to install
|
|
220
|
+
// into. Host and helper then landed in a cache npm is free to prune, with a `bin`
|
|
221
|
+
// that is on nobody's PATH: the command reported success and the machine had
|
|
222
|
+
// nothing installed. A cache is never an install target, so it is refused here
|
|
223
|
+
// and the caller falls back to a real global prefix.
|
|
224
|
+
function isNpxCacheRoot(root) {
|
|
225
|
+
return root.split(path.sep).some((segment) => segment === "_npx" || segment === "_cacache");
|
|
226
|
+
}
|
|
227
|
+
|
|
216
228
|
function npmPrefixForInstalledHelper(root) {
|
|
217
229
|
if (path.basename(root) !== "platform") return undefined;
|
|
230
|
+
if (isNpxCacheRoot(root)) return undefined;
|
|
218
231
|
const scopeRoot = path.dirname(root);
|
|
219
232
|
if (path.basename(scopeRoot) !== "@piagent") return undefined;
|
|
220
233
|
const nodeModulesRoot = path.dirname(scopeRoot);
|
|
@@ -444,10 +457,29 @@ function main() {
|
|
|
444
457
|
console.log(` npm: using explicit global prefix ${npmTarget.prefix}`);
|
|
445
458
|
}
|
|
446
459
|
|
|
460
|
+
// The host version above was read before this prepend, so it described whatever
|
|
461
|
+
// `pi` the caller's PATH happened to resolve. Once the prefix moves -- a default
|
|
462
|
+
// global root that is not writable, an explicit `--npm-prefix`, or a helper
|
|
463
|
+
// already installed elsewhere -- every step below uses a different `pi`, and
|
|
464
|
+
// "(already current)" could be describing a binary this run never touches. The
|
|
465
|
+
// decision belongs to the `pi` that will actually be used, so it is taken again
|
|
466
|
+
// here rather than trusted from before the move.
|
|
467
|
+
let effectiveHost = currentHost;
|
|
468
|
+
let effectiveHostNeedsChange = hostNeedsChange;
|
|
469
|
+
if (npmTarget.bin) {
|
|
470
|
+
const activeHost = installedHostVersion();
|
|
471
|
+
if (activeHost !== currentHost) {
|
|
472
|
+
effectiveHost = activeHost;
|
|
473
|
+
effectiveHostNeedsChange = options.host && (options.force || activeHost !== targetHost);
|
|
474
|
+
console.log(` host: the active prefix resolves pi ${activeHost ?? "not installed"}, not ${currentHost ?? "not installed"};`
|
|
475
|
+
+ ` using ${effectiveHost ?? "not installed"} -> ${targetHost}${effectiveHostNeedsChange ? "" : " (already current)"}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
447
479
|
// Host first: piagent-install refuses to run against a host that does not match
|
|
448
480
|
// the version its package.json pins, so a helper installed ahead of the host
|
|
449
481
|
// would fail on its very next step.
|
|
450
|
-
if (
|
|
482
|
+
if (effectiveHostNeedsChange) {
|
|
451
483
|
const args = npmInstallArgs(`${HOST_PACKAGE}@${targetHost}`, npmTarget);
|
|
452
484
|
describeStep(options.dryRun, "npm", args);
|
|
453
485
|
if (!options.dryRun && !run("npm", args).ok) fail(`could not install ${HOST_PACKAGE}@${targetHost}`);
|
package/scripts/verify-local.sh
CHANGED
|
@@ -173,6 +173,7 @@ required_files=(
|
|
|
173
173
|
"$ROOT/scripts/build-docs-site.mjs"
|
|
174
174
|
"$ROOT/scripts/check-architecture.mjs"
|
|
175
175
|
"$ROOT/scripts/check-doc-languages.mjs"
|
|
176
|
+
"$ROOT/scripts/verify-translation-pairing.mjs"
|
|
176
177
|
"$ROOT/scripts/check-third-party-neutrality.mjs"
|
|
177
178
|
"$ROOT/governance/piagent-webui/wui5-18-acceptance-matrix.v1.json"
|
|
178
179
|
"$ROOT/tests/piagent-webui-wui5-18-gate.test.mjs"
|
|
@@ -205,6 +206,7 @@ required_files=(
|
|
|
205
206
|
"$ROOT/schemas/piagent-webui/queue-v1.schema.json"
|
|
206
207
|
"$ROOT/schemas/piagent-webui/model-catalog-v1.schema.json"
|
|
207
208
|
"$ROOT/schemas/piagent-webui/attachment-v1.schema.json"
|
|
209
|
+
"$ROOT/schemas/piagent-webui/document-workspace-v1.schema.json"
|
|
208
210
|
"$ROOT/schemas/piagent-webui/control-command-v1.schema.json"
|
|
209
211
|
"$ROOT/schemas/piagent-webui/approval-v1.schema.json"
|
|
210
212
|
"$ROOT/schemas/piagent-webui/capabilities-v1.schema.json"
|
|
@@ -231,6 +233,7 @@ required_files=(
|
|
|
231
233
|
"$ROOT/packages/piagent-webui/contracts/generated/queue-v1.ts"
|
|
232
234
|
"$ROOT/packages/piagent-webui/contracts/generated/model-catalog-v1.ts"
|
|
233
235
|
"$ROOT/packages/piagent-webui/contracts/generated/attachment-v1.ts"
|
|
236
|
+
"$ROOT/packages/piagent-webui/contracts/generated/document-workspace-v1.ts"
|
|
234
237
|
"$ROOT/packages/piagent-webui/contracts/generated/session-catalog-v1.ts"
|
|
235
238
|
"$ROOT/packages/piagent-webui/contracts/generated/session-command-v1.ts"
|
|
236
239
|
"$ROOT/packages/piagent-webui/contracts/generated/gateway-capabilities-v1.ts"
|
|
@@ -248,6 +251,9 @@ required_files=(
|
|
|
248
251
|
"$ROOT/packages/piagent-webui/client/src/evidence-view-model.ts"
|
|
249
252
|
"$ROOT/packages/piagent-webui/client/src/EvidencePanels.tsx"
|
|
250
253
|
"$ROOT/packages/piagent-webui/client/src/ChatPanel.tsx"
|
|
254
|
+
"$ROOT/packages/piagent-webui/client/src/MarkdownMessage.tsx"
|
|
255
|
+
"$ROOT/packages/piagent-webui/client/src/DocumentWorkspace.tsx"
|
|
256
|
+
"$ROOT/packages/piagent-webui/client/src/attachment-intake.ts"
|
|
251
257
|
"$ROOT/packages/piagent-webui/client/src/chat-view-model.ts"
|
|
252
258
|
"$ROOT/packages/piagent-webui/client/src/chat-command.ts"
|
|
253
259
|
"$ROOT/packages/piagent-webui/client/src/review-command.ts"
|
|
@@ -291,6 +297,8 @@ required_files=(
|
|
|
291
297
|
"$ROOT/packages/piagent-webui/gateway/mcp-auth-broker.ts"
|
|
292
298
|
"$ROOT/tests/piagent-webui-mcp-auth.test.mjs"
|
|
293
299
|
"$ROOT/packages/piagent-webui/gateway/session-catalog.ts"
|
|
300
|
+
"$ROOT/packages/piagent-webui/gateway/ai-capability-inspection.ts"
|
|
301
|
+
"$ROOT/packages/piagent-webui/gateway/session-attachment-registry.ts"
|
|
294
302
|
"$ROOT/packages/piagent-webui/gateway/session-command-controller.ts"
|
|
295
303
|
"$ROOT/packages/piagent-webui/gateway/session-command-store.ts"
|
|
296
304
|
"$ROOT/packages/piagent-webui/gateway/session-lease-store.ts"
|
|
@@ -304,6 +312,11 @@ required_files=(
|
|
|
304
312
|
"$ROOT/packages/piagent-webui/extension/source-open-controller.ts"
|
|
305
313
|
"$ROOT/packages/piagent-webui/extension/vscode-handoff.ts"
|
|
306
314
|
"$ROOT/packages/piagent-core/runtime/inspection/review-state-contract.ts"
|
|
315
|
+
"$ROOT/packages/piagent-core/runtime/inspection/document-workspace-projection.ts"
|
|
316
|
+
"$ROOT/packages/piagent-core/runtime/inspection/session-identity.ts"
|
|
317
|
+
"$ROOT/packages/piagent-core/runtime/input/attachment-reservation.ts"
|
|
318
|
+
"$ROOT/packages/piagent-core/runtime/input/attachment-document-command.ts"
|
|
319
|
+
"$ROOT/packages/piagent-core/runtime/input/attachment-store.ts"
|
|
307
320
|
"$ROOT/packages/piagent-core/runtime/inspection/mcp-control.ts"
|
|
308
321
|
"$ROOT/packages/piagent-core/runtime/inspection/review-state-projection.ts"
|
|
309
322
|
"$ROOT/packages/piagent-core/runtime/inspection/review-state-store.ts"
|
|
@@ -346,7 +359,6 @@ required_files=(
|
|
|
346
359
|
"$ROOT/packages/piagent-webui/extension/held-message-queue.ts"
|
|
347
360
|
"$ROOT/packages/piagent-webui/extension/session-options-controller.ts"
|
|
348
361
|
"$ROOT/packages/piagent-webui/extension/session-stream-adapter.ts"
|
|
349
|
-
"$ROOT/packages/piagent-webui/extension/attachment-store.ts"
|
|
350
362
|
"$ROOT/packages/piagent-webui/extension/lifecycle-controller.ts"
|
|
351
363
|
"$ROOT/packages/piagent-webui/extension/lifecycle-event-adapter.ts"
|
|
352
364
|
"$ROOT/packages/piagent-core/runtime/inspection/approval-broker.ts"
|
|
@@ -359,6 +371,8 @@ required_files=(
|
|
|
359
371
|
"$ROOT/tests/piagent-webui-held-message-queue.test.mjs"
|
|
360
372
|
"$ROOT/tests/piagent-webui-session-options.test.mjs"
|
|
361
373
|
"$ROOT/tests/piagent-webui-attachment-store.test.mjs"
|
|
374
|
+
"$ROOT/tests/piagent-webui-document-workspace.test.mjs"
|
|
375
|
+
"$ROOT/tests/helpers/piagent-docx-fixture.mjs"
|
|
362
376
|
"$ROOT/tests/piagent-webui-approval-broker.test.mjs"
|
|
363
377
|
"$ROOT/tests/piagent-webui-lifecycle-control.test.mjs"
|
|
364
378
|
"$ROOT/tests/piagent-webui-lifecycle-client.test.mjs"
|
|
@@ -570,6 +584,8 @@ const jsonFiles = [
|
|
|
570
584
|
"schemas/piagent-webui/transcript-v1.schema.json",
|
|
571
585
|
"schemas/piagent-webui/queue-v1.schema.json",
|
|
572
586
|
"schemas/piagent-webui/model-catalog-v1.schema.json",
|
|
587
|
+
"schemas/piagent-webui/attachment-v1.schema.json",
|
|
588
|
+
"schemas/piagent-webui/document-workspace-v1.schema.json",
|
|
573
589
|
"schemas/piagent-webui/control-command-v1.schema.json",
|
|
574
590
|
"schemas/piagent-webui/approval-v1.schema.json",
|
|
575
591
|
"schemas/piagent-webui/capabilities-v1.schema.json",
|
|
@@ -914,6 +930,9 @@ node --check "$ROOT/scripts/build-docs-site.mjs" >/dev/null
|
|
|
914
930
|
node --check "$ROOT/scripts/preview-docs-site.mjs" >/dev/null
|
|
915
931
|
node "$ROOT/scripts/check-architecture.mjs" >/dev/null
|
|
916
932
|
node "$ROOT/scripts/check-doc-languages.mjs" >/dev/null
|
|
933
|
+
# check-doc-languages proves the peer exists. This proves neither side moved
|
|
934
|
+
# without the other, which is the way a translation actually goes stale.
|
|
935
|
+
node "$ROOT/scripts/verify-translation-pairing.mjs" >/dev/null
|
|
917
936
|
node "$ROOT/scripts/check-third-party-neutrality.mjs" >/dev/null
|
|
918
937
|
run_quietly "npm run build --workspace @piagent/webui" npm run build --workspace @piagent/webui
|
|
919
938
|
run_quietly "npm test" npm test
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Fails when one side of a bilingual pair has moved and the other has not.
|
|
3
|
+
//
|
|
4
|
+
// The existing language gate proves the Vietnamese peer exists. Existing is not
|
|
5
|
+
// the same as saying the same thing: editing docs/en/architecture.md and
|
|
6
|
+
// forgetting docs/vi/architecture.md passes every check in this repository,
|
|
7
|
+
// and the reader of the stale side has no way to know.
|
|
8
|
+
//
|
|
9
|
+
// So each pair carries the content digest of both sides as of the last time
|
|
10
|
+
// somebody confirmed them consistent. A digest that no longer matches means the
|
|
11
|
+
// record is stale, and the fix is to bring the other side along and re-record.
|
|
12
|
+
//
|
|
13
|
+
// What this cannot do is read the two documents and decide they agree. It is a
|
|
14
|
+
// record that a human looked, nothing more -- which is worth exactly as much as
|
|
15
|
+
// the honesty of whoever runs --write.
|
|
16
|
+
|
|
17
|
+
import { createHash } from "node:crypto";
|
|
18
|
+
import fs from "node:fs";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
const repositoryRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
23
|
+
|
|
24
|
+
export function contentDigest(text) {
|
|
25
|
+
// Newline-normalised so a checkout on another platform does not read as drift.
|
|
26
|
+
return createHash("sha256").update(text.replace(/\r\n/g, "\n"), "utf8").digest("hex").slice(0, 40);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function digestForFile(root, relative) {
|
|
30
|
+
const file = path.join(root, relative);
|
|
31
|
+
if (!fs.existsSync(file)) return null;
|
|
32
|
+
return contentDigest(fs.readFileSync(file, "utf8"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {{root?: string, manifest?: object}} options
|
|
37
|
+
* @returns {{ok: boolean, checked: number, unrecorded: string[], drifted: object[], stale: object[], missing: string[]}}
|
|
38
|
+
*/
|
|
39
|
+
export function inspectPairing({ root = repositoryRoot, manifest } = {}) {
|
|
40
|
+
const languages = manifest
|
|
41
|
+
?? JSON.parse(fs.readFileSync(path.join(root, "docs", "languages.json"), "utf8"));
|
|
42
|
+
const result = { ok: true, checked: 0, unrecorded: [], drifted: [], stale: [], missing: [] };
|
|
43
|
+
|
|
44
|
+
for (const pair of languages.pairs ?? []) {
|
|
45
|
+
const current = { en: digestForFile(root, pair.en), vi: digestForFile(root, pair.vi) };
|
|
46
|
+
for (const side of ["en", "vi"]) {
|
|
47
|
+
if (current[side] === null) result.missing.push(`${pair.topic}: ${pair[side]} does not exist`);
|
|
48
|
+
}
|
|
49
|
+
if (current.en === null || current.vi === null) { result.ok = false; continue; }
|
|
50
|
+
|
|
51
|
+
result.checked += 1;
|
|
52
|
+
const recorded = pair.consistentAt;
|
|
53
|
+
if (!recorded) { result.unrecorded.push(pair.topic); result.ok = false; continue; }
|
|
54
|
+
|
|
55
|
+
const movedSides = ["en", "vi"].filter((side) => recorded[side] !== current[side]);
|
|
56
|
+
if (movedSides.length === 0) continue;
|
|
57
|
+
result.ok = false;
|
|
58
|
+
// One side moving alone is the failure this exists for: the other side is
|
|
59
|
+
// now describing something that changed. Both moving is likely a paired
|
|
60
|
+
// edit, but nothing here can prove it, so it still needs re-recording.
|
|
61
|
+
const entry = { topic: pair.topic, moved: movedSides, en: pair.en, vi: pair.vi };
|
|
62
|
+
if (movedSides.length === 1) result.drifted.push(entry);
|
|
63
|
+
else result.stale.push(entry);
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function record(root) {
|
|
69
|
+
const file = path.join(root, "docs", "languages.json");
|
|
70
|
+
const languages = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
71
|
+
for (const pair of languages.pairs ?? []) {
|
|
72
|
+
const en = digestForFile(root, pair.en);
|
|
73
|
+
const vi = digestForFile(root, pair.vi);
|
|
74
|
+
if (en === null || vi === null) continue;
|
|
75
|
+
pair.consistentAt = { en, vi };
|
|
76
|
+
}
|
|
77
|
+
fs.writeFileSync(file, `${JSON.stringify(languages, null, 2)}\n`);
|
|
78
|
+
return languages.pairs.length;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const invokedDirectly = process.argv[1]
|
|
82
|
+
&& path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname);
|
|
83
|
+
|
|
84
|
+
if (invokedDirectly) {
|
|
85
|
+
const write = process.argv.includes("--write");
|
|
86
|
+
if (process.argv.includes("-h") || process.argv.includes("--help")) {
|
|
87
|
+
console.log(`Usage:
|
|
88
|
+
node scripts/verify-translation-pairing.mjs [--write]
|
|
89
|
+
|
|
90
|
+
Checks that neither side of a bilingual documentation pair has moved since the
|
|
91
|
+
two were last confirmed consistent. --write re-records the current state, which
|
|
92
|
+
is a claim that you brought both sides into line.`);
|
|
93
|
+
process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
if (write) {
|
|
96
|
+
console.log(`Recorded ${record(repositoryRoot)} pairs as consistent.`);
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const result = inspectPairing();
|
|
101
|
+
if (result.ok) {
|
|
102
|
+
console.log(`PASS: ${result.checked} bilingual pairs match their recorded state`);
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
for (const line of result.missing) console.error(`FAIL: ${line}`);
|
|
106
|
+
for (const topic of result.unrecorded) {
|
|
107
|
+
console.error(`FAIL: ${topic} has no recorded consistent state.`);
|
|
108
|
+
}
|
|
109
|
+
for (const entry of result.drifted) {
|
|
110
|
+
const moved = entry.moved[0];
|
|
111
|
+
const other = moved === "en" ? "vi" : "en";
|
|
112
|
+
console.error(`FAIL: ${entry.topic}: ${entry[moved]} changed and ${entry[other]} did not.`);
|
|
113
|
+
console.error(` Bring ${entry[other]} along, then re-record.`);
|
|
114
|
+
}
|
|
115
|
+
for (const entry of result.stale) {
|
|
116
|
+
console.error(`FAIL: ${entry.topic}: both sides changed, so the record no longer says anything.`);
|
|
117
|
+
console.error(` Confirm they still agree, then re-record.`);
|
|
118
|
+
}
|
|
119
|
+
console.error("\nRe-record with: node scripts/verify-translation-pairing.mjs --write");
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|