create-principles-disciple 1.144.12 → 1.144.14
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/_release/product-identity.json +1 -1
- package/codex-adapter/dist/pd-hook.js +23 -5
- package/codex-adapter/dist/worker/workspace-worker.d.ts +6 -0
- package/codex-adapter/dist/worker/workspace-worker.js +7 -0
- package/codex-adapter/package.json +1 -1
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.d.ts +9 -0
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +12 -0
- package/console/dist/ui/i18n/en.json +1 -0
- package/console/dist/ui/i18n/zh-CN.json +1 -0
- package/console/dist/ui/pages/focus/FocusPage.js +7 -4
- package/console/dist/ui/pages/focus/focus-validation.d.ts +12 -1
- package/console/dist/ui/pages/focus/focus-validation.js +29 -0
- package/console/dist/ui/utils/validators.d.ts +6 -0
- package/console/dist/ui/utils/validators.js +6 -0
- package/console/dist/web/assets/app.js +17 -17
- package/console/package.json +2 -2
- package/core/dist/runtime-v2/adapter/output-schema-registry.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/output-schema-registry.js +4 -0
- package/core/dist/runtime-v2/adapter/output-schema-registry.js.map +1 -1
- package/core/dist/runtime-v2/bridge-result-shaper.d.ts +8 -0
- package/core/dist/runtime-v2/bridge-result-shaper.d.ts.map +1 -1
- package/core/dist/runtime-v2/bridge-result-shaper.js +61 -3
- package/core/dist/runtime-v2/bridge-result-shaper.js.map +1 -1
- package/core/dist/runtime-v2/candidate-intake-service.d.ts +52 -2
- package/core/dist/runtime-v2/candidate-intake-service.d.ts.map +1 -1
- package/core/dist/runtime-v2/candidate-intake-service.js +201 -76
- package/core/dist/runtime-v2/candidate-intake-service.js.map +1 -1
- package/core/dist/runtime-v2/config/index.d.ts +2 -2
- package/core/dist/runtime-v2/config/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/index.js +1 -1
- package/core/dist/runtime-v2/config/index.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts +7 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-defaults.js +11 -0
- package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-effective.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-effective.js +24 -1
- package/core/dist/runtime-v2/config/pd-config-effective.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-redaction.js +2 -0
- package/core/dist/runtime-v2/config/pd-config-redaction.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.d.ts +39 -0
- package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.d.ts.map +1 -1
- package/core/dist/runtime-v2/config/pd-config-validate.js +44 -0
- package/core/dist/runtime-v2/config/pd-config-validate.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +8 -1
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +14 -1
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/pain-signal-bridge.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-bridge.js +93 -1
- package/core/dist/runtime-v2/pain-signal-bridge.js.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts +29 -14
- package/core/dist/runtime-v2/pain-signal-runtime-factory.d.ts.map +1 -1
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js +215 -46
- package/core/dist/runtime-v2/pain-signal-runtime-factory.js.map +1 -1
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-output.d.ts +40 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-output.d.ts.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-output.js +97 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-output.js.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-runner.d.ts +111 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-runner.d.ts.map +1 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-runner.js +232 -0
- package/core/dist/runtime-v2/principle-reuse/reuse-evaluation-runner.js.map +1 -0
- package/core/dist/telemetry-event.d.ts +2 -2
- package/core/dist/telemetry-event.d.ts.map +1 -1
- package/core/dist/telemetry-event.js +11 -0
- package/core/dist/telemetry-event.js.map +1 -1
- package/core/package.json +1 -1
- package/host-runtime/dist/index.d.ts +2 -0
- package/host-runtime/dist/index.js +1 -0
- package/host-runtime/dist/production-rulehost-gate.d.ts +1 -0
- package/host-runtime/dist/production-rulehost-gate.js +1 -1
- package/host-runtime/package.json +1 -1
- package/package.json +3 -3
- package/pd-cli/dist/commands/candidate.d.ts.map +1 -1
- package/pd-cli/dist/commands/candidate.js +170 -20
- package/pd-cli/dist/commands/candidate.js.map +1 -1
- package/pd-cli/dist/commands/config-doctor.d.ts.map +1 -1
- package/pd-cli/dist/commands/config-doctor.js +14 -0
- package/pd-cli/dist/commands/config-doctor.js.map +1 -1
- package/pd-cli/dist/commands/diagnose.d.ts.map +1 -1
- package/pd-cli/dist/commands/diagnose.js +67 -11
- package/pd-cli/dist/commands/diagnose.js.map +1 -1
- package/pd-cli/dist/commands/pain-retry.d.ts.map +1 -1
- package/pd-cli/dist/commands/pain-retry.js +80 -11
- package/pd-cli/dist/commands/pain-retry.js.map +1 -1
- package/pd-cli/dist/services/config-doctor.d.ts +11 -0
- package/pd-cli/dist/services/config-doctor.d.ts.map +1 -1
- package/pd-cli/dist/services/config-doctor.js +6 -0
- package/pd-cli/dist/services/config-doctor.js.map +1 -1
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.d.ts +14 -0
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.d.ts.map +1 -1
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.js +20 -4
- package/pd-cli/dist/services/resolve-runtime-from-pd-config.js.map +1 -1
- package/pd-cli/dist/services/runtime-adapter-resolver.d.ts +6 -0
- package/pd-cli/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
- package/pd-cli/dist/services/runtime-adapter-resolver.js +11 -2
- package/pd-cli/dist/services/runtime-adapter-resolver.js.map +1 -1
- package/pd-cli/package.json +1 -1
- package/plugin/dist/bundle.js +548 -544
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +4 -4
- package/release-manager/package.json +3 -3
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
4
5
|
import process from 'node:process';
|
|
5
6
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
@@ -195,11 +196,27 @@ export async function processHookInvocation(rawStdin, _env = process.env, cwd =
|
|
|
195
196
|
const nextAction = error instanceof CodexDecoderError ? error.nextAction : 'Inspect the Codex 0.147 hook payload.';
|
|
196
197
|
return { stdout: {}, exitCode: 0, stderr: [diagnostic(reason, nextAction)] };
|
|
197
198
|
}
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
// Workspace's hook into another Workspace's business state.
|
|
199
|
+
// Initializing the user workspace explicitly opts Codex into shared
|
|
200
|
+
// governance. Otherwise preserve the existing per-project resolution.
|
|
201
201
|
const requestedCwd = event.context.workspaceDir || cwd;
|
|
202
|
-
|
|
202
|
+
if (!path.isAbsolute(requestedCwd))
|
|
203
|
+
return { stdout: {}, exitCode: 0, stderr: [diagnostic('cwd_not_absolute', 'Provide the absolute Codex project directory.')] };
|
|
204
|
+
// Resolve like pd-locate.cjs does: a relative CODEX_HOME must yield the same
|
|
205
|
+
// absolute workspace for hooks and owner scripts, or resolveNearestPdWorkspace
|
|
206
|
+
// rejects it after the config check already accepted it.
|
|
207
|
+
const userWorkspace = path.join(path.resolve(_env.CODEX_HOME || path.join(os.homedir(), '.codex')), 'pd-workspace');
|
|
208
|
+
let userWorkspaceInitialized = false;
|
|
209
|
+
try {
|
|
210
|
+
userWorkspaceInitialized = statSync(path.join(userWorkspace, '.pd', 'config.yaml')).isFile();
|
|
211
|
+
if (!userWorkspaceInitialized)
|
|
212
|
+
return { stdout: {}, exitCode: 0, stderr: [diagnostic('user_workspace_config_invalid', 'Repair the user workspace .pd/config.yaml; project configuration was not used.')] };
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
if (!(error instanceof Error && Object.hasOwn(error, 'code') && Reflect.get(error, 'code') === 'ENOENT')) {
|
|
216
|
+
return { stdout: {}, exitCode: 0, stderr: [diagnostic('user_workspace_config_unreadable', 'Restore access to the user workspace .pd/config.yaml; project configuration was not used.')] };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const resolution = resolveNearestPdWorkspace(userWorkspaceInitialized ? userWorkspace : requestedCwd);
|
|
203
220
|
if (!resolution.ok)
|
|
204
221
|
return { stdout: {}, exitCode: 0, stderr: [diagnostic(resolution.reason, resolution.nextAction)] };
|
|
205
222
|
event = { ...event, context: { ...event.context, workspaceDir: resolution.workspaceDir } };
|
|
@@ -242,6 +259,7 @@ export async function processHookInvocation(rawStdin, _env = process.env, cwd =
|
|
|
242
259
|
// the Codex adapter stays independent of the OpenClaw plugin). The line
|
|
243
260
|
// writer appends synchronously — no flush/dispose needed for the subprocess.
|
|
244
261
|
const result = await createProductionHostRuntime({
|
|
262
|
+
projectDir: requestedCwd,
|
|
245
263
|
hostKind: 'codex',
|
|
246
264
|
toolSemantics: CODEX_TOOL_SEMANTICS,
|
|
247
265
|
events: codexEventEmitter(path.join(resolution.workspaceDir, '.state')),
|
|
@@ -13,6 +13,12 @@ export interface CodexWorkerCycleStepReport {
|
|
|
13
13
|
readonly errorCategory?: string;
|
|
14
14
|
/** PRI-638 P1-C: recovery action for capability-disabled pauses. */
|
|
15
15
|
readonly nextAction?: string;
|
|
16
|
+
/**
|
|
17
|
+
* PRI-917 v0.3.3 review fix (P2-2): candidates parked by the Reuse Review
|
|
18
|
+
* Gate (suspected duplicates awaiting the Owner). Forwarded per-candidate
|
|
19
|
+
* so the report shows WHAT awaits the Owner, not just a degraded status.
|
|
20
|
+
*/
|
|
21
|
+
readonly reuseReviewRequiredCandidateIds?: readonly string[];
|
|
16
22
|
} | null;
|
|
17
23
|
readonly downstream: InternalizationConsumerCycleOutcome | null;
|
|
18
24
|
}
|
|
@@ -156,11 +156,18 @@ export async function runCodexWorkspaceWorkerCycle(options) {
|
|
|
156
156
|
getEnvVar: (name) => process.env[name],
|
|
157
157
|
});
|
|
158
158
|
const executed = await bridge.executePendingDiagnosis({ taskId: candidate.taskId });
|
|
159
|
+
// PRI-917 v0.3.3 review fix (P2-2): forward the parked-candidate
|
|
160
|
+
// dispositions so the Owner sees the pending items instead of a bare
|
|
161
|
+
// degraded status with no subject.
|
|
162
|
+
const reuseReviewRequired = (executed.candidateOutcomes ?? [])
|
|
163
|
+
.filter((outcome) => outcome.reason === 'reuse_review_required')
|
|
164
|
+
.map((outcome) => outcome.candidateId);
|
|
159
165
|
diagnostician = {
|
|
160
166
|
taskId: candidate.taskId,
|
|
161
167
|
status: executed.status,
|
|
162
168
|
...(executed.message !== undefined ? { message: executed.message.slice(0, 200) } : {}),
|
|
163
169
|
...(executed.errorCategory !== undefined ? { errorCategory: executed.errorCategory } : {}),
|
|
170
|
+
...(reuseReviewRequired.length > 0 ? { reuseReviewRequiredCandidateIds: reuseReviewRequired } : {}),
|
|
164
171
|
};
|
|
165
172
|
// The bridge stays cached per workspace (bounded: one per workspace
|
|
166
173
|
// per worker process, exactly like the OpenClaw plugin host) — no
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/codex-adapter",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Codex CLI host adapter for Principles Disciple — implements HostAdapter interface for OpenAI Codex CLI's stdin/stdout JSON hook model (ADR-0020).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -15,6 +15,15 @@ export interface ApprovalGroup {
|
|
|
15
15
|
* (the client has no access to the escaped, serialized size).
|
|
16
16
|
*/
|
|
17
17
|
fitsPromptBudget?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* PRI-940: the pinned artifact no longer exists in pi_artifacts (typically
|
|
20
|
+
* superseded by a newer scribe revision). There is no contentJson, so
|
|
21
|
+
* `candidateDescription` is unavailable and `principleTitle` degrades to the
|
|
22
|
+
* synthesized `unlinked:<artifactId>` grouping key — a machine id that must
|
|
23
|
+
* never be rendered as a title. Absent when the artifact is readable. rc-9:
|
|
24
|
+
* the UI shows a visible degradation note instead of going silently blank.
|
|
25
|
+
*/
|
|
26
|
+
artifactUnavailable?: boolean;
|
|
18
27
|
records: {
|
|
19
28
|
id: string;
|
|
20
29
|
artifactId: string;
|
|
@@ -170,6 +170,10 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
170
170
|
// the pre-approval badge never promises rotation for an entry that is
|
|
171
171
|
// oversized (rotation cannot rescue a per-entry overflow).
|
|
172
172
|
const artifactFitsBudgetMap = new Map();
|
|
173
|
+
// PRI-940: which approval artifacts are MISSING from the store (superseded
|
|
174
|
+
// or pruned) — those cards have no contentJson, so the UI must degrade
|
|
175
|
+
// visibly instead of rendering the `unlinked:` machine id as a title.
|
|
176
|
+
const artifactUnavailableMap = new Map();
|
|
173
177
|
const stateDir = path.join(this.workspaceDir, '.state');
|
|
174
178
|
const resolutionDeps = createArtifactPrincipleResolutionDeps(conn, artifactStore, this.workspaceDir);
|
|
175
179
|
for (const approval of allApprovals) {
|
|
@@ -185,12 +189,14 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
185
189
|
artifactDescriptionMap.set(approval.artifactId, null);
|
|
186
190
|
}
|
|
187
191
|
artifactFitsBudgetMap.set(approval.artifactId, candidateFitsPromptBudget(artifact));
|
|
192
|
+
artifactUnavailableMap.set(approval.artifactId, artifact === null);
|
|
188
193
|
}
|
|
189
194
|
catch (err) {
|
|
190
195
|
if (isMissingTableError(err)) {
|
|
191
196
|
artifactPrincipleMap.set(approval.artifactId, null);
|
|
192
197
|
artifactDescriptionMap.set(approval.artifactId, null);
|
|
193
198
|
artifactFitsBudgetMap.set(approval.artifactId, false);
|
|
199
|
+
artifactUnavailableMap.set(approval.artifactId, true);
|
|
194
200
|
}
|
|
195
201
|
else {
|
|
196
202
|
throw err;
|
|
@@ -258,6 +264,12 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
258
264
|
...(firstArtifactId !== undefined && artifactFitsBudgetMap.has(firstArtifactId)
|
|
259
265
|
? { fitsPromptBudget: artifactFitsBudgetMap.get(firstArtifactId) === true }
|
|
260
266
|
: {}),
|
|
267
|
+
// PRI-940: absent when the pinned artifact is readable — same
|
|
268
|
+
// spread style as fitsPromptBudget so the wire contract stays
|
|
269
|
+
// unchanged for healthy cards.
|
|
270
|
+
...(firstArtifactId !== undefined && artifactUnavailableMap.get(firstArtifactId) === true
|
|
271
|
+
? { artifactUnavailable: true }
|
|
272
|
+
: {}),
|
|
261
273
|
records,
|
|
262
274
|
});
|
|
263
275
|
}
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
"deviationCount": "deviation(s)",
|
|
97
97
|
"evidenceSummary": "Evidence: {{count}} record(s)",
|
|
98
98
|
"untitledCandidate": "Untitled candidate principle",
|
|
99
|
+
"artifactUnavailableNote": "The draft artifact this approval points to is no longer in the artifact store (likely superseded by a newer revision), so its content cannot be shown. If in doubt, reject it; the raw artifact ID is shown in the edit section's current-artifact field.",
|
|
99
100
|
"approveAction": "Approve",
|
|
100
101
|
"rejectAction": "Reject",
|
|
101
102
|
"editAction": "Edit",
|
|
@@ -96,6 +96,7 @@
|
|
|
96
96
|
"deviationCount": "条偏差",
|
|
97
97
|
"evidenceSummary": "证据:{{count}} 条记录",
|
|
98
98
|
"untitledCandidate": "待命名候选原则",
|
|
99
|
+
"artifactUnavailableNote": "该审批指向的草稿原件已不在工件库中(可能已被新版本取代),内容无法显示。如无法判断,可选择拒绝;原始工件编号可点「编辑」后在「当前工件」一栏查看。",
|
|
99
100
|
"approveAction": "批准",
|
|
100
101
|
"rejectAction": "拒绝",
|
|
101
102
|
"editAction": "编辑",
|
|
@@ -9,7 +9,7 @@ import { SectionTitle } from "../../components/layout/section-title.js";
|
|
|
9
9
|
import { DailyThoughtCard } from "../../components/focus/daily-thought-card.js";
|
|
10
10
|
import { ShinyText } from "../../components/ui/shiny-text.js";
|
|
11
11
|
import { fetchGovernanceQueue, fetchApprovalsGrouped, approveApproval, rejectApproval, editApproval, fetchAllActivations, fetchOwnerDecisions, } from "../../api.js";
|
|
12
|
-
import { validateApprovalsGroupedData } from "./focus-validation.js";
|
|
12
|
+
import { selectApprovalGroupDisplayTitle, validateApprovalsGroupedData } from "./focus-validation.js";
|
|
13
13
|
import { OwnerDecisionCard } from "./OwnerDecisionCard.js";
|
|
14
14
|
import { localizeApprovalWarning, splitApprovalWarnings } from "../../utils/approval-warning-localization.js";
|
|
15
15
|
/** PRI-908: how long the decided card stays visible before the pending list refresh removes it. */
|
|
@@ -178,8 +178,11 @@ function PendingReviewCard({ group, actionsLockedReason, onDecisionApplied, prom
|
|
|
178
178
|
}
|
|
179
179
|
// Display title: prefer candidateDescription (human-readable) over principleTitle
|
|
180
180
|
// (which falls back to a fabricated principleId when the principle isn't in ledger).
|
|
181
|
-
|
|
182
|
-
|
|
181
|
+
// PRI-940: the selection rule lives in focus-validation (contract-tested) —
|
|
182
|
+
// when the pinned artifact is missing, principleTitle IS the machine id
|
|
183
|
+
// (`unlinked:<artifactId>`) and must fall through to the untitled copy; the
|
|
184
|
+
// degradation note below explains why there is no content.
|
|
185
|
+
const displayTitle = selectApprovalGroupDisplayTitle(group)
|
|
183
186
|
?? t("pages.focus.untitledCandidate", { defaultValue: "待命名候选原则" });
|
|
184
187
|
async function applyDecisionToAllRecords(action, reason) {
|
|
185
188
|
// Only process pending records — skip already-approved/rejected to avoid
|
|
@@ -306,7 +309,7 @@ function PendingReviewCard({ group, actionsLockedReason, onDecisionApplied, prom
|
|
|
306
309
|
? "border-green/40 bg-green/10 text-green"
|
|
307
310
|
: "border-line bg-surface/80 text-ink-3"}`, role: "status", "data-testid": `decided-banner-${group.principleId}`, children: decidedOutcome === "approved"
|
|
308
311
|
? t("pages.focus.decidedApproved", { defaultValue: "已批准 ✓ 正在写入生效记录…" })
|
|
309
|
-
: t("pages.focus.decidedRejected", { defaultValue: "已拒绝" }) })), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("span", { className: "inline-flex items-center border border-line rounded-[2px] px-[7px] py-1 font-mono text-[11px] text-ink-3 bg-surface/80 uppercase", children: channelLabel }), isReversible && (_jsx("span", { className: "inline-flex items-center border border-amber/35 text-amber rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: primaryChannel === "prompt" ? t("pages.focus.tagReversible") : t("pages.focus.tagLowRisk") }))] }), _jsx("div", { className: "mt-[14px] mb-2 font-semibold text-ink leading-snug", children: displayTitle }), _jsx("div", { className: "mt-2 px-3 py-2 bg-surface/60 border-l-2 border-gov text-ink-3 text-[13px] leading-snug", children: t("pages.focus.evidenceSummary", { count: group.records.length }) }), actionsLocked && (_jsxs("div", { className: "mt-3 rounded-[3px] border border-amber/40 bg-amber/5 p-3", "data-testid": `pending-actions-locked-${group.principleId}`, children: [_jsx("p", { className: "text-ink-2 text-[12.5px] leading-relaxed", children: actionsLockedReason }), _jsx(Link, { to: "/settings", "data-testid": `pending-actions-locked-settings-${group.principleId}`, className: "mt-2 inline-flex items-center border border-line text-ink bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors", children: t("pages.focus.ownerDecision.goSettingsCta") })] })), decidedOutcome === null && primaryChannel === "prompt" && promptInjection?.truncated === true && (_jsx("div", { className: "mt-3 rounded-[3px] border border-amber/40 bg-amber/5 px-3 py-2 text-[12.5px] text-amber", "data-testid": `approve-queue-badge-${group.principleId}`, children: promptInjection.productionRotates === true && group.fitsPromptBudget === true
|
|
312
|
+
: t("pages.focus.decidedRejected", { defaultValue: "已拒绝" }) })), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("span", { className: "inline-flex items-center border border-line rounded-[2px] px-[7px] py-1 font-mono text-[11px] text-ink-3 bg-surface/80 uppercase", children: channelLabel }), isReversible && (_jsx("span", { className: "inline-flex items-center border border-amber/35 text-amber rounded-[2px] px-[7px] py-1 font-mono text-[11px] uppercase", children: primaryChannel === "prompt" ? t("pages.focus.tagReversible") : t("pages.focus.tagLowRisk") }))] }), _jsx("div", { className: "mt-[14px] mb-2 font-semibold text-ink leading-snug", children: displayTitle }), group.artifactUnavailable === true && (_jsx("div", { className: "mb-2 text-[12.5px] leading-relaxed text-amber", "data-testid": `artifact-unavailable-${group.principleId}`, children: t("pages.focus.artifactUnavailableNote") })), _jsx("div", { className: "mt-2 px-3 py-2 bg-surface/60 border-l-2 border-gov text-ink-3 text-[13px] leading-snug", children: t("pages.focus.evidenceSummary", { count: group.records.length }) }), actionsLocked && (_jsxs("div", { className: "mt-3 rounded-[3px] border border-amber/40 bg-amber/5 p-3", "data-testid": `pending-actions-locked-${group.principleId}`, children: [_jsx("p", { className: "text-ink-2 text-[12.5px] leading-relaxed", children: actionsLockedReason }), _jsx(Link, { to: "/settings", "data-testid": `pending-actions-locked-settings-${group.principleId}`, className: "mt-2 inline-flex items-center border border-line text-ink bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors", children: t("pages.focus.ownerDecision.goSettingsCta") })] })), decidedOutcome === null && primaryChannel === "prompt" && promptInjection?.truncated === true && (_jsx("div", { className: "mt-3 rounded-[3px] border border-amber/40 bg-amber/5 px-3 py-2 text-[12.5px] text-amber", "data-testid": `approve-queue-badge-${group.principleId}`, children: promptInjection.productionRotates === true && group.fitsPromptBudget === true
|
|
310
313
|
? t("pages.focus.approveQueueBadgeRotation", {
|
|
311
314
|
defaultValue: "批准后可能排队:提示词注入位已满({{used}}/{{budget}} 字符)。当前按公平轮转选取,新原则会在后续轮次自行轮到,无需停用旧原则。",
|
|
312
315
|
used: promptInjection.usedChars,
|
|
@@ -4,5 +4,16 @@
|
|
|
4
4
|
* kept separate from ui/utils/validators.ts because the page additionally
|
|
5
5
|
* enforces the pending/approved/rejected status whitelist it renders on.
|
|
6
6
|
*/
|
|
7
|
-
import type { ApprovalsGroupedData } from "../../api.js";
|
|
7
|
+
import type { ApprovalsGroupedData, ApprovalGroup } from "../../api.js";
|
|
8
8
|
export declare function validateApprovalsGroupedData(raw: unknown): ApprovalsGroupedData | null;
|
|
9
|
+
/**
|
|
10
|
+
* PRI-940: which string may the pending-review card render as its title.
|
|
11
|
+
*
|
|
12
|
+
* `principleTitle` degrades to the synthesized `unlinked:<artifactId>` grouping
|
|
13
|
+
* key when the pinned artifact is missing — a machine id that must never reach
|
|
14
|
+
* the Owner as a title (real case: an approval pinned to a superseded scribe
|
|
15
|
+
* revision rendered `unlinked:pi-art-scribe-…` on the card). Returns undefined
|
|
16
|
+
* for that case so the caller falls back to the localized untitled copy; the
|
|
17
|
+
* degradation itself is explained by the artifactUnavailable note, not here.
|
|
18
|
+
*/
|
|
19
|
+
export declare function selectApprovalGroupDisplayTitle(group: Pick<ApprovalGroup, "candidateDescription" | "principleTitle" | "artifactUnavailable">): string | undefined;
|
|
@@ -64,12 +64,23 @@ function validateApprovalGroup(raw) {
|
|
|
64
64
|
return null;
|
|
65
65
|
fitsPromptBudget = fits;
|
|
66
66
|
}
|
|
67
|
+
// PRI-940: artifactUnavailable is optional. ERR-009 discipline — present but
|
|
68
|
+
// wrong type fails loud (return null); absent stays undefined, which keeps
|
|
69
|
+
// the card title rendering exactly as before.
|
|
70
|
+
let artifactUnavailable;
|
|
71
|
+
if (Object.hasOwn(raw, "artifactUnavailable")) {
|
|
72
|
+
const { artifactUnavailable: unavailable } = raw;
|
|
73
|
+
if (typeof unavailable !== "boolean")
|
|
74
|
+
return null;
|
|
75
|
+
artifactUnavailable = unavailable;
|
|
76
|
+
}
|
|
67
77
|
return {
|
|
68
78
|
principleId,
|
|
69
79
|
principleTitle,
|
|
70
80
|
candidateDescription,
|
|
71
81
|
status,
|
|
72
82
|
fitsPromptBudget,
|
|
83
|
+
artifactUnavailable,
|
|
73
84
|
records: validRecords,
|
|
74
85
|
};
|
|
75
86
|
}
|
|
@@ -101,3 +112,21 @@ export function validateApprovalsGroupedData(raw) {
|
|
|
101
112
|
promptInjection: validatePromptInjectionBudgetStatus(raw.promptInjection) ?? undefined,
|
|
102
113
|
};
|
|
103
114
|
}
|
|
115
|
+
/**
|
|
116
|
+
* PRI-940: which string may the pending-review card render as its title.
|
|
117
|
+
*
|
|
118
|
+
* `principleTitle` degrades to the synthesized `unlinked:<artifactId>` grouping
|
|
119
|
+
* key when the pinned artifact is missing — a machine id that must never reach
|
|
120
|
+
* the Owner as a title (real case: an approval pinned to a superseded scribe
|
|
121
|
+
* revision rendered `unlinked:pi-art-scribe-…` on the card). Returns undefined
|
|
122
|
+
* for that case so the caller falls back to the localized untitled copy; the
|
|
123
|
+
* degradation itself is explained by the artifactUnavailable note, not here.
|
|
124
|
+
*/
|
|
125
|
+
export function selectApprovalGroupDisplayTitle(group) {
|
|
126
|
+
if (group.candidateDescription !== undefined && group.candidateDescription !== "") {
|
|
127
|
+
return group.candidateDescription;
|
|
128
|
+
}
|
|
129
|
+
if (group.artifactUnavailable === true)
|
|
130
|
+
return undefined;
|
|
131
|
+
return group.principleTitle;
|
|
132
|
+
}
|
|
@@ -642,6 +642,12 @@ export interface ApprovalGroupData {
|
|
|
642
642
|
* "do not promise rotation".
|
|
643
643
|
*/
|
|
644
644
|
fitsPromptBudget?: boolean;
|
|
645
|
+
/**
|
|
646
|
+
* PRI-940: the pinned artifact is missing from the store, so the card has no
|
|
647
|
+
* human-readable payload and `principleTitle` carries the synthesized
|
|
648
|
+
* `unlinked:<artifactId>` machine id, which must never render as a title.
|
|
649
|
+
*/
|
|
650
|
+
artifactUnavailable?: boolean;
|
|
645
651
|
status: string;
|
|
646
652
|
records: ApprovalGroupRecordData[];
|
|
647
653
|
}
|
|
@@ -1652,6 +1652,12 @@ function validateApprovalGroup(v) {
|
|
|
1652
1652
|
if (Object.hasOwn(v, 'fitsPromptBudget') && typeof v.fitsPromptBudget === 'boolean') {
|
|
1653
1653
|
result.fitsPromptBudget = v.fitsPromptBudget;
|
|
1654
1654
|
}
|
|
1655
|
+
// PRI-940: optional artifactUnavailable. Only a real boolean is propagated; a
|
|
1656
|
+
// present-but-wrong value is dropped so the card keeps its plain untitled
|
|
1657
|
+
// fallback rather than rendering an unverified claim.
|
|
1658
|
+
if (Object.hasOwn(v, 'artifactUnavailable') && typeof v.artifactUnavailable === 'boolean') {
|
|
1659
|
+
result.artifactUnavailable = v.artifactUnavailable;
|
|
1660
|
+
}
|
|
1655
1661
|
return result;
|
|
1656
1662
|
}
|
|
1657
1663
|
export function validatePromptInjectionBudgetStatus(v) {
|