gentle-pi 2.6.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/contracts/telemetry/runtime-aggregate-v1.schema.json +8 -6
- package/extensions/gentle-agents.ts +0 -4
- package/extensions/runtime-metrics.ts +10 -19
- package/lib/native-review-cli.ts +5 -0
- package/lib/runtime-metrics-children.ts +3 -5
- package/lib/runtime-metrics-native.ts +17 -9
- package/lib/runtime-metrics.ts +95 -42
- package/package.json +1 -1
- package/runtime/native-review-cli.mjs +5 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/types-baseline.json +1 -5
- package/scripts/verify-package-files.mjs +3 -3
- package/tests/fixtures/runtime-metrics-native-batches.json +1 -1
- package/tests/gentle-agents.test.ts +0 -6
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-installer.test.ts +47 -47
- package/tests/native-review-capability-contract.test.ts +12 -1
- package/tests/package-manifest.test.ts +6 -6
- package/tests/runtime-metrics-children.test.ts +4 -3
- package/tests/runtime-metrics-extension.test.ts +40 -31
- package/tests/runtime-metrics-model.test.ts +76 -0
- package/tests/runtime-metrics-native.test.ts +73 -7
- package/tests/runtime-metrics.test.ts +22 -19
- package/lib/runtime-metrics-pi-identity.ts +0 -113
- package/tests/runtime-metrics-pi-identity.test.ts +0 -113
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ Most coding-agent sessions fail for operational reasons, not model reasons:
|
|
|
85
85
|
| **Skill creation workflow** | Provides the `gentle-ai-skill-creator`/`gentle-ai-skill-improver` skills, `/skill-creation` prompt, and packaged style guide for LLM-first skills. |
|
|
86
86
|
| **Delivery skills** | Includes issue-first PRs, chained PRs, work-unit commits, cognitive docs, comment writing, and Judgment Day review. |
|
|
87
87
|
| **Bounded native review** | Freezes one candidate, dispatches only controller-selected lenses, and records native authority. Review outcomes are informational; delivery follows ordinary repository policy. |
|
|
88
|
-
| **Verified native runtime** | Provisions the exact package-local Gentle AI v2.8.
|
|
88
|
+
| **Verified native runtime** | Provisions the exact package-local Gentle AI v2.8.1 runtime: signed, SHA-256-pinned release archives on Darwin/Linux and a Go SumDB-verified source build on Windows x64/arm64. It validates package-local integrity and rejects PATH, global, sibling, symlink, and mode fallbacks. |
|
|
89
89
|
| **Runtime safety** | Blocks destructive shell commands, asks for confirmation for sensitive operations, and blocks direct read/write/edit access to sensitive paths. |
|
|
90
90
|
|
|
91
91
|
## Native pointer regions
|
|
@@ -138,7 +138,7 @@ pi install npm:gentle-pi@0.14.0
|
|
|
138
138
|
pi install npm:gentle-pi@latest
|
|
139
139
|
```
|
|
140
140
|
|
|
141
|
-
The latest RDD package installs Gentle AI only into its private `.gentle-ai/` directory. Darwin and Linux use pinned release assets with asset and executable SHA-256 verification (signed archives for stable pins such as the current v2.8.
|
|
141
|
+
The latest RDD package installs Gentle AI only into its private `.gentle-ai/` directory. Darwin and Linux use pinned release assets with asset and executable SHA-256 verification (signed archives for stable pins such as the current v2.8.1; raw prerelease binaries only under a prerelease pin). Windows x64 and arm64 build the exact `v2.8.1` source tag with a local Go 1.25.10+ toolchain, a sealed Go environment, `GOTOOLCHAIN=local`, and `GOSUMDB=sum.golang.org`; it does not download Go automatically. Windows provenance is Go-toolchain plus SumDB evidence and postinstall tamper detection, **not** Authenticode or protection against a malicious joint binary-and-manifest replacement. Package-private locks coordinate cooperative concurrent or crashed installers; their tombstones fail closed. A malicious same-user process with write access to package-private `node_modules` is outside that protocol because it can already replace package code, binary, or manifest, and portable Node has no pathname-delete CAS. It never uses `PATH` or a global `gentle-ai` installation. For development or offline installs only, set `GENTLE_PI_SKIP_GENTLE_AI_INSTALL=1`; native review operations then fail closed with an actionable `package-local-binary-missing` error. To recover explicitly, if `GENTLE_PI_SKIP_GENTLE_AI_INSTALL` is set, remove or unset it before changing to the installed `gentle-pi` package directory. Then run `node scripts/install-gentle-ai.mjs`. This invokes the package-owned installer without relying on a global binary or npm configuration change. A missing binary can result from skipped lifecycle scripts, but does not prove that lifecycle scripts were disabled.
|
|
142
142
|
|
|
143
143
|
Recommended companion packages:
|
|
144
144
|
|
|
@@ -294,13 +294,13 @@ flowchart TD
|
|
|
294
294
|
|
|
295
295
|
VALIDATE is informational. Commit, push, PR, and release commands follow ordinary repository policy; RDD never authorizes, rewrites, consumes review state for, or blocks them. Dangerous-command safety and destructive-review consent remain independent.
|
|
296
296
|
|
|
297
|
-
Native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v2.8.
|
|
297
|
+
Native contract pairing is exact: this adapter resolves only the integrity-verified package-local Gentle AI v2.8.1 executable, independently hashes it, then negotiates `gentle-ai.review-integration/v2` outside the repository. Capabilities are cached by that executable digest. Every START, target status, FINALIZE, validate, and BIND-SDD request passes the same contract identifier. Negotiated envelopes decode exactly against the vendored schemas; `recover` routes only the provider-selected `action_disposition`, and optional additions require a future compatible schema/minor that the provider explicitly advertises and the consumer negotiates.
|
|
298
298
|
|
|
299
299
|
Contract `/v2` replaces the Base64 `candidate_diff` reviewer transport of `/v1` with immutable `base_tree`/`candidate_tree` plus an ordered `changed_path_manifest` and never an inline patch. `gentle-pi` negotiates `/v2` only, with no dual-lane fallback; the cutover landed as one atomic commit against gentle-ai v2.2.2 (tracked by the `migrate-review-integration-v2` change), and the `/v1` schemas stay packaged because the `/v2` schemas `$ref` into their fragments. This provider contract version is unrelated to Pi's own internal "compact-v2" review-authority naming used below — the shared digit is coincidental, not a version pairing.
|
|
300
300
|
|
|
301
301
|
Target status owns `current_target`, `unrelated`, `ambiguous`, and `corrupted` applicability and returns one native action. Pi does not reconstruct ordinary authority from provider-private files or choose a lineage from repository-wide history. Restart recovery rebuilds only the derived candidate view from the native Git/content projection, including intended-untracked paths, symlinks, and immutable gitlink identities. Native failure envelopes retain their exact mutation outcome, replayability, required inputs, request digest, and next action. After an unknown or lost mutating result, Pi calls target status before any replay decision and returns only the provider-declared action.
|
|
302
302
|
|
|
303
|
-
Once the pinned gentle-ai runtime (currently v2.8.
|
|
303
|
+
Once the pinned gentle-ai runtime (currently v2.8.1) has written review authority, rollback MUST preserve every native store and receipt and MUST NOT run a downgraded binary against that repository. Disable the Pi route or roll forward to a compatible authority-aware release instead; deleting authority data or reinstalling an older binary is not a rollback path.
|
|
304
304
|
|
|
305
305
|
### FINALIZE wrapper input
|
|
306
306
|
|
|
@@ -34,13 +34,15 @@
|
|
|
34
34
|
"effort": {"enum": ["off", "minimal", "low", "medium", "high", "xhigh", "max", "not_selected", "unknown", "custom", "unavailable", "unsupported"]},
|
|
35
35
|
"model": {
|
|
36
36
|
"type": "object", "additionalProperties": false, "required": ["provider", "id"],
|
|
37
|
-
"properties": {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
37
|
+
"properties": {
|
|
38
|
+
"provider": {"type": "string", "maxLength": 32},
|
|
39
|
+
"id": {"type": "string", "maxLength": 64}
|
|
40
|
+
},
|
|
41
|
+
"anyOf": [
|
|
42
|
+
{"properties": {"provider": {"pattern": "^[a-z0-9][a-z0-9-]{0,31}$"}, "id": {"pattern": "^(claude|gpt|o[1-9]|codex|gemini|gemma|deepseek|glm|qwen|qwq|kimi|moonshot|llama|codellama|mistral|mixtral|codestral|devstral|magistral|ministral|minimax|grok|phi|nemotron|jamba|hunyuan|doubao|ernie|mimo|granite|olmo|smollm|starcoder|titan)[a-z0-9]*([-_.:][a-z0-9]+){0,8}$"}}},
|
|
42
43
|
{"properties": {"provider": {"const": "unknown"}, "id": {"const": "unknown"}}},
|
|
43
|
-
{"properties": {"provider": {"const": "custom"}, "id": {"const": "custom"}}}
|
|
44
|
+
{"properties": {"provider": {"const": "custom"}, "id": {"const": "custom"}}},
|
|
45
|
+
{"properties": {"provider": {"const": "opencode"}, "id": {"const": "custom"}}}
|
|
44
46
|
]
|
|
45
47
|
},
|
|
46
48
|
"row": {
|
|
@@ -30,7 +30,6 @@ import { openInExternalEditor } from "./gentle-shell.ts";
|
|
|
30
30
|
import { resolveGentlePiAgentHome } from "../lib/agent-home.ts";
|
|
31
31
|
import { assertResearchCheckpoint, parseResearchPersistence, RESEARCH_PERSISTENCE_ENTRY, canonicalArtifactPath, researchAgent, renderResearchCapabilities, RESEARCH_CHILD_TOOLS_ENV, RESEARCH_SELECTION_ENV, RESEARCH_ARTIFACT_ENV, parseResearchArtifactIntent, researchArtifactCall, researchArtifactReadback, type ResearchArtifactIntent, type ResearchWriteIdentity } from "../lib/sdd-research-capabilities.ts";
|
|
32
32
|
import { CHILD_METRICS_EVENT, CHILD_METRICS_REVOKED, childEvent, launchSelection, type LaunchSelection } from "../lib/runtime-metrics-children.ts";
|
|
33
|
-
import { lookupPiCatalogName } from "../lib/runtime-metrics-pi-identity.ts";
|
|
34
33
|
import { runtimeMetricsEnvAllows, type RuntimeMetricsPolicyDeps } from "../lib/runtime-metrics-policy.ts";
|
|
35
34
|
|
|
36
35
|
// Gentle Agents: subagents as isolated `pi --mode rpc` children, a task
|
|
@@ -244,7 +243,6 @@ export interface AgentsDeps extends RunnerDeps {
|
|
|
244
243
|
runtimeMetricsPolicy?: RuntimeMetricsPolicyDeps;
|
|
245
244
|
metricsNow?: () => number;
|
|
246
245
|
metricsSchedule?: RunnerDeps["schedule"];
|
|
247
|
-
lookupPiCatalogName?: typeof lookupPiCatalogName;
|
|
248
246
|
}
|
|
249
247
|
|
|
250
248
|
export function agentRuntimePaths(home: string, agentHome = join(home, ".pi", "agent")): { sessions: string; transcripts: string } {
|
|
@@ -633,7 +631,6 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
633
631
|
const stoppingTaskIds = new Set<string>();
|
|
634
632
|
const yieldedTaskIds = new Set<string>();
|
|
635
633
|
const metricsNow = deps.metricsNow ?? (() => performance.now());
|
|
636
|
-
const catalogLookup = deps.lookupPiCatalogName ?? lookupPiCatalogName;
|
|
637
634
|
let metricsOwner = {};
|
|
638
635
|
const metricTasks = new Map<string, { selection?: LaunchSelection; started: number; launched: boolean; finished: boolean; current(): boolean; valid(): boolean }>();
|
|
639
636
|
const unsubscribeMetrics = pi.events.on(CHILD_METRICS_REVOKED, id => {
|
|
@@ -1121,7 +1118,6 @@ export default function gentleAgents(pi: ExtensionAPI, env: NodeJS.ProcessEnv =
|
|
|
1121
1118
|
onLaunch: () => { metrics.launched = true; request.onLaunch?.(); },
|
|
1122
1119
|
...(observe ? { canCollectResponseObservations: metrics.valid, prepareResponseObservations: async () => {
|
|
1123
1120
|
if (metrics.finished || owner !== metricsOwner || request.parentSessionId !== activeSessionId() || !runtimeMetricsEnvAllows(deps.env)) return false;
|
|
1124
|
-
void catalogLookup({ provider: "openai", modelId: "gpt-4o" }).catch(() => {});
|
|
1125
1121
|
if (!metrics.valid()) return false;
|
|
1126
1122
|
metrics.selection = launchSelection(request.agent, request.model, request.thinking);
|
|
1127
1123
|
metrics.started = metricsNow();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { EFFORTS, ORCHESTRATOR_AGENT_CLASS, RuntimeMetrics, UNKNOWN_AGENT_CLASS, type FinalResponse, type TokenMeasurement } from "../lib/runtime-metrics.ts";
|
|
3
|
-
import { classifyPiCatalogName, lookupPiCatalogName } from "../lib/runtime-metrics-pi-identity.ts";
|
|
4
3
|
import { CHILD_METRICS_EVENT, CHILD_METRICS_REVOKED, snapshotChildEvent, type ChildLaunchBucket } from "../lib/runtime-metrics-children.ts";
|
|
5
4
|
import { RuntimeMetricsAttempt } from "../lib/runtime-metrics-delivery.ts";
|
|
6
5
|
import { sendNativeRuntimeEvent, type NativeRuntimeTransportDeps } from "../lib/runtime-metrics-native.ts";
|
|
@@ -12,10 +11,8 @@ import { runtimeMetricsEnvAllows } from "../lib/runtime-metrics-policy.ts";
|
|
|
12
11
|
* Pi hooks lack request correlation: latency and SDK-zero presence are unknown.
|
|
13
12
|
*/
|
|
14
13
|
export default function runtimeMetrics(pi: ExtensionAPI, env = process.env,
|
|
15
|
-
{
|
|
16
|
-
|
|
17
|
-
{ lookup?: typeof lookupPiCatalogName; classify?: typeof classifyPiCatalogName; native?: NativeRuntimeTransportDeps;
|
|
18
|
-
send?: typeof sendNativeRuntimeEvent; now?: () => number; shutdownWaitMs?: number } = {}): void {
|
|
14
|
+
{ native, send = sendNativeRuntimeEvent, now = () => performance.now(), shutdownWaitMs = 1500 }:
|
|
15
|
+
{ native?: NativeRuntimeTransportDeps; send?: typeof sendNativeRuntimeEvent; now?: () => number; shutdownWaitMs?: number } = {}): void {
|
|
19
16
|
const allows = () => env.GENTLE_PI_AGENTS_CHILD !== "1" && runtimeMetricsEnvAllows(env);
|
|
20
17
|
if (!allows()) return;
|
|
21
18
|
type Selection = Pick<FinalResponse, "selectedModelId" | "selectedProvider" | "effort">;
|
|
@@ -24,7 +21,7 @@ export default function runtimeMetrics(pi: ExtensionAPI, env = process.env,
|
|
|
24
21
|
let requestSeen = false;
|
|
25
22
|
let ambiguous = false;
|
|
26
23
|
let live: { id: string; started: number; ctx: ExtensionContext; attempt: RuntimeMetricsAttempt;
|
|
27
|
-
seen: WeakSet<object>; children: Set<string
|
|
24
|
+
seen: WeakSet<object>; children: Set<string> } | undefined;
|
|
28
25
|
const missing = { state: "unavailable" } as const;
|
|
29
26
|
function invalidate() { selection = undefined; ambiguous = true; }
|
|
30
27
|
function dispose() { live?.attempt.dispose(); live = undefined; active = false; invalidate(); }
|
|
@@ -32,15 +29,10 @@ export default function runtimeMetrics(pi: ExtensionAPI, env = process.env,
|
|
|
32
29
|
if (!allows() || live?.id !== ctx.sessionManager.getSessionId()) dispose();
|
|
33
30
|
return live;
|
|
34
31
|
}
|
|
35
|
-
function refreshCatalog(owner: NonNullable<typeof live>, input: { provider: unknown; modelId: unknown }) {
|
|
36
|
-
void lookup(input).then(result => {
|
|
37
|
-
if (live === owner && result.classification === "catalog_public") owner.catalog = true;
|
|
38
|
-
}, () => { /* A later classification callback may retry the bounded load. */ });
|
|
39
|
-
}
|
|
40
32
|
function submit(owner: NonNullable<typeof live>, responses: FinalResponse[], launches?: ChildLaunchBucket[]) {
|
|
41
33
|
if (!responses.length || live !== owner || !current(owner.ctx)) return;
|
|
42
34
|
// Ephemeral event-local accounting only; source IDs never enter these rows.
|
|
43
|
-
const metrics = new RuntimeMetrics(
|
|
35
|
+
const metrics = new RuntimeMetrics();
|
|
44
36
|
for (const [index, row] of responses.entries()) metrics.record({ ...row, responseId: String(index) });
|
|
45
37
|
const rows = metrics.snapshot();
|
|
46
38
|
if (!rows.length) return;
|
|
@@ -71,10 +63,8 @@ export default function runtimeMetrics(pi: ExtensionAPI, env = process.env,
|
|
|
71
63
|
pi.on("session_start", (_event, ctx) => {
|
|
72
64
|
dispose();
|
|
73
65
|
if (!allows()) return;
|
|
74
|
-
|
|
75
|
-
attempt: new RuntimeMetricsAttempt(), seen: new WeakSet<object>(), children: new Set<string>()
|
|
76
|
-
live = owner;
|
|
77
|
-
refreshCatalog(owner, { provider: "openai", modelId: "gpt-4o" });
|
|
66
|
+
live = { id: ctx.sessionManager.getSessionId(), started: now(), ctx,
|
|
67
|
+
attempt: new RuntimeMetricsAttempt(), seen: new WeakSet<object>(), children: new Set<string>() };
|
|
78
68
|
});
|
|
79
69
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
80
70
|
const teardown = () => { dispose(); offChild(); offRevoke(); };
|
|
@@ -95,8 +85,10 @@ export default function runtimeMetrics(pi: ExtensionAPI, env = process.env,
|
|
|
95
85
|
if (!active || ambiguous) return;
|
|
96
86
|
let effort: FinalResponse["effort"] = "unavailable";
|
|
97
87
|
try { const value = pi.getThinkingLevel(); if (EFFORTS.includes(value)) effort = value; } catch { /* No evidence. */ }
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
// No catalog gate: the selection is taken as-is (bounded to 128 chars) and
|
|
89
|
+
// the schema-driven normalizer decides what actually leaves the machine.
|
|
90
|
+
selection = { selectedProvider: typeof ctx.model?.provider === "string" && ctx.model.provider.length <= 128 ? ctx.model.provider : undefined,
|
|
91
|
+
selectedModelId: typeof ctx.model?.id === "string" && ctx.model.id.length <= 128 ? ctx.model.id : undefined, effort };
|
|
100
92
|
});
|
|
101
93
|
function token(value: unknown): TokenMeasurement {
|
|
102
94
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= 1_000_000_000
|
|
@@ -109,7 +101,6 @@ export default function runtimeMetrics(pi: ExtensionAPI, env = process.env,
|
|
|
109
101
|
if (!owner || message.role !== "assistant" || message.stopReason === "pending" || message.stopReason === "deferred"
|
|
110
102
|
|| owner.seen.has(message)) return;
|
|
111
103
|
owner.seen.add(message);
|
|
112
|
-
refreshCatalog(owner, { provider: "openai", modelId: "gpt-4o" });
|
|
113
104
|
const selected = active && !ambiguous ? selection : undefined;
|
|
114
105
|
submit(owner, [{ kind: "final_assistant_response", responseId: "0",
|
|
115
106
|
selectedProvider: selected?.selectedProvider ?? "unknown", selectedModelId: selected?.selectedModelId,
|
package/lib/native-review-cli.ts
CHANGED
|
@@ -966,6 +966,11 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
966
966
|
// so this row repeats 2.7.0. riskEvidence and hint remain dark because
|
|
967
967
|
// neither is proven to reach the negotiated START path Pi consumes.
|
|
968
968
|
"2.8.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
969
|
+
// v2.8.1 only changed runtime telemetry model attribution (gentle-ai#4536);
|
|
970
|
+
// the closed fields Pi consumes did not change between 2.8.0 and 2.8.1, so
|
|
971
|
+
// this row repeats 2.8.0 exactly. riskEvidence and hint remain dark
|
|
972
|
+
// because neither is proven to reach the negotiated START path Pi consumes.
|
|
973
|
+
"2.8.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
969
974
|
});
|
|
970
975
|
|
|
971
976
|
export interface NativeReviewProcessDiagnostics {
|
|
@@ -2,14 +2,12 @@ import { readFileSync, readdirSync } from "node:fs";
|
|
|
2
2
|
import { parseAgentDefinition, type AgentDefinition, type ModelRef } from "./agents-config.ts";
|
|
3
3
|
import type { ChildObservationSnapshot } from "./agents-runner.ts";
|
|
4
4
|
import { FINISHED_STATUSES, type TaskStatus } from "./agents-protocol.ts";
|
|
5
|
-
import { classifyRuntimeModelId, EFFORTS, parseAgentClass, RuntimeMetrics, UNKNOWN_AGENT_CLASS, validRuntimeResponse, type AgentClass, type FinalResponse, type RuntimeMetricBucket } from "./runtime-metrics.ts";
|
|
6
|
-
import { classifyPiCatalogName } from "./runtime-metrics-pi-identity.ts";
|
|
5
|
+
import { classifyRuntimeModelId, EFFORTS, normalizeRuntimeProvider, parseAgentClass, RuntimeMetrics, UNKNOWN_AGENT_CLASS, validRuntimeResponse, type AgentClass, type FinalResponse, type RuntimeMetricBucket } from "./runtime-metrics.ts";
|
|
7
6
|
export const CHILD_METRICS_EVENT = "gentle:runtime-metrics:child/v1";
|
|
8
7
|
// Local revocation notification invalidates active observations. Contains only
|
|
9
8
|
// the local session join, never policy output.
|
|
10
9
|
export const CHILD_METRICS_REVOKED = "gentle:runtime-metrics:revoked/v1";
|
|
11
10
|
const missing = { state: "unavailable" } as const;
|
|
12
|
-
const providers = ["anthropic", "openai", "openai-codex", "google", "google-vertex", "amazon-bedrock", "openrouter", "custom", "unknown"];
|
|
13
11
|
const tokenFields = ["input", "output", "cacheRead", "cacheWrite", "reasoning", "totalTokens"] as const;
|
|
14
12
|
/** Recognize only names from this package's fixed assets and the transport's
|
|
15
13
|
* closed agent_class enum. Customized packaged agents retain their schema name;
|
|
@@ -44,11 +42,11 @@ export function classifyBuiltinAgent(agent: AgentDefinition): AgentClass {
|
|
|
44
42
|
} catch { return UNKNOWN_AGENT_CLASS; }
|
|
45
43
|
}
|
|
46
44
|
function provider(value: unknown): FinalResponse["provider"] {
|
|
47
|
-
return
|
|
45
|
+
return normalizeRuntimeProvider(value);
|
|
48
46
|
}
|
|
49
47
|
function modelId(namespace: unknown, value: unknown): string {
|
|
50
48
|
if (value === "unknown" || value === undefined) return "unknown";
|
|
51
|
-
return classifyRuntimeModelId(namespace, value
|
|
49
|
+
return classifyRuntimeModelId(namespace, value);
|
|
52
50
|
}
|
|
53
51
|
function effort(value: unknown): FinalResponse["effort"] {
|
|
54
52
|
return EFFORTS.includes(value as FinalResponse["effort"]) ? value as FinalResponse["effort"] : "unavailable";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn, type ChildProcessWithoutNullStreams, type SpawnOptionsWithoutStdio } from "node:child_process";
|
|
2
2
|
import { resolveGentleAiBinary } from "./gentle-ai-binary.ts";
|
|
3
3
|
import { runtimeMetricsEnvAllows } from "./runtime-metrics-policy.ts";
|
|
4
|
-
import type
|
|
4
|
+
import { isPublicRuntimeModelId, normalizeRuntimeModel, type RuntimeMetricBucket } from "./runtime-metrics.ts";
|
|
5
5
|
import type { ChildLaunchBucket } from "./runtime-metrics-children.ts";
|
|
6
6
|
|
|
7
7
|
import schema from "../contracts/telemetry/runtime-aggregate-v1.schema.json" with { type: "json" };
|
|
@@ -10,11 +10,11 @@ export type NativeRuntimeResult = "stored" | "duplicate" | "discarded" | "disabl
|
|
|
10
10
|
export const NATIVE_SEND_ACK_SCHEMA = "gentle-ai.telemetry-runtime-send/v1";
|
|
11
11
|
const MAX_METRIC = schema.$defs.count.maximum;
|
|
12
12
|
const count = (value: number) => Number.isSafeInteger(value) && value >= 0 && value <= MAX_METRIC;
|
|
13
|
+
// Re-derives through the same schema-driven normalizer used at record time so
|
|
14
|
+
// an already-classified (provider, id) pair is re-validated against the
|
|
15
|
+
// transport's anyOf shape before it ever leaves the machine.
|
|
13
16
|
function publicModel(provider: string, id: string) {
|
|
14
|
-
|
|
15
|
-
if (schema.$defs.model.oneOf.some(({ properties }) => matches(properties.provider, provider) && matches(properties.id, id))) return { provider, id };
|
|
16
|
-
const kind = !id || id === "unknown" ? "unknown" : "custom";
|
|
17
|
-
return { provider: kind, id: kind };
|
|
17
|
+
return normalizeRuntimeModel(provider, id);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
/** Closed field projection of one event, not a session snapshot. Oversized or
|
|
@@ -34,14 +34,22 @@ export function encodeNativeRuntimeEvent(source: readonly RuntimeMetricBucket[],
|
|
|
34
34
|
};
|
|
35
35
|
const agentClass = schema.$defs.row.properties.agent_class.enum.includes(row.agentClass) ? row.agentClass : "unknown";
|
|
36
36
|
const effort = (value: string) => schema.$defs.effort.enum.includes(value) ? value : "unavailable";
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
// Evidence order: an actually-dispatched response model wins; otherwise
|
|
38
|
+
// the caller's own selection; otherwise the SDK-observed model the
|
|
39
|
+
// selection never captured (still labeled "selected" evidence, since
|
|
40
|
+
// the transport has no separate "observed" category). Never response
|
|
41
|
+
// evidence unless the response model itself normalizes to a public id.
|
|
42
|
+
const response = isPublicRuntimeModelId(row.responseModelId);
|
|
43
|
+
const selected = !response && isPublicRuntimeModelId(row.selectedModelId);
|
|
44
|
+
const observed = !response && !selected && isPublicRuntimeModelId(row.observedModelId);
|
|
39
45
|
const model = response ? publicModel(row.provider, row.responseModelId)
|
|
40
|
-
: selected ? publicModel(row.selectedProvider, row.selectedModelId)
|
|
46
|
+
: selected ? publicModel(row.selectedProvider, row.selectedModelId)
|
|
47
|
+
: observed ? publicModel(row.provider, row.observedModelId)
|
|
48
|
+
: { provider: "unknown", id: "unknown" };
|
|
41
49
|
const time = row.fullResponseMs;
|
|
42
50
|
if (!count(time.measured) || !Number.isFinite(time.sum) || time.sum < 0 || time.sum > MAX_METRIC
|
|
43
51
|
|| (!time.measured && time.sum !== 0)) throw new Error("Invalid duration");
|
|
44
|
-
return { model, model_evidence: response ? "response" : selected ? "selected" : "unknown",
|
|
52
|
+
return { model, model_evidence: response ? "response" : (selected || observed) ? "selected" : "unknown",
|
|
45
53
|
agent_kind: agentClass === "orchestrator" || agentClass === "unknown" ? agentClass : "built_in", agent_class: agentClass,
|
|
46
54
|
selected_effort: effort(row.effort), effective_effort: effort(row.providerThinkingLevel),
|
|
47
55
|
launches: null, responses: row.responses,
|
package/lib/runtime-metrics.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import { classifyPiCatalogName } from "./runtime-metrics-pi-identity.ts";
|
|
3
2
|
|
|
4
3
|
const runtimeSchema = JSON.parse(readFileSync(new URL("../contracts/telemetry/runtime-aggregate-v1.schema.json", import.meta.url), "utf8"));
|
|
5
4
|
|
|
@@ -7,7 +6,6 @@ const runtimeSchema = JSON.parse(readFileSync(new URL("../contracts/telemetry/ru
|
|
|
7
6
|
// Callers supply finalized assistant responses and authoritative classifications.
|
|
8
7
|
// Never infer executor, usage availability, or measured timings from SDK defaults.
|
|
9
8
|
const EXECUTORS = ["orchestrator", "worker", "reviewer", "unknown"] as const;
|
|
10
|
-
const PROVIDERS = ["anthropic", "openai", "openai-codex", "google", "google-vertex", "amazon-bedrock", "openrouter", "custom", "unknown"] as const;
|
|
11
9
|
// Stable families, not model IDs/versions: new models need no catalog update.
|
|
12
10
|
// Callers map known native metadata to families; private aliases stay custom.
|
|
13
11
|
const FAMILIES = ["claude", "gpt", "o-series", "gemini", "llama", "qwen", "deepseek", "kimi", "custom", "unknown"] as const;
|
|
@@ -42,38 +40,93 @@ function requiredAgentClass(value: string): AgentClass {
|
|
|
42
40
|
export const UNKNOWN_AGENT_CLASS = requiredAgentClass("unknown");
|
|
43
41
|
export const ORCHESTRATOR_AGENT_CLASS = requiredAgentClass("orchestrator");
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
throw new Error("Invalid runtime telemetry model schema");
|
|
56
|
-
}
|
|
57
|
-
return candidates as string[];
|
|
58
|
-
};
|
|
59
|
-
const models = new Set<string>();
|
|
60
|
-
for (const rule of rules) for (const provider of values(rule, "provider")) {
|
|
61
|
-
for (const id of values(rule, "id")) models.add(JSON.stringify([provider, id]));
|
|
43
|
+
interface ModelFieldRule { pattern: RegExp; maxLength: number }
|
|
44
|
+
|
|
45
|
+
function modelFieldRule(field: "provider" | "id"): ModelFieldRule {
|
|
46
|
+
// The length cap lives on $defs.model.properties.<field>; the shape pattern
|
|
47
|
+
// lives only on the first (public-pattern) branch of $defs.model.anyOf, next
|
|
48
|
+
// to the unknown/custom/opencode sentinel branches. Both are mirrored,
|
|
49
|
+
// byte-for-byte, from the Gentle AI transport schema.
|
|
50
|
+
const property: unknown = runtimeSchema?.$defs?.model?.properties?.[field];
|
|
51
|
+
if (!object(property) || typeof property.maxLength !== "number") {
|
|
52
|
+
throw new Error(`Invalid runtime telemetry model ${field} schema`);
|
|
62
53
|
}
|
|
63
|
-
|
|
54
|
+
const branches: unknown = runtimeSchema?.$defs?.model?.anyOf;
|
|
55
|
+
const patternProperty = Array.isArray(branches) && object(branches[0]) && object(branches[0].properties)
|
|
56
|
+
? branches[0].properties[field] : undefined;
|
|
57
|
+
if (!object(patternProperty) || typeof patternProperty.pattern !== "string") {
|
|
58
|
+
throw new Error(`Invalid runtime telemetry model ${field} schema`);
|
|
59
|
+
}
|
|
60
|
+
return { pattern: new RegExp(patternProperty.pattern), maxLength: property.maxLength };
|
|
64
61
|
}
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
// Schema-driven, not a hardcoded TypeScript regex: the mirrored transport
|
|
64
|
+
// contract owns the open family-pattern rules for provider/id shape. A
|
|
65
|
+
// companion Gentle AI change keeps the Go side on the same patterns.
|
|
66
|
+
const MODEL_PROVIDER_RULE = modelFieldRule("provider");
|
|
67
|
+
const MODEL_ID_RULE = modelFieldRule("id");
|
|
67
68
|
|
|
68
|
-
/**
|
|
69
|
-
*
|
|
70
|
-
*
|
|
69
|
+
/** Provider-only normalization, independent of any specific model id: trims,
|
|
70
|
+
* lowercases, and keeps the slug only when it matches the schema provider
|
|
71
|
+
* pattern within its maxLength. Non-string/empty input is "unknown"; a
|
|
72
|
+
* non-conforming non-empty string is "custom". Shared by the standalone
|
|
73
|
+
* provider dimension and by normalizeRuntimeModel's own provider handling.
|
|
71
74
|
*/
|
|
72
|
-
export function
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
export function normalizeRuntimeProvider(value: unknown): string {
|
|
76
|
+
if (typeof value !== "string") return "unknown";
|
|
77
|
+
const trimmed = value.trim();
|
|
78
|
+
if (!trimmed) return "unknown";
|
|
79
|
+
const lower = trimmed.toLowerCase();
|
|
80
|
+
return lower.length <= MODEL_PROVIDER_RULE.maxLength && MODEL_PROVIDER_RULE.pattern.test(lower) ? lower : "custom";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Generic, schema-driven family-pattern normalizer for a (provider, id)
|
|
84
|
+
* selection or response pair (gentle-pi#968 / gentle-ai#4536). Open-weight
|
|
85
|
+
* models on arbitrary providers are reported by name; private aliases and
|
|
86
|
+
* fine-tunes stay custom. Rules, identical to the mirrored Go/schema side:
|
|
87
|
+
* - a non-string or empty provider or id fails closed to unknown/unknown;
|
|
88
|
+
* - id: trim, keep the LAST "/"-separated segment, lowercase; public only
|
|
89
|
+
* when it matches the schema id pattern within its maxLength, otherwise
|
|
90
|
+
* "custom";
|
|
91
|
+
* - provider: trim, lowercase; kept when it matches the schema provider
|
|
92
|
+
* pattern, otherwise "custom";
|
|
93
|
+
* - when the id is not public the provider becomes "custom" too, except
|
|
94
|
+
* "opencode", which stays opencode/custom;
|
|
95
|
+
* - the literal unknown/unknown and custom/custom pairs pass through.
|
|
96
|
+
*/
|
|
97
|
+
export function normalizeRuntimeModel(provider: unknown, id: unknown): { provider: string; id: string } {
|
|
98
|
+
if (typeof provider !== "string" || typeof id !== "string") return { provider: "unknown", id: "unknown" };
|
|
99
|
+
const providerTrimmed = provider.trim();
|
|
100
|
+
const idTrimmed = id.trim();
|
|
101
|
+
if (!providerTrimmed || !idTrimmed) return { provider: "unknown", id: "unknown" };
|
|
102
|
+
const providerLower = providerTrimmed.toLowerCase();
|
|
103
|
+
const idSegment = idTrimmed.split("/").pop() ?? "";
|
|
104
|
+
const idLower = idSegment.toLowerCase();
|
|
105
|
+
if (providerLower === "unknown" && idLower === "unknown") return { provider: "unknown", id: "unknown" };
|
|
106
|
+
if (providerLower === "custom" && idLower === "custom") return { provider: "custom", id: "custom" };
|
|
107
|
+
const idPublic = idLower.length <= MODEL_ID_RULE.maxLength && MODEL_ID_RULE.pattern.test(idLower);
|
|
108
|
+
const id_ = idPublic ? idLower : "custom";
|
|
109
|
+
let providerResult = normalizeRuntimeProvider(providerTrimmed);
|
|
110
|
+
if (!idPublic) providerResult = providerResult === "opencode" ? "opencode" : "custom";
|
|
111
|
+
return { provider: providerResult, id: id_ };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Thin wrapper over normalizeRuntimeModel for callers that only track the
|
|
115
|
+
* id dimension (the provider is used only to decide the closed unknown/empty
|
|
116
|
+
* fast path; a valid non-empty provider string never changes the id result).
|
|
117
|
+
*/
|
|
118
|
+
export function classifyRuntimeModelId(provider: unknown, modelId: unknown): string {
|
|
119
|
+
return normalizeRuntimeModel(provider, modelId).id;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** True only for an id already in its normalized public form: it matches the
|
|
123
|
+
* schema id pattern within its maxLength (which, by construction, also
|
|
124
|
+
* excludes the "unknown"/"custom" sentinels, since neither matches any
|
|
125
|
+
* recognized family prefix). Used to pick the strongest available evidence
|
|
126
|
+
* tier among already-classified id dimensions (response/selected/observed).
|
|
127
|
+
*/
|
|
128
|
+
export function isPublicRuntimeModelId(value: unknown): value is string {
|
|
129
|
+
return typeof value === "string" && value.length > 0 && value.length <= MODEL_ID_RULE.maxLength && MODEL_ID_RULE.pattern.test(value);
|
|
77
130
|
}
|
|
78
131
|
|
|
79
132
|
type Missing = { state: "unavailable" | "unsupported" };
|
|
@@ -84,20 +137,23 @@ export interface FinalResponse {
|
|
|
84
137
|
/** Local dedupe only: 1..128 UTF-16 code units; never exported. */
|
|
85
138
|
responseId: string;
|
|
86
139
|
/** Caller-observed selected SDK model ID, never dispatched/response identity.
|
|
87
|
-
*
|
|
140
|
+
* Normalized through normalizeRuntimeModel at record time; only an id
|
|
141
|
+
* matching the schema family pattern survives as a public name.
|
|
88
142
|
*/
|
|
89
143
|
selectedModelId?: string;
|
|
90
144
|
/** Selection namespace, independent from observed response provider.
|
|
91
145
|
* Omission preserves legacy same-provider callers; adapters must pass it explicitly.
|
|
92
146
|
*/
|
|
93
|
-
selectedProvider?:
|
|
147
|
+
selectedProvider?: string;
|
|
94
148
|
agentClass?: AgentClass;
|
|
95
|
-
/**
|
|
149
|
+
/** SDK-observed model id from response metadata, never endpoint proof.
|
|
150
|
+
* Normalized like selectedModelId; only a schema family match survives.
|
|
151
|
+
*/
|
|
96
152
|
observedModelId?: string;
|
|
97
153
|
responseModelId?: string;
|
|
98
154
|
providerThinkingLevel?: typeof EFFORTS[number];
|
|
99
155
|
executor: typeof EXECUTORS[number];
|
|
100
|
-
provider:
|
|
156
|
+
provider: string;
|
|
101
157
|
modelFamily: typeof FAMILIES[number];
|
|
102
158
|
effort: typeof EFFORTS[number];
|
|
103
159
|
error: typeof ERRORS[number];
|
|
@@ -195,17 +251,14 @@ export class RuntimeMetrics {
|
|
|
195
251
|
#buckets = new Map<string, RuntimeMetricBucket>();
|
|
196
252
|
#maxResponses: number;
|
|
197
253
|
#maxBuckets: number;
|
|
198
|
-
#classifyModel: typeof classifyPiCatalogName;
|
|
199
254
|
|
|
200
|
-
constructor({ maxResponses = 1024, maxBuckets = 64
|
|
201
|
-
{ maxResponses?: number; maxBuckets?: number; classifyModel?: typeof classifyPiCatalogName } = {}) {
|
|
255
|
+
constructor({ maxResponses = 1024, maxBuckets = 64 }: { maxResponses?: number; maxBuckets?: number } = {}) {
|
|
202
256
|
if (!Number.isInteger(maxResponses) || maxResponses < 1 || maxResponses > 1024
|
|
203
257
|
|| !Number.isInteger(maxBuckets) || maxBuckets < 1 || maxBuckets > 64) {
|
|
204
258
|
throw new RangeError("Invalid runtime metrics capacity");
|
|
205
259
|
}
|
|
206
260
|
this.#maxResponses = maxResponses;
|
|
207
261
|
this.#maxBuckets = maxBuckets;
|
|
208
|
-
this.#classifyModel = classifyModel;
|
|
209
262
|
}
|
|
210
263
|
|
|
211
264
|
record(response: FinalResponse): "recorded" | "duplicate" | "invalid" | "capacity" {
|
|
@@ -215,13 +268,13 @@ export class RuntimeMetrics {
|
|
|
215
268
|
const dimensions = {
|
|
216
269
|
hostAgent: "pi" as const,
|
|
217
270
|
agentClass: category(AGENT_CLASSES, response.agentClass, UNKNOWN_AGENT_CLASS),
|
|
218
|
-
observedModelId: classifyRuntimeModelId(response.provider, response.observedModelId
|
|
219
|
-
responseModelId: classifyRuntimeModelId(response.provider, response.responseModelId
|
|
271
|
+
observedModelId: classifyRuntimeModelId(response.provider, response.observedModelId),
|
|
272
|
+
responseModelId: classifyRuntimeModelId(response.provider, response.responseModelId),
|
|
220
273
|
providerThinkingLevel: category(EFFORTS, response.providerThinkingLevel, "unavailable"),
|
|
221
|
-
selectedModelId: classifyRuntimeModelId(selectedProvider, response.selectedModelId
|
|
222
|
-
selectedProvider:
|
|
274
|
+
selectedModelId: classifyRuntimeModelId(selectedProvider, response.selectedModelId),
|
|
275
|
+
selectedProvider: normalizeRuntimeProvider(selectedProvider),
|
|
223
276
|
executor: category(EXECUTORS, response.executor, "unknown"),
|
|
224
|
-
provider:
|
|
277
|
+
provider: normalizeRuntimeProvider(response.provider),
|
|
225
278
|
modelFamily: category(FAMILIES, response.modelFamily, typeof response.modelFamily === "string" && response.modelFamily ? "custom" : "unknown"),
|
|
226
279
|
effort: response.effort,
|
|
227
280
|
error: response.error,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gentle-pi",
|
|
3
|
-
"version": "2.6.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -967,6 +967,11 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
967
967
|
// so this row repeats 2.7.0. riskEvidence and hint remain dark because
|
|
968
968
|
// neither is proven to reach the negotiated START path Pi consumes.
|
|
969
969
|
"2.8.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
970
|
+
// v2.8.1 only changed runtime telemetry model attribution (gentle-ai#4536);
|
|
971
|
+
// the closed fields Pi consumes did not change between 2.8.0 and 2.8.1, so
|
|
972
|
+
// this row repeats 2.8.0 exactly. riskEvidence and hint remain dark
|
|
973
|
+
// because neither is proven to reach the negotiated START path Pi consumes.
|
|
974
|
+
"2.8.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
970
975
|
});
|
|
971
976
|
|
|
972
977
|
|
|
@@ -36,7 +36,7 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows";
|
|
|
36
36
|
// version check below) derives from this constant instead of repeating the
|
|
37
37
|
// literal, so a pin bump cannot leave a stale copy behind. See
|
|
38
38
|
// scripts/install-gentle-ai.mjs for the incident that motivated this.
|
|
39
|
-
export const INSTALLER_VERSION = "2.8.
|
|
39
|
+
export const INSTALLER_VERSION = "2.8.1";
|
|
40
40
|
export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`;
|
|
41
41
|
export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
42
42
|
SIGNED_RELEASE_ASSET: "signed-release-asset",
|
|
@@ -45,10 +45,10 @@ export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
|
45
45
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v2/cmd/gentle-ai";
|
|
46
46
|
export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v2";
|
|
47
47
|
export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`;
|
|
48
|
-
// `go mod download -json github.com/gentleman-programming/gentle-ai/v2@v2.8.
|
|
48
|
+
// `go mod download -json github.com/gentleman-programming/gentle-ai/v2@v2.8.1`
|
|
49
49
|
// with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the
|
|
50
|
-
// tag resolves to commit
|
|
51
|
-
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:
|
|
50
|
+
// tag resolves to commit 7aad6a51, the published v2.8.1 release head.
|
|
51
|
+
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:EixFRy7P3JLVi1ovr18pMxQIrtjDib9i7VEH9g4XRVE=";
|
|
52
52
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`;
|
|
53
53
|
export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10";
|
|
54
54
|
export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE";
|
|
@@ -67,7 +67,7 @@ export class GentleAiInstallerError extends Error {
|
|
|
67
67
|
// Sentinel used while a re-pinned gentle-ai release is not yet published. A
|
|
68
68
|
// sentinel digest can never match a real SHA-256, so installation fails closed,
|
|
69
69
|
// and verify-package-files.mjs refuses to pack/publish while any digest below
|
|
70
|
-
// still holds it. The v2.8.
|
|
70
|
+
// still holds it. The v2.8.1 digests are pinned from the published release:
|
|
71
71
|
// archive sha256 values verified against the minisign-signed checksums.txt and
|
|
72
72
|
// freshly computed hashes; binary sha256 values computed from the extracted
|
|
73
73
|
// executables.
|
|
@@ -109,15 +109,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// Windows is absent from signed release archives on purpose. gentle-ai stopped
|
|
112
|
-
// distributing unsigned Windows builds in c4b764d0, so v2.8.
|
|
112
|
+
// distributing unsigned Windows builds in c4b764d0, so v2.8.1 publishes signed
|
|
113
113
|
// Darwin/Linux archives only. Windows x64/arm64 uses the separately verified
|
|
114
114
|
// exact-tag Go SumDB source-build path below; restore archive rows only when
|
|
115
115
|
// upstream ships signed Windows assets.
|
|
116
116
|
export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
|
|
117
|
-
"darwin/amd64": asset("gentle-ai_2.8.
|
|
118
|
-
"darwin/arm64": asset("gentle-ai_2.8.
|
|
119
|
-
"linux/amd64": asset("gentle-ai_2.8.
|
|
120
|
-
"linux/arm64": asset("gentle-ai_2.8.
|
|
117
|
+
"darwin/amd64": asset("gentle-ai_2.8.1_darwin_amd64.tar.gz", "cbfba54a72ec0a28ce5e42c3057a03cc3e809c28b1ff38d080ef42a242027897", "77784169c58b7f84d21c2df538031de2a9582cc1ea43242103d9487f3278c85f", "gentle-ai"),
|
|
118
|
+
"darwin/arm64": asset("gentle-ai_2.8.1_darwin_arm64.tar.gz", "a5aca61d4e98fed0e0a8d1f2d6e1ae5d48746bd6baa541c91f629ade25f47839", "1c4b63db9aba22f02b6cf53b4b61cb750c2a7d8bf429c5aae82993227ca1e613", "gentle-ai"),
|
|
119
|
+
"linux/amd64": asset("gentle-ai_2.8.1_linux_amd64.tar.gz", "609190fa9e8ef1896a8b4fcbb5a44e1ad5ac64aae1d17eb0459735c5bbef199b", "c11a6d627a81741d00abc7bc9071522e9279de6f95219d810469a448615e618e", "gentle-ai"),
|
|
120
|
+
"linux/arm64": asset("gentle-ai_2.8.1_linux_arm64.tar.gz", "919232dcf7ba1b0cd228f808166146cce8f2ea5d691a59bda9cd42a294432ea4", "f4eb76ea6f466ba991e530b7fc6dce57b39da273d8b3013570edeb17dfe6908d", "gentle-ai"),
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
// A pinned asset is either a signed archive or, for a prerelease pin only,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"note": "Recorded TypeScript diagnostics. Regenerate with `node scripts/check-types.mjs --update` after fixing or deliberately accepting diagnostics. This file may only shrink without a review decision.",
|
|
3
|
-
"total":
|
|
3
|
+
"total": 200,
|
|
4
4
|
"byFileAndCode": {
|
|
5
5
|
"extensions/codegraph-tools.ts TS2339": 1,
|
|
6
6
|
"extensions/codegraph-tools.ts TS2345": 1,
|
|
@@ -36,8 +36,6 @@
|
|
|
36
36
|
"tests/agents-runner-process.test.ts TS2322": 1,
|
|
37
37
|
"tests/devbinary/native-review-parity.devtest.ts TS2339": 1,
|
|
38
38
|
"tests/devbinary/pi-host-relay.devtest.ts TS2339": 3,
|
|
39
|
-
"tests/gentle-agents.test.ts TS2339": 1,
|
|
40
|
-
"tests/gentle-agents.test.ts TS2352": 2,
|
|
41
39
|
"tests/gentle-ai-binary.test.ts TS2339": 2,
|
|
42
40
|
"tests/gentle-ai-installer.test.ts TS2339": 7,
|
|
43
41
|
"tests/gentle-ai-installer.test.ts TS2556": 1,
|
|
@@ -87,8 +85,6 @@
|
|
|
87
85
|
"tests/runtime-metrics-extension.test.ts TS2345": 1,
|
|
88
86
|
"tests/sdd-preflight.test.ts TS2352": 1,
|
|
89
87
|
"tests/sdd-preflight.test.ts TS2554": 2,
|
|
90
|
-
"tests/sdd-research-capabilities.test.ts TS2339": 1,
|
|
91
|
-
"tests/sdd-research-capabilities.test.ts TS2698": 1,
|
|
92
88
|
"tests/sdd-research-live.test.ts TS2322": 1,
|
|
93
89
|
"tests/shell-sidebar-layout.test.ts TS2339": 1,
|
|
94
90
|
"tests/shell-sidebar-layout.test.ts TS2345": 1,
|