create-principles-disciple 1.145.11 → 1.145.12
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/codec/input-decoder.js +11 -3
- package/codex-adapter/package.json +1 -1
- package/console/dist/server/models/ActivationsConsoleModel.js +1 -1
- package/console/dist/server/models/ApprovalsConsoleModel.d.ts +1 -1
- package/console/dist/server/models/ApprovalsConsoleModel.js +8 -1
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.d.ts +13 -1
- package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +51 -16
- package/console/dist/shared/prompt-injection-contract.d.ts +30 -2
- package/console/dist/ui/pages/activation/ActivationPage.js +17 -4
- package/console/dist/ui/pages/focus/FocusPage.js +36 -12
- package/console/dist/ui/pages/focus/focus-validation.js +12 -0
- package/console/dist/ui/utils/validators.d.ts +6 -0
- package/console/dist/ui/utils/validators.js +33 -3
- package/console/dist/web/assets/app.js +20 -20
- package/core/dist/host/host-adapter.d.ts +27 -0
- package/core/dist/host/host-adapter.d.ts.map +1 -1
- package/core/dist/host/host-adapter.js.map +1 -1
- package/core/dist/host/index.d.ts +1 -1
- package/core/dist/host/index.d.ts.map +1 -1
- package/core/dist/host/index.js.map +1 -1
- package/core/dist/runtime-v2/activation/index.d.ts +2 -0
- package/core/dist/runtime-v2/activation/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/activation/index.js +2 -0
- package/core/dist/runtime-v2/activation/index.js.map +1 -1
- package/core/dist/runtime-v2/activation/prompt-budget-policy.d.ts +106 -0
- package/core/dist/runtime-v2/activation/prompt-budget-policy.d.ts.map +1 -0
- package/core/dist/runtime-v2/activation/prompt-budget-policy.js +229 -0
- package/core/dist/runtime-v2/activation/prompt-budget-policy.js.map +1 -0
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.d.ts +2 -0
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.d.ts.map +1 -1
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.js +50 -1
- package/core/dist/runtime-v2/adapter/pi-ai-catalog.js.map +1 -1
- package/core/dist/runtime-v2/index.d.ts +3 -3
- package/core/dist/runtime-v2/index.d.ts.map +1 -1
- package/core/dist/runtime-v2/index.js +2 -2
- package/core/dist/runtime-v2/index.js.map +1 -1
- package/core/dist/runtime-v2/types/event-types.d.ts +24 -0
- package/core/dist/runtime-v2/types/event-types.d.ts.map +1 -1
- package/core/dist/runtime-v2/types/event-types.js +22 -0
- package/core/dist/runtime-v2/types/event-types.js.map +1 -1
- package/core/package.json +1 -1
- package/host-runtime/dist/active-principle-prompt.d.ts +23 -1
- package/host-runtime/dist/active-principle-prompt.js +21 -5
- package/host-runtime/dist/index.js +33 -2
- package/host-runtime/dist/prompt-activation-deliverability.d.ts +36 -3
- package/host-runtime/dist/prompt-activation-deliverability.js +44 -26
- package/host-runtime/dist/prompt-injection-projection.d.ts +23 -1
- package/host-runtime/dist/prompt-injection-projection.js +25 -6
- package/host-runtime/dist/prompt-injection-status.d.ts +20 -5
- package/host-runtime/dist/prompt-injection-status.js +30 -7
- package/host-runtime/package.json +1 -1
- package/package.json +2 -2
- package/pd-cli/dist/commands/runtime-activation.d.ts.map +1 -1
- package/pd-cli/dist/commands/runtime-activation.js +18 -3
- package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
- package/plugin/dist/bundle.js +426 -426
- package/plugin/dist/governance-audit.js +2 -2
- package/plugin/dist/openclaw-sdk.d.ts +9 -1
- package/plugin/dist/openclaw.plugin.json +1 -1
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/release-manager/package.json +2 -2
- package/workbuddy-adapter/dist/codec/input-decoder.js +16 -4
- package/workbuddy-adapter/package.json +1 -1
|
@@ -59,10 +59,15 @@ function validateCommon(raw, needsTurn) {
|
|
|
59
59
|
const sessionId = requiredString(raw, 'session_id');
|
|
60
60
|
requiredNullableString(raw, 'transcript_path');
|
|
61
61
|
const workspaceDir = requiredString(raw, 'cwd');
|
|
62
|
-
|
|
62
|
+
// SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001: keep the VALIDATED model identity
|
|
63
|
+
// (was: checked then discarded). It is an identity string only — never a
|
|
64
|
+
// capacity; the budget resolver turns it into a budget via the installed
|
|
65
|
+
// catalog, falling back to the fixed budget on unknown/ambiguous ids. It
|
|
66
|
+
// must not block the hook or leak into tool allow/deny decisions.
|
|
67
|
+
const modelId = requiredString(raw, 'model');
|
|
63
68
|
requiredString(raw, 'permission_mode');
|
|
64
69
|
const turnId = needsTurn ? requiredString(raw, 'turn_id') : undefined;
|
|
65
|
-
return { workspaceDir, sessionId, ...(turnId ? { turnId } : {}) };
|
|
70
|
+
return { base: { workspaceDir, sessionId, ...(turnId ? { turnId } : {}) }, modelId };
|
|
66
71
|
}
|
|
67
72
|
export function decodeCodexInput(raw) {
|
|
68
73
|
if (!isRecord(raw))
|
|
@@ -71,7 +76,10 @@ export function decodeCodexInput(raw) {
|
|
|
71
76
|
const kind = EVENT_KINDS.get(eventName);
|
|
72
77
|
if (!kind)
|
|
73
78
|
throw new CodexDecoderError(`unknown hook_event_name "${eventName}"`, 'Configure only PreToolUse, PostToolUse, UserPromptSubmit, SessionStart, or Stop.');
|
|
74
|
-
const
|
|
79
|
+
const { base, modelId } = validateCommon(raw, kind !== 'session_start');
|
|
80
|
+
// Model identity rides every event context under modelContext (budget
|
|
81
|
+
// resolution happens on the prompt path; other paths carry it harmlessly).
|
|
82
|
+
const common = { ...base, modelContext: { modelId } };
|
|
75
83
|
let context;
|
|
76
84
|
let rawPayload = raw;
|
|
77
85
|
if (kind === 'before_tool_call' || kind === 'after_tool_call') {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@principles/codex-adapter",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
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",
|
|
@@ -343,7 +343,7 @@ export class ActivationsConsoleModel {
|
|
|
343
343
|
...(entry.route !== undefined ? { route: entry.route } : {}),
|
|
344
344
|
...(entry.reason !== undefined ? { reason: entry.reason } : {}),
|
|
345
345
|
...(entry.perHostFits !== undefined
|
|
346
|
-
? { perHostFits: entry.perHostFits.map((fit) => ({ hostKind: fit.hostKind,
|
|
346
|
+
? { perHostFits: entry.perHostFits.map((fit) => ({ hostKind: fit.hostKind, tierFit: fit.tierFit })) }
|
|
347
347
|
: {}),
|
|
348
348
|
},
|
|
349
349
|
]));
|
|
@@ -252,6 +252,13 @@ export class ApprovalsConsoleModel {
|
|
|
252
252
|
}
|
|
253
253
|
if (deliverability.status === 'deliverable')
|
|
254
254
|
return undefined;
|
|
255
|
+
// SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001 §6.3: model_dependent is NOT a
|
|
256
|
+
// refusal — the entry fits within the supported budget range and whether
|
|
257
|
+
// it injects depends on the runtime model. The approval proceeds; the
|
|
258
|
+
// honest uncertainty is already visible on the pre-approval badge
|
|
259
|
+
// (promptBudgetTierFit) and the activation status surface.
|
|
260
|
+
if (deliverability.status === 'model_dependent')
|
|
261
|
+
return undefined;
|
|
255
262
|
if (deliverability.status === 'undeliverable') {
|
|
256
263
|
return {
|
|
257
264
|
ok: false,
|
|
@@ -276,7 +283,7 @@ export class ApprovalsConsoleModel {
|
|
|
276
283
|
capacity: {
|
|
277
284
|
category: deliverability.reason.startsWith('route_unconfirmed') ? 'route_unconfirmed' : 'content_unconfirmed',
|
|
278
285
|
...(deliverability.perHost !== undefined
|
|
279
|
-
? { perHost: deliverability.perHost.map((entry) => ({ hostKind: entry.hostKind,
|
|
286
|
+
? { perHost: deliverability.perHost.map((entry) => ({ hostKind: entry.hostKind, tierFit: entry.tierFit, costChars: entry.costChars })) }
|
|
280
287
|
: {}),
|
|
281
288
|
},
|
|
282
289
|
};
|
|
@@ -11,11 +11,23 @@ export interface ApprovalGroup {
|
|
|
11
11
|
/**
|
|
12
12
|
* PR-1894: whether this candidate's own serialized prompt entry fits the
|
|
13
13
|
* injection budget on its own. `false` means the entry is oversized, so fair
|
|
14
|
-
* rotation can NEVER inject it
|
|
14
|
+
* rotation can NEVER inject it — the pre-approval badge must not promise it
|
|
15
15
|
* will come around. Computed server-side with the production serializer
|
|
16
16
|
* (the client has no access to the escaped, serialized size).
|
|
17
|
+
* SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001: `true` now means "fits within the
|
|
18
|
+
* supported budget range" — see `promptBudgetTierFit` for whether that is
|
|
19
|
+
* unconditional (fits every tier) or model-dependent.
|
|
17
20
|
*/
|
|
18
21
|
fitsPromptBudget?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001 §6.3: honest tier classification of
|
|
24
|
+
* the candidate's own serialized entry against the supported
|
|
25
|
+
* [min, max] budget range. `model_dependent` = fits only on
|
|
26
|
+
* larger-context models — the badge must qualify rotation promises, not
|
|
27
|
+
* promise unconditionally. `unconfirmed` mirrors the precheck's honest
|
|
28
|
+
* unknown.
|
|
29
|
+
*/
|
|
30
|
+
promptBudgetTierFit?: 'fits_all_tiers' | 'model_dependent' | 'exceeds_max_tier' | 'unconfirmed';
|
|
19
31
|
revisionReviewUnavailable?: boolean;
|
|
20
32
|
revisionReview?: {
|
|
21
33
|
oldStatement: string;
|
|
@@ -104,19 +104,21 @@ function extractCandidateDescription(contentJson) {
|
|
|
104
104
|
return null;
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
|
-
* PR-1894 → PD_PROMPT_CAPACITY_V1 R-A2
|
|
108
|
-
*
|
|
107
|
+
* PR-1894 → PD_PROMPT_CAPACITY_V1 R-A2 → SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001:
|
|
108
|
+
* classify this candidate's own prompt entry against the SUPPORTED budget
|
|
109
|
+
* range (not one fixed number — approval time cannot know the executing
|
|
110
|
+
* model).
|
|
109
111
|
*
|
|
110
|
-
* The judgment
|
|
112
|
+
* The judgment comes from the ONE shared host-runtime precheck
|
|
111
113
|
* (`checkPromptArtifactDeliverability`): the REAL resolved principle id (not a
|
|
112
114
|
* fixed 36-char UUID allowance — a title-derived id can only be longer), the
|
|
113
115
|
* effective route's own serializer (list entry vs full directive render incl.
|
|
114
116
|
* the self-report footer), and an honest `unconfirmed` verdict when the route
|
|
115
|
-
* or the artifact content cannot be confirmed. `
|
|
116
|
-
* conservative answer
|
|
117
|
+
* or the artifact content cannot be confirmed. `unconfirmed` is also the
|
|
118
|
+
* conservative answer when the precheck itself fails — an unknown size must
|
|
117
119
|
* never become a rotation promise (rc-9).
|
|
118
120
|
*/
|
|
119
|
-
async function
|
|
121
|
+
async function candidatePromptBudgetTierFit(workspaceDir, artifact, targetHost) {
|
|
120
122
|
const deliverability = await checkPromptArtifactDeliverability({
|
|
121
123
|
workspaceDir,
|
|
122
124
|
targetHost,
|
|
@@ -130,15 +132,31 @@ async function candidateFitsPromptBudget(workspaceDir, artifact, targetHost) {
|
|
|
130
132
|
: null,
|
|
131
133
|
});
|
|
132
134
|
if (deliverability.status === 'deliverable')
|
|
133
|
-
return
|
|
135
|
+
return 'fits_all_tiers';
|
|
136
|
+
if (deliverability.status === 'model_dependent')
|
|
137
|
+
return 'model_dependent';
|
|
134
138
|
if (deliverability.status === 'undeliverable')
|
|
135
|
-
return
|
|
136
|
-
//
|
|
137
|
-
//
|
|
139
|
+
return 'exceeds_max_tier';
|
|
140
|
+
// Route/artifact unconfirmed: aggregate the per-host tier verdicts
|
|
141
|
+
// MONOTONICALLY. The list route bills ~700 fewer chars than the shared
|
|
142
|
+
// route for the same entry (fixed wrapper delta), so "shared exceeds but
|
|
143
|
+
// list does not" is REACHABLE (e.g. a ~15300-char statement: list ≈15341
|
|
144
|
+
// = model_dependent, shared ≈16058 = exceeds). Collapsing that to
|
|
145
|
+
// exceeds_max_tier would tell an OpenClaw owner their entry can never
|
|
146
|
+
// inject when a large-window list-route host could carry it. Only when
|
|
147
|
+
// EVERY applicable host exceeds is "oversized everywhere" true; any
|
|
148
|
+
// disagreement leaves the honest model_dependent. Without per-host
|
|
149
|
+
// facts, do not promise at all.
|
|
138
150
|
if (deliverability.perHost && deliverability.perHost.length > 0) {
|
|
139
|
-
|
|
151
|
+
if (deliverability.perHost.every((entry) => entry.tierFit === 'exceeds_max_tier')) {
|
|
152
|
+
return 'exceeds_max_tier';
|
|
153
|
+
}
|
|
154
|
+
if (deliverability.perHost.every((entry) => entry.tierFit === 'fits_all_tiers')) {
|
|
155
|
+
return 'fits_all_tiers';
|
|
156
|
+
}
|
|
157
|
+
return 'model_dependent';
|
|
140
158
|
}
|
|
141
|
-
return
|
|
159
|
+
return 'unconfirmed';
|
|
142
160
|
}
|
|
143
161
|
export class ApprovalsGroupedConsoleModel {
|
|
144
162
|
workspaceDir;
|
|
@@ -231,14 +249,14 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
231
249
|
if (!detection.ok || (detection.target !== null && !revisionReviews.has(artifact.artifactId)))
|
|
232
250
|
revisionReviewUnavailable.add(artifact.artifactId);
|
|
233
251
|
}
|
|
234
|
-
artifactFitsBudgetMap.set(approval.artifactId, await
|
|
252
|
+
artifactFitsBudgetMap.set(approval.artifactId, await candidatePromptBudgetTierFit(this.workspaceDir, artifact, targetHost));
|
|
235
253
|
artifactUnavailableMap.set(approval.artifactId, artifact === null);
|
|
236
254
|
}
|
|
237
255
|
catch (err) {
|
|
238
256
|
if (isMissingTableError(err)) {
|
|
239
257
|
artifactPrincipleMap.set(approval.artifactId, null);
|
|
240
258
|
artifactDescriptionMap.set(approval.artifactId, null);
|
|
241
|
-
artifactFitsBudgetMap.set(approval.artifactId,
|
|
259
|
+
artifactFitsBudgetMap.set(approval.artifactId, 'unconfirmed');
|
|
242
260
|
artifactUnavailableMap.set(approval.artifactId, true);
|
|
243
261
|
}
|
|
244
262
|
else {
|
|
@@ -302,10 +320,18 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
302
320
|
principleTitle,
|
|
303
321
|
candidateDescription,
|
|
304
322
|
status,
|
|
305
|
-
// PR-1894: undefined-safe
|
|
323
|
+
// PR-1894: undefined-safe — absent when the artifact could not be
|
|
306
324
|
// read, which the UI treats as "do not promise rotation".
|
|
325
|
+
// SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001: the boolean stays the
|
|
326
|
+
// rotation-promise signal (true = within the supported budget
|
|
327
|
+
// range); the tier fit carries the honest fits-everywhere vs
|
|
328
|
+
// model-dependent distinction.
|
|
307
329
|
...(firstArtifactId !== undefined && artifactFitsBudgetMap.has(firstArtifactId)
|
|
308
|
-
? {
|
|
330
|
+
? {
|
|
331
|
+
fitsPromptBudget: artifactFitsBudgetMap.get(firstArtifactId) === 'fits_all_tiers'
|
|
332
|
+
|| artifactFitsBudgetMap.get(firstArtifactId) === 'model_dependent',
|
|
333
|
+
promptBudgetTierFit: artifactFitsBudgetMap.get(firstArtifactId),
|
|
334
|
+
}
|
|
309
335
|
: {}),
|
|
310
336
|
// PRI-940: absent when the pinned artifact is readable — same
|
|
311
337
|
// spread style as fitsPromptBudget so the wire contract stays
|
|
@@ -370,6 +396,7 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
370
396
|
usedChars: entry.projection.usedChars,
|
|
371
397
|
truncated: entry.projection.truncated,
|
|
372
398
|
})),
|
|
399
|
+
...(reference?.budgetFallbackReason !== undefined ? { budgetFallbackReason: reference.budgetFallbackReason } : {}),
|
|
373
400
|
},
|
|
374
401
|
};
|
|
375
402
|
}
|
|
@@ -391,6 +418,14 @@ export class ApprovalsGroupedConsoleModel {
|
|
|
391
418
|
...(projection.fullRenderChars !== undefined ? { fullRenderChars: projection.fullRenderChars } : {}),
|
|
392
419
|
oversizedActivationIds: projection.oversizedActivationIds,
|
|
393
420
|
oversizedDiagnosticTruncated: projection.oversizedDiagnosticTruncated,
|
|
421
|
+
// SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001 §7: budget provenance — the
|
|
422
|
+
// console holds no runtime model facts, so this is the fixed-budget
|
|
423
|
+
// view by construction; the UI must label it as such rather than
|
|
424
|
+
// presenting it as a model-accurate forecast.
|
|
425
|
+
...(projection.budgetCapacitySource !== undefined ? { budgetCapacitySource: projection.budgetCapacitySource } : {}),
|
|
426
|
+
...(projection.budgetCapacityStatus !== undefined ? { budgetCapacityStatus: projection.budgetCapacityStatus } : {}),
|
|
427
|
+
...(projection.budgetContextWindowTokens != null ? { budgetContextWindowTokens: projection.budgetContextWindowTokens } : {}),
|
|
428
|
+
...(projection.budgetFallbackReason !== undefined ? { budgetFallbackReason: projection.budgetFallbackReason } : {}),
|
|
394
429
|
},
|
|
395
430
|
};
|
|
396
431
|
}
|
|
@@ -29,7 +29,14 @@ export interface PromptInjectionPerHostForecast {
|
|
|
29
29
|
*/
|
|
30
30
|
export interface PromptActivationInjectionStatusWire {
|
|
31
31
|
activationId: string;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001: `model_dependent` = the row's own
|
|
34
|
+
* entry misses the CURRENT projection budget but fits within the supported
|
|
35
|
+
* [min, max] budget range — injectability depends on the runtime model's
|
|
36
|
+
* context window, which this surface cannot see (never displayed as a
|
|
37
|
+
* definite yes or a permanent no).
|
|
38
|
+
*/
|
|
39
|
+
status: 'injectable' | 'oversized' | 'model_dependent' | 'window_excluded' | 'resolution_failed' | 'route_unconfirmed';
|
|
33
40
|
inCurrentWindow?: boolean;
|
|
34
41
|
costChars?: number;
|
|
35
42
|
budget?: number;
|
|
@@ -38,9 +45,11 @@ export interface PromptActivationInjectionStatusWire {
|
|
|
38
45
|
reason?: string;
|
|
39
46
|
perHostFits?: {
|
|
40
47
|
hostKind: PromptInjectionTargetHostName;
|
|
41
|
-
|
|
48
|
+
tierFit: PromptBudgetTierFitName;
|
|
42
49
|
}[];
|
|
43
50
|
}
|
|
51
|
+
/** Tier classification of a single serialized entry vs the supported budget range. */
|
|
52
|
+
export type PromptBudgetTierFitName = 'fits_all_tiers' | 'model_dependent' | 'exceeds_max_tier';
|
|
44
53
|
/** Route context attached to the activations payload (AC-01 visibility). */
|
|
45
54
|
export interface PromptInjectionRouteSummaryWire {
|
|
46
55
|
status: 'confirmed' | 'unconfirmed';
|
|
@@ -105,4 +114,23 @@ export interface PromptInjectionBudgetStatus {
|
|
|
105
114
|
nextAction?: string;
|
|
106
115
|
/** Present when capacityStatus === 'unconfirmed': per-host forecasts. */
|
|
107
116
|
perHost?: PromptInjectionPerHostForecast[];
|
|
117
|
+
/**
|
|
118
|
+
* SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001 §7: provenance of `budget` — the
|
|
119
|
+
* same resolution the real injection runs. `legacy_fallback` + `unknown`
|
|
120
|
+
* means these numbers are the fixed-budget view (no runtime model capacity
|
|
121
|
+
* was confirmed); the UI must present them as such, not as a
|
|
122
|
+
* model-accurate forecast. Absent behaves as the legacy fixed view
|
|
123
|
+
* (pre-SPEC payload compat).
|
|
124
|
+
*/
|
|
125
|
+
budgetCapacitySource?: 'host_effective_window' | 'pi_catalog_exact' | 'legacy_fallback';
|
|
126
|
+
/** confirmed = a real context window was resolved; unknown = fixed-budget fallback. */
|
|
127
|
+
budgetCapacityStatus?: 'confirmed' | 'unknown';
|
|
128
|
+
/** The context window the budget was derived from; present only when confirmed. */
|
|
129
|
+
budgetContextWindowTokens?: number;
|
|
130
|
+
/**
|
|
131
|
+
* SPEC §7 v0.2: bounded machine-readable reason — present only when
|
|
132
|
+
* budgetCapacityStatus is 'unknown'. Closed set mirroring the core
|
|
133
|
+
* `PromptBudgetFallbackReason`; never free-form detail or user content.
|
|
134
|
+
*/
|
|
135
|
+
budgetFallbackReason?: 'host_capacity_absent' | 'host_capacity_source_untrusted' | 'host_capacity_invalid_value' | 'catalog_unavailable' | 'model_identity_absent' | 'catalog_no_exact_match' | 'catalog_no_match_for_model_id' | 'catalog_ambiguous_model_id' | 'catalog_provider_absent' | 'catalog_metadata_invalid';
|
|
108
136
|
}
|
|
@@ -13,6 +13,15 @@ import { ReceiptCoverageDisclosure, getReceiptSourceStatusLabelKey } from "../..
|
|
|
13
13
|
import { validateActivationsData, validateLifecycleMetricsData, isReversibleChannel, } from "./ActivationValidators.js";
|
|
14
14
|
import { enumLabel } from "../../utils/enum-labels.js";
|
|
15
15
|
import { formatDate } from "../../utils/format-date.js";
|
|
16
|
+
function tierFitLabel(t, tierFit) {
|
|
17
|
+
if (tierFit === undefined)
|
|
18
|
+
return t("pages.activation.fitUnknown", { defaultValue: "未知" });
|
|
19
|
+
if (tierFit === "fits_all_tiers")
|
|
20
|
+
return t("pages.activation.fitYes", { defaultValue: "可装入" });
|
|
21
|
+
if (tierFit === "model_dependent")
|
|
22
|
+
return t("pages.activation.fitModelDependent", { defaultValue: "视模型而定" });
|
|
23
|
+
return t("pages.activation.fitNo", { defaultValue: "超长" });
|
|
24
|
+
}
|
|
16
25
|
// Date+time rendering options — mirrors the old default toLocaleString() shape
|
|
17
26
|
// (numeric date, hours/minutes/seconds) while following the UI language.
|
|
18
27
|
const DATETIME_OPTS = {
|
|
@@ -151,7 +160,7 @@ function ActivationFactCard({ record, lifecycleData, receiptCount, onDisable, di
|
|
|
151
160
|
navigate(`/report-problem?${params.toString()}`);
|
|
152
161
|
}, children: t("pages.reportProblem.entryFeedback") }), _jsxs("div", { className: "mt-[14px] mb-2", children: [_jsx("span", { className: "text-ink-2 text-[13px]", children: t("pages.activation.principleLabel") }), " ", _jsx(Link, { to: `/principles/${record.principleId}`, className: "text-gov text-[13px] hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", "data-testid": `principle-link-${record.principleId}`, title: record.principleId, children: record.action }), _jsx("span", { className: "ml-2 font-mono text-[11px] text-ink-4", children: record.principleId })] }), record.legacyDecisionUnknown && _jsx("div", { className: "mt-3 border-l-2 border-amber px-3 text-[12px] text-amber", children: t('pages.activation.legacyUnknown', { due: record.ownerReviewDueAt ? formatDate(record.ownerReviewDueAt, i18n.language, DATETIME_OPTS) : t('pages.activation.sevenDays') }) }), _jsxs("div", { className: "text-ink-3 text-[13px] leading-relaxed space-y-1", children: [_jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.actionLabel") }), " ", _jsx("span", { className: "text-ink-2", children: record.action })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.targetLabel") }), " ", _jsx("span", { className: "text-ink-2 font-mono text-[12px]", children: record.targetRef })] }), _jsxs("div", { children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.activatedAtLabel") }), " ", neverActivated ? (_jsx("span", { className: "text-amber", children: t("pages.activation.neverActivated") })) : (_jsx("span", { className: "text-ink-2 font-mono text-[12px] tabular-nums", children: formatDate(record.activatedAt, i18n.language, DATETIME_OPTS) }))] }), record.promptInjection !== undefined && (_jsxs("div", { className: "flex items-start gap-2", "data-testid": `activation-injection-${record.activationId}`, children: [_jsx("span", { className: "text-ink-4 font-mono text-[11px] uppercase", children: t("pages.activation.injectionLabel", { defaultValue: "注入状态" }) }), _jsxs("span", { className: `text-[12.5px] leading-relaxed ${record.promptInjection.status === "injectable"
|
|
153
162
|
? "text-ink-2"
|
|
154
|
-
: record.promptInjection.status === "window_excluded"
|
|
163
|
+
: record.promptInjection.status === "window_excluded" || record.promptInjection.status === "model_dependent"
|
|
155
164
|
? "text-amber"
|
|
156
165
|
: "text-danger"}`, children: [record.promptInjection.status === "injectable" &&
|
|
157
166
|
t("pages.activation.injectionInjectable", {
|
|
@@ -174,15 +183,19 @@ function ActivationFactCard({ record, lifecycleData, receiptCount, onDisable, di
|
|
|
174
183
|
defaultValue: "超长不可注入:单条已 {{cost}} 字符 > 上限 {{budget}} 字符,轮转也救不了——需精简正文",
|
|
175
184
|
cost: record.promptInjection.costChars ?? 0,
|
|
176
185
|
budget: record.promptInjection.budget ?? 0,
|
|
186
|
+
}), record.promptInjection.status === "model_dependent" &&
|
|
187
|
+
t("pages.activation.injectionModelDependent", {
|
|
188
|
+
defaultValue: "取决于模型上下文:单条 {{cost}} 字符在最小预算内装不下、但在支持上限内——较小上下文模型会排除它,较大模型可注入",
|
|
189
|
+
cost: record.promptInjection.costChars ?? 0,
|
|
177
190
|
}), record.promptInjection.status === "resolution_failed" &&
|
|
178
191
|
t("pages.activation.injectionResolutionFailed", {
|
|
179
192
|
defaultValue: "无法判定:{{reason}}",
|
|
180
193
|
reason: record.promptInjection.reason ?? "unknown",
|
|
181
194
|
}), record.promptInjection.status === "route_unconfirmed" && (_jsxs(_Fragment, { children: [t("pages.activation.injectionRouteUnconfirmed", {
|
|
182
195
|
defaultValue: "当前宿主容量未确认:OpenClaw {{oc}} / Codex {{cx}} / WorkBuddy {{wb}}",
|
|
183
|
-
oc:
|
|
184
|
-
cx:
|
|
185
|
-
wb:
|
|
196
|
+
oc: tierFitLabel(t, record.promptInjection.perHostFits?.find((f) => f.hostKind === "openclaw")?.tierFit),
|
|
197
|
+
cx: tierFitLabel(t, record.promptInjection.perHostFits?.find((f) => f.hostKind === "codex")?.tierFit),
|
|
198
|
+
wb: tierFitLabel(t, record.promptInjection.perHostFits?.find((f) => f.hostKind === "workbuddy")?.tierFit),
|
|
186
199
|
}), record.promptInjection.reason !== undefined && (_jsx("span", { className: "block text-ink-4 text-[11.5px]", children: record.promptInjection.reason }))] }))] })] })), canProposeRevision && (_jsxs("div", { className: "mt-2", "data-testid": `activation-revision-${record.activationId}`, children: [!showRevisionInput && revisionResult === null && (_jsx("button", { type: "button", onClick: () => { setShowRevisionInput(true); setRevisionText(""); }, "data-testid": `activation-revision-open-${record.activationId}`, className: "inline-flex items-center border border-line rounded-[3px] px-[10px] py-[4px] text-[12px] hover:border-line-2", children: t("pages.activation.revisionOpen", { defaultValue: "修改为可注入版本" }) })), showRevisionInput && (_jsxs("div", { className: "rounded-[3px] border border-line bg-surface/60 p-2", children: [_jsx("textarea", { value: revisionText, onChange: (e) => setRevisionText(e.target.value), rows: 3, className: "w-full rounded-[2px] border border-line bg-panel px-2 py-1 text-[12.5px]", placeholder: t("pages.activation.revisionPlaceholder", { defaultValue: "输入新的原则正文(保留触发条件、必须动作和必要例外,尽量精简)" }), "data-testid": `activation-revision-input-${record.activationId}` }), _jsxs("div", { className: "mt-1 flex gap-2", children: [_jsx(Button, { variant: "ghost", size: "sm", onClick: handleProposeRevision, disabled: revisionSubmitting || revisionText.trim().length === 0, "data-testid": `activation-revision-submit-${record.activationId}`, children: revisionSubmitting ? t("common.loading") + "…" : t("pages.activation.revisionSubmit", { defaultValue: "创建待审批的新版本" }) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setShowRevisionInput(false), children: t("common.cancel", { defaultValue: "取消" }) })] })] })), revisionResult !== null && (_jsxs("div", { className: "rounded-[3px] border border-gov/40 bg-gov/5 p-2 text-[12px] leading-relaxed", "data-testid": `activation-revision-result-${record.activationId}`, children: [_jsx("p", { children: t("pages.activation.revisionResultNote", {
|
|
187
200
|
defaultValue: "新版本已进入待审批(审批号 {{approvalId}})。批准后旧版本自动停用并由新版本替换;拒绝则旧版本继续生效。",
|
|
188
201
|
approvalId: revisionResult.approvalId ?? "",
|
|
@@ -402,29 +402,53 @@ function PendingReviewCard({ group, actionsLockedReason, onDecisionApplied, prom
|
|
|
402
402
|
? "border-green/40 bg-green/10 text-green"
|
|
403
403
|
: "border-line bg-surface/80 text-ink-3"}`, role: "status", "data-testid": `decided-banner-${group.principleId}`, children: decidedOutcome === "approved"
|
|
404
404
|
? t("pages.focus.decidedApproved", { defaultValue: "已批准 ✓ 正在写入生效记录…" })
|
|
405
|
-
: 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", "data-testid": `candidate-title-${group.principleId}`, children: readingLayout.title }), readingLayout.body !== null && (_jsx("div", { className: "mb-2 text-[15px] leading-relaxed text-ink whitespace-pre-wrap break-words", "data-testid": `candidate-body-${group.principleId}`, children: readingLayout.body })), 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") })), unlinkedCandidateNote && (_jsx("div", { className: "mb-2 text-[12.5px] leading-relaxed text-amber", "data-testid": `unlinked-candidate-${group.principleId}`, children: t("pages.focus.unlinkedCandidateNote") })), _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 && (_jsxs("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
|
|
406
|
-
? t("pages.focus.
|
|
407
|
-
defaultValue: "批准后可能排队:提示词注入位已满({{used}}/{{budget}}
|
|
405
|
+
: 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", "data-testid": `candidate-title-${group.principleId}`, children: readingLayout.title }), readingLayout.body !== null && (_jsx("div", { className: "mb-2 text-[15px] leading-relaxed text-ink whitespace-pre-wrap break-words", "data-testid": `candidate-body-${group.principleId}`, children: readingLayout.body })), 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") })), unlinkedCandidateNote && (_jsx("div", { className: "mb-2 text-[12.5px] leading-relaxed text-amber", "data-testid": `unlinked-candidate-${group.principleId}`, children: t("pages.focus.unlinkedCandidateNote") })), _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 && (_jsxs("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 && group.promptBudgetTierFit === "model_dependent"
|
|
406
|
+
? t("pages.focus.approveQueueBadgeRotationModelDependent", {
|
|
407
|
+
defaultValue: "批准后可能排队:提示词注入位已满({{used}}/{{budget}} 字符)。这条原则的篇幅在较小上下文模型的注入预算内装不下,轮到它时也未必进入提示词——是否生效取决于当时模型的上限。",
|
|
408
408
|
used: promptInjection.usedChars,
|
|
409
409
|
budget: promptInjection.budget,
|
|
410
410
|
})
|
|
411
|
-
: promptInjection.productionRotates === true
|
|
412
|
-
? t("pages.focus.
|
|
413
|
-
defaultValue: "批准后可能排队:提示词注入位已满({{used}}/{{budget}}
|
|
411
|
+
: promptInjection.productionRotates === true && group.fitsPromptBudget === true
|
|
412
|
+
? t("pages.focus.approveQueueBadgeRotation", {
|
|
413
|
+
defaultValue: "批准后可能排队:提示词注入位已满({{used}}/{{budget}} 字符)。当前按公平轮转选取,新原则会在后续轮次自行轮到,无需停用旧原则。",
|
|
414
414
|
used: promptInjection.usedChars,
|
|
415
415
|
budget: promptInjection.budget,
|
|
416
416
|
})
|
|
417
|
-
:
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
417
|
+
: promptInjection.productionRotates === true
|
|
418
|
+
? t("pages.focus.approveQueueBadgeOversized", {
|
|
419
|
+
defaultValue: "批准后可能排队:提示词注入位已满({{used}}/{{budget}} 字符)。这条原则本身已超出注入位上限,轮转也无法让它进入提示词——需要先精简它的内容。",
|
|
420
|
+
used: promptInjection.usedChars,
|
|
421
|
+
budget: promptInjection.budget,
|
|
422
|
+
})
|
|
423
|
+
: t("pages.focus.approveQueueBadge", {
|
|
424
|
+
defaultValue: "批准后可能排队:提示词注入位已满({{used}}/{{budget}} 字符)。",
|
|
425
|
+
used: promptInjection.usedChars,
|
|
426
|
+
budget: promptInjection.budget,
|
|
427
|
+
}), _jsxs("div", { className: "mt-1 text-ink-3 text-[11.5px]", "data-testid": `approve-queue-badge-scope-${group.principleId}`, children: [promptInjection.route === "shared_render"
|
|
422
428
|
? t("pages.focus.promptScopeShared")
|
|
423
429
|
: t("pages.focus.promptScopeList", {
|
|
424
430
|
full: promptInjection.fullRenderChars !== undefined
|
|
425
431
|
? t("pages.focus.promptScopeFullRender", { count: promptInjection.fullRenderChars })
|
|
426
432
|
: "",
|
|
427
|
-
}), promptInjection.hostKind !== undefined ? ` · ${promptInjection.hostKind === "openclaw" ? "OpenClaw" : promptInjection.hostKind === "workbuddy" ? "WorkBuddy" : "Codex"}` : ""
|
|
433
|
+
}), promptInjection.hostKind !== undefined ? ` · ${promptInjection.hostKind === "openclaw" ? "OpenClaw" : promptInjection.hostKind === "workbuddy" ? "WorkBuddy" : "Codex"}` : "", ` · ${promptInjection.budgetCapacityStatus === "confirmed"
|
|
434
|
+
? t("pages.focus.promptBudgetModelAwareNote", {
|
|
435
|
+
defaultValue: "预算按已确认的模型上下文解析({{window}} tokens)",
|
|
436
|
+
window: promptInjection.budgetContextWindowTokens ?? 0,
|
|
437
|
+
})
|
|
438
|
+
: t("pages.focus.promptBudgetFallbackNote", {
|
|
439
|
+
defaultValue: "按固定回退预算估算(控制台未绑定运行时模型{{reason}})",
|
|
440
|
+
reason: promptInjection.budgetFallbackReason !== undefined
|
|
441
|
+
? t("pages.focus.promptBudgetFallbackReasonSuffix", {
|
|
442
|
+
defaultValue: ",原因: {{code}}",
|
|
443
|
+
// The raw bounded code stays the default: it is the
|
|
444
|
+
// grep-able identifier shared with events/logs. Locales
|
|
445
|
+
// may override via pages.focus.budgetFallbackReason.<code>.
|
|
446
|
+
code: t(`pages.focus.budgetFallbackReason.${promptInjection.budgetFallbackReason}`, {
|
|
447
|
+
defaultValue: promptInjection.budgetFallbackReason,
|
|
448
|
+
}),
|
|
449
|
+
})
|
|
450
|
+
: "",
|
|
451
|
+
})}`] })] })), group.revisionReviewUnavailable === true && _jsx("p", { role: "alert", className: "mt-3 text-danger", children: t("pages.focus.revisionReviewUnavailable") }), group.revisionReview !== undefined && (_jsxs("div", { className: "mt-3 border border-line p-3 text-[12.5px]", "data-testid": `revision-intent-review-${group.principleId}`, children: [_jsx("p", { children: t("pages.focus.revisionIntentReview", { defaultValue: "修订复核:确认新正文保留触发条件、行动要求和必要例外,并与沿用意图一致。" }) }), _jsx("pre", { className: "mt-2 max-h-32 overflow-auto whitespace-pre-wrap", children: group.revisionReview.oldStatement }), _jsx("pre", { className: "mt-2 max-h-32 overflow-auto whitespace-pre-wrap", children: group.revisionReview.newStatement }), _jsx("dl", { className: "mt-2", children: Object.entries(group.revisionReview.intentFields).map(([key, value]) => _jsxs("div", { children: [_jsx("dt", { children: t(`pages.focus.intentField.${key}`, { defaultValue: key }) }), _jsx("dd", { children: value })] }, key)) }), _jsxs("label", { className: "mt-2 flex gap-2", children: [_jsx("input", { type: "checkbox", checked: intentReviewed, onChange: (event) => setIntentReviewed(event.target.checked) }), t("pages.focus.revisionIntentConfirmed", { defaultValue: "我已核对上述意图与新正文一致" })] })] })), _jsxs("div", { className: "flex gap-2 mt-4 flex-wrap items-center", children: [_jsx("button", { type: "button", onClick: handleApprove, disabled: decisionDisabled || (group.revisionReviewUnavailable === true || (group.revisionReview !== undefined && !intentReviewed)), "data-testid": `approve-btn-${group.principleId}`, className: "inline-flex items-center border border-gov bg-gov text-paper rounded-[3px] px-[14px] py-[6px] text-[12.5px] font-medium hover:bg-gov-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: actionLoading ? t("common.loading") + "…" : t("pages.focus.approveAction", { defaultValue: "批准" }) }), _jsx("button", { type: "button", onClick: () => { setShowEditInput((v) => !v); setShowRejectInput(false); }, disabled: decisionDisabled, "data-testid": `edit-btn-${group.principleId}`, className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("pages.focus.editAction", { defaultValue: "替换待审版本" }) }), _jsx("button", { type: "button", onClick: () => { setShowRejectInput((v) => !v); setShowEditInput(false); }, disabled: decisionDisabled, "data-testid": `reject-btn-${group.principleId}`, className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("pages.focus.rejectAction", { defaultValue: "拒绝" }) }), _jsx(Link, { to: "/pain", className: "inline-flex items-center text-gov text-[12.5px] hover:underline focus-visible:outline-2 focus-visible:outline-gov focus-visible:outline-offset-2", children: t("pages.focus.viewFullChain") })] }), showRejectInput && (_jsxs("div", { className: "mt-3 p-3 border border-danger/20 rounded-[var(--radius-md)]", children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("pages.focus.rejectReasonLabel", { defaultValue: "拒绝原因(必填)" }) }), _jsx("textarea", { value: rejectReason, onChange: (e) => setRejectReason(e.target.value), "data-testid": `reject-reason-${group.principleId}`, placeholder: t("pages.focus.rejectReasonPlaceholder", { defaultValue: "请说明拒绝原因" }), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] min-h-[80px] resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov" }), _jsxs("div", { className: "flex gap-2 mt-2", children: [_jsx("button", { type: "button", onClick: handleReject, disabled: !rejectReason.trim() || actionLoading, "data-testid": `confirm-reject-${group.principleId}`, className: "inline-flex items-center border border-danger text-danger bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-danger/5 transition-colors disabled:opacity-50 disabled:cursor-not-allowed", children: t("pages.focus.confirmReject", { defaultValue: "确认拒绝" }) }), _jsx("button", { type: "button", onClick: () => { setShowRejectInput(false); setRejectReason(""); }, className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors", children: t("pages.focus.cancelAction", { defaultValue: "取消" }) })] })] })), showEditInput && (_jsxs("div", { className: "mt-3 p-3 border border-gov/20 rounded-[var(--radius-md)]", children: [_jsx("label", { className: "block font-mono text-[11px] uppercase tracking-[0.08em] text-ink-3 mb-2", children: t("pages.focus.editReasonLabel", { defaultValue: "替换原因(必填)" }) }), _jsxs("div", { className: "mb-2 text-ink-4 text-[12px] font-mono", children: [t("pages.focus.currentArtifactLabel", { defaultValue: "当前工件" }), ": ", currentArtifactId] }), _jsx("input", { type: "text", value: newArtifactId, onChange: (e) => setNewArtifactId(e.target.value), "data-testid": `edit-new-artifact-${group.principleId}`, placeholder: t("pages.focus.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] mb-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov", "aria-label": t("pages.focus.editNewArtifactPlaceholder", { defaultValue: "输入新的已验证工件 ID" }) }), _jsx("textarea", { value: editReason, onChange: (e) => setEditReason(e.target.value), "data-testid": `edit-reason-${group.principleId}`, placeholder: t("pages.focus.editReasonPlaceholder", { defaultValue: "请说明为什么需要替换这份待审版本" }), className: "w-full border border-line rounded-[var(--radius-md)] bg-surface text-ink px-3 py-2 text-[13px] min-h-[80px] resize-y focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gov" }), _jsxs("div", { className: "flex gap-2 mt-2", children: [_jsx("button", { type: "button", onClick: handleEdit, disabled: !editReason.trim() || !newArtifactId.trim() || actionLoading, "data-testid": `confirm-edit-${group.principleId}`, className: "inline-flex items-center border border-gov text-gov bg-surface rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:bg-gov/5 transition-colors disabled:opacity-50 disabled:cursor-not-allowed", children: t("pages.focus.confirmEdit", { defaultValue: "确认替换" }) }), _jsx("button", { type: "button", onClick: () => { setShowEditInput(false); setEditReason(""); setNewArtifactId(""); }, className: "inline-flex items-center border border-line bg-surface text-ink rounded-[3px] px-[14px] py-[6px] text-[12.5px] hover:border-line-2 transition-colors", children: t("pages.focus.cancelAction", { defaultValue: "取消" }) })] })] }))] }));
|
|
428
452
|
}
|
|
429
453
|
function StagnationSignalCard({ signal }) {
|
|
430
454
|
const { t } = useTranslation();
|
|
@@ -64,6 +64,17 @@ function validateApprovalGroup(raw) {
|
|
|
64
64
|
return null;
|
|
65
65
|
fitsPromptBudget = fits;
|
|
66
66
|
}
|
|
67
|
+
// SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001: promptBudgetTierFit is optional,
|
|
68
|
+
// whitelist-checked. model_dependent qualifies the rotation promise.
|
|
69
|
+
let promptBudgetTierFit;
|
|
70
|
+
if (Object.hasOwn(raw, "promptBudgetTierFit")) {
|
|
71
|
+
const { promptBudgetTierFit: tier } = raw;
|
|
72
|
+
if (tier !== "fits_all_tiers" && tier !== "model_dependent"
|
|
73
|
+
&& tier !== "exceeds_max_tier" && tier !== "unconfirmed") {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
promptBudgetTierFit = tier;
|
|
77
|
+
}
|
|
67
78
|
// PRI-940: artifactUnavailable is optional. ERR-009 discipline — present but
|
|
68
79
|
// wrong type fails loud (return null); absent stays undefined, which keeps
|
|
69
80
|
// the card title rendering exactly as before.
|
|
@@ -80,6 +91,7 @@ function validateApprovalGroup(raw) {
|
|
|
80
91
|
candidateDescription,
|
|
81
92
|
status,
|
|
82
93
|
fitsPromptBudget,
|
|
94
|
+
promptBudgetTierFit,
|
|
83
95
|
artifactUnavailable,
|
|
84
96
|
records: validRecords,
|
|
85
97
|
};
|
|
@@ -655,6 +655,12 @@ export interface ApprovalGroupData {
|
|
|
655
655
|
intentFields: Record<string, string>;
|
|
656
656
|
};
|
|
657
657
|
fitsPromptBudget?: boolean;
|
|
658
|
+
/**
|
|
659
|
+
* SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001: honest tier classification of the
|
|
660
|
+
* candidate's own entry vs the supported budget range. `model_dependent`
|
|
661
|
+
* qualifies the rotation promise (fits only on larger-context models).
|
|
662
|
+
*/
|
|
663
|
+
promptBudgetTierFit?: 'fits_all_tiers' | 'model_dependent' | 'exceeds_max_tier' | 'unconfirmed';
|
|
658
664
|
/**
|
|
659
665
|
* PRI-940: the pinned artifact is missing from the store, so the card has no
|
|
660
666
|
* human-readable payload and `principleTitle` carries the synthesized
|
|
@@ -1061,7 +1061,7 @@ export function validateGovernanceQueue(v) {
|
|
|
1061
1061
|
return result;
|
|
1062
1062
|
}
|
|
1063
1063
|
const PROMPT_INJECTION_STATUS_KINDS = new Set([
|
|
1064
|
-
'injectable', 'oversized', 'window_excluded', 'resolution_failed', 'route_unconfirmed',
|
|
1064
|
+
'injectable', 'oversized', 'model_dependent', 'window_excluded', 'resolution_failed', 'route_unconfirmed',
|
|
1065
1065
|
]);
|
|
1066
1066
|
/** rc-1..rc-4: validate the per-activation injection status as untrusted input. */
|
|
1067
1067
|
function validatePromptActivationInjectionStatus(v) {
|
|
@@ -1089,9 +1089,10 @@ function validatePromptActivationInjectionStatus(v) {
|
|
|
1089
1089
|
for (const entry of v.perHostFits) {
|
|
1090
1090
|
if (!isObject(entry))
|
|
1091
1091
|
return null;
|
|
1092
|
-
if ((entry.hostKind !== 'openclaw' && entry.hostKind !== 'codex' && entry.hostKind !== 'workbuddy')
|
|
1092
|
+
if ((entry.hostKind !== 'openclaw' && entry.hostKind !== 'codex' && entry.hostKind !== 'workbuddy')
|
|
1093
|
+
|| (entry.tierFit !== 'fits_all_tiers' && entry.tierFit !== 'model_dependent' && entry.tierFit !== 'exceeds_max_tier'))
|
|
1093
1094
|
return null;
|
|
1094
|
-
perHostFits.push({ hostKind: entry.hostKind,
|
|
1095
|
+
perHostFits.push({ hostKind: entry.hostKind, tierFit: entry.tierFit });
|
|
1095
1096
|
}
|
|
1096
1097
|
result.perHostFits = perHostFits;
|
|
1097
1098
|
}
|
|
@@ -1743,6 +1744,13 @@ function validateApprovalGroup(v) {
|
|
|
1743
1744
|
if (Object.hasOwn(v, 'fitsPromptBudget') && typeof v.fitsPromptBudget === 'boolean') {
|
|
1744
1745
|
result.fitsPromptBudget = v.fitsPromptBudget;
|
|
1745
1746
|
}
|
|
1747
|
+
// SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001: optional tier fit, whitelist +
|
|
1748
|
+
// drop-on-invalid (same rule). Absent keeps the boolean-only behavior.
|
|
1749
|
+
if (Object.hasOwn(v, 'promptBudgetTierFit')
|
|
1750
|
+
&& (v.promptBudgetTierFit === 'fits_all_tiers' || v.promptBudgetTierFit === 'model_dependent'
|
|
1751
|
+
|| v.promptBudgetTierFit === 'exceeds_max_tier' || v.promptBudgetTierFit === 'unconfirmed')) {
|
|
1752
|
+
result.promptBudgetTierFit = v.promptBudgetTierFit;
|
|
1753
|
+
}
|
|
1746
1754
|
// PRI-940: optional artifactUnavailable. Only a real boolean is propagated; a
|
|
1747
1755
|
// present-but-wrong value is dropped so the card keeps its plain untitled
|
|
1748
1756
|
// fallback rather than rendering an unverified claim.
|
|
@@ -1806,6 +1814,28 @@ export function validatePromptInjectionBudgetStatus(v) {
|
|
|
1806
1814
|
if (Object.hasOwn(v, 'nextAction') && isString(v.nextAction)) {
|
|
1807
1815
|
result.nextAction = v.nextAction;
|
|
1808
1816
|
}
|
|
1817
|
+
// SPEC-PD-MODEL-AWARE-PROMPT-BUDGET-001 §7: budget provenance, additive +
|
|
1818
|
+
// drop-on-invalid. Absent behaves as the legacy fixed-budget view
|
|
1819
|
+
// (pre-SPEC server compat).
|
|
1820
|
+
if (Object.hasOwn(v, 'budgetCapacitySource')
|
|
1821
|
+
&& (v.budgetCapacitySource === 'host_effective_window' || v.budgetCapacitySource === 'pi_catalog_exact'
|
|
1822
|
+
|| v.budgetCapacitySource === 'legacy_fallback')) {
|
|
1823
|
+
result.budgetCapacitySource = v.budgetCapacitySource;
|
|
1824
|
+
}
|
|
1825
|
+
if (Object.hasOwn(v, 'budgetFallbackReason')
|
|
1826
|
+
&& (v.budgetFallbackReason === 'host_capacity_absent' || v.budgetFallbackReason === 'host_capacity_source_untrusted'
|
|
1827
|
+
|| v.budgetFallbackReason === 'host_capacity_invalid_value' || v.budgetFallbackReason === 'catalog_unavailable'
|
|
1828
|
+
|| v.budgetFallbackReason === 'model_identity_absent' || v.budgetFallbackReason === 'catalog_no_exact_match'
|
|
1829
|
+
|| v.budgetFallbackReason === 'catalog_no_match_for_model_id' || v.budgetFallbackReason === 'catalog_ambiguous_model_id'
|
|
1830
|
+
|| v.budgetFallbackReason === 'catalog_provider_absent' || v.budgetFallbackReason === 'catalog_metadata_invalid')) {
|
|
1831
|
+
result.budgetFallbackReason = v.budgetFallbackReason;
|
|
1832
|
+
}
|
|
1833
|
+
if (Object.hasOwn(v, 'budgetCapacityStatus') && (v.budgetCapacityStatus === 'confirmed' || v.budgetCapacityStatus === 'unknown')) {
|
|
1834
|
+
result.budgetCapacityStatus = v.budgetCapacityStatus;
|
|
1835
|
+
}
|
|
1836
|
+
if (Object.hasOwn(v, 'budgetContextWindowTokens') && typeof v.budgetContextWindowTokens === 'number' && Number.isInteger(v.budgetContextWindowTokens) && v.budgetContextWindowTokens > 0) {
|
|
1837
|
+
result.budgetContextWindowTokens = v.budgetContextWindowTokens;
|
|
1838
|
+
}
|
|
1809
1839
|
if (Object.hasOwn(v, 'perHost') && Array.isArray(v.perHost)) {
|
|
1810
1840
|
// rc-4: validate every element, not just "is array".
|
|
1811
1841
|
const perHost = [];
|