@tea-agent/loop-agent 0.17.0 → 0.17.2

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.
@@ -0,0 +1 @@
1
+ :root{--canvas: #f7f7f4;--canvas-soft: #fafaf7;--surface: #ffffff;--surface-muted: #f0efe9;--ink: #26251e;--body: #5a5852;--muted: #807d72;--muted-soft: #a09c92;--hairline: #e6e5e0;--hairline-strong: #cfcdc4;--orange: #f54e00;--orange-active: #d04200;--orange-soft: color-mix(in srgb, var(--orange) 12%, white);--blue: #3f6ea4;--green: #1f8a65;--amber: #a66a00;--red: #c72d53;--peach: #f1d8cb;--mint: #d8ead5;--space-1: 4px;--space-2: 8px;--space-3: 12px;--space-4: 16px;--space-5: 20px;--space-6: 24px;--space-8: 32px;--radius-sm: 6px;--radius-md: 8px;--radius-lg: 12px;--radius-pill: 999px;--transition-fast: .16s ease;--transition-base: .22s ease;--shadow-panel: 0 1px 2px rgb(38 37 30 / 4%), 0 8px 24px rgb(38 37 30 / 3%);--shadow-soft: 0 10px 30px rgb(38 37 30 / 6%);color-scheme:light;font-family:Segoe UI,PingFang SC,Hiragino Sans GB,Noto Sans SC,system-ui,-apple-system,sans-serif;line-height:1.5}*{box-sizing:border-box}html{background:radial-gradient(1200px 480px at 8% -10%,rgb(241 216 203 / 45%),transparent 55%),radial-gradient(900px 420px at 100% 0%,rgb(216 234 213 / 35%),transparent 50%),var(--canvas);min-height:100%}body{margin:0;min-height:100vh;min-width:320px;background:transparent;color:var(--ink);font-size:14px}button,input,select,textarea{font:inherit}a{color:inherit}code,.cmd,.kv dd code{font-family:JetBrains Mono,SFMono-Regular,Consolas,monospace}.console-shell{width:min(100% - 48px,1120px);margin:0 auto;padding:var(--space-8) 0 var(--space-12, 48px)}.console-header{display:grid;gap:var(--space-5);grid-template-columns:1.5fr minmax(220px,.9fr);align-items:end;margin-bottom:var(--space-5);padding-bottom:var(--space-6);border-bottom:1px solid var(--hairline)}.brand-block{display:grid;gap:var(--space-2);align-content:start}.brand-mark{display:inline-flex;align-items:center;gap:var(--space-2);width:fit-content;padding:4px 10px;border-radius:var(--radius-pill);background:var(--orange-soft);color:var(--orange-active);font-size:11px;font-weight:700;letter-spacing:.04em}h1{margin:0;color:var(--ink);font-size:clamp(26px,3vw,34px);font-weight:650;line-height:1.15;letter-spacing:-.02em}.subtitle{max-width:58ch;margin:0;color:var(--body);font-size:14px}.repo-chip{display:inline-flex;flex-wrap:wrap;align-items:center;gap:6px;width:fit-content;margin-top:var(--space-1);padding:6px 10px;border:1px solid var(--hairline);border-radius:var(--radius-pill);background:var(--surface);font-size:12px}.repo-chip code{font-size:11px;color:var(--body)}.top-nav{display:flex;flex-wrap:wrap;align-items:center;gap:var(--space-2);margin:0 0 var(--space-5);padding:6px;border:1px solid var(--hairline);border-radius:var(--radius-pill);background:color-mix(in srgb,var(--surface) 88%,transparent);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:var(--shadow-panel)}.nav-link{appearance:none;display:inline-flex;align-items:center;min-height:34px;padding:0 14px;border:1px solid transparent;border-radius:var(--radius-pill);background:transparent;color:var(--body);font-size:13px;font-weight:600;cursor:pointer;transition:background-color var(--transition-fast),color var(--transition-fast),border-color var(--transition-fast),transform var(--transition-fast)}.nav-link:hover:not(:disabled),.nav-link:focus-visible:not(:disabled){background:var(--surface);color:var(--ink);outline:none}.nav-link-active{background:var(--ink);color:#fff}.nav-link-active:hover:not(:disabled),.nav-link-active:focus-visible:not(:disabled){background:var(--ink);color:#fff}.nav-link-external{margin-left:auto;border-color:var(--hairline);background:var(--surface);color:var(--orange-active)}.nav-link-external:hover:not(:disabled){border-color:var(--orange);background:var(--orange-soft);color:var(--orange-active)}.readiness{position:relative;border:1px solid var(--hairline);background:var(--surface);border-radius:var(--radius-lg);padding:var(--space-5);box-shadow:var(--shadow-panel)}.readiness:before{content:"";position:absolute;top:18px;right:18px;width:8px;height:8px;border-radius:50%;background:var(--muted-soft)}.readiness[data-state=setup-required]{border-color:var(--hairline);background:var(--surface)}.readiness[data-state=setup-required]:before{background:var(--amber)}.readiness[data-state=ready]{border-color:var(--hairline);background:var(--surface)}.readiness[data-state=ready]:before{background:color-mix(in srgb,var(--green) 72%,white);box-shadow:0 0 0 3px color-mix(in srgb,var(--green) 12%,transparent)}.readiness[data-state=degraded]{border-color:var(--hairline);background:var(--surface)}.readiness[data-state=degraded]:before{background:var(--amber);box-shadow:0 0 0 3px color-mix(in srgb,var(--amber) 12%,transparent)}.readiness-label{display:block;margin-bottom:var(--space-1);color:var(--muted);font-size:12px;font-weight:600}.readiness strong{color:var(--ink);font-size:18px;font-weight:700}.readiness[data-state=ready] strong,.readiness[data-state=setup-required] strong,.readiness[data-state=degraded] strong{color:var(--ink)}.readiness .muted{margin:var(--space-2) 0 0}.flash{display:flex;gap:var(--space-3);align-items:flex-start;margin:0 0 var(--space-4);padding:12px 14px;border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--surface);box-shadow:var(--shadow-panel);animation:flash-in .22s ease}.flash-error{border-color:color-mix(in srgb,var(--red) 35%,var(--hairline));background:color-mix(in srgb,var(--red) 6%,var(--surface));color:var(--red)}.flash-info{border-color:color-mix(in srgb,var(--blue) 30%,var(--hairline));background:color-mix(in srgb,var(--blue) 6%,var(--surface));color:var(--body)}.flash-label{flex:0 0 auto;min-width:3.5rem;font-size:12px;font-weight:700;letter-spacing:.02em}.flash-body{margin:0;overflow-wrap:anywhere}.muted{color:var(--muted)}.console-main{display:grid;gap:var(--space-4)}.panel{min-width:0;border:1px solid var(--hairline);background:color-mix(in srgb,var(--surface) 96%,transparent);border-radius:var(--radius-lg);padding:var(--space-6);box-shadow:var(--shadow-panel)}.panel h2{margin:0 0 var(--space-2);color:var(--ink);font-size:18px;font-weight:700;letter-spacing:-.01em}.panel-lead{margin:0 0 var(--space-5);color:var(--muted);font-size:13px}.panel h3{margin:0 0 var(--space-3);color:var(--ink);font-size:15px;font-weight:700}.empty-state{border:1px dashed var(--hairline-strong);border-radius:var(--radius-md);padding:var(--space-6);background:var(--canvas-soft);color:var(--body)}.empty-title{margin:0 0 var(--space-2);color:var(--ink);font-size:16px;font-weight:700}.checklist{margin:0 0 var(--space-4);padding:var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--canvas-soft);list-style:none;color:var(--body)}.checklist li{position:relative;padding:6px 0 6px 22px}.checklist li:before{content:"";position:absolute;left:0;top:12px;width:8px;height:8px;border-radius:50%;background:var(--orange);opacity:.75}.kv{display:grid;gap:var(--space-1);margin:0}.kv div{display:grid;grid-template-columns:9rem 1fr;gap:var(--space-3);padding:10px 0;border-bottom:1px solid var(--hairline)}.kv div:last-child{border-bottom:none}.kv dt{color:var(--muted);font-size:12px;font-weight:600}.kv dd{margin:0;color:var(--ink);overflow-wrap:anywhere}code{font-size:.85rem;color:var(--ink)}.cmd{display:block;width:100%;margin:0;padding:var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:#f3efe8;color:var(--ink);font-size:12px;line-height:1.55;overflow-x:auto;white-space:pre-wrap;word-break:break-word;max-height:280px}.external-link{color:var(--orange);text-decoration:none;font-weight:600}.external-link:hover{text-decoration:underline}.error{color:var(--red)}.wizard-steps{display:grid;grid-template-columns:repeat(auto-fit,minmax(108px,1fr));gap:var(--space-2);list-style:none;padding:0;margin:0 0 var(--space-5)}.wizard-steps li{position:relative;min-height:52px;display:grid;align-content:center;gap:2px;border:1px solid var(--hairline);border-radius:var(--radius-md);padding:8px 10px;background:var(--canvas-soft);color:var(--muted);font-size:12px;font-weight:650;transition:border-color var(--transition-fast),background-color var(--transition-fast),color var(--transition-fast),box-shadow var(--transition-fast)}.wizard-steps li[data-done=true]{border-color:color-mix(in srgb,var(--green) 35%,var(--hairline));background:color-mix(in srgb,var(--mint) 45%,var(--surface));color:var(--green)}.wizard-steps li[data-active=true]{border-color:var(--orange);background:var(--surface);color:var(--ink);box-shadow:0 0 0 3px var(--orange-soft)}.wizard-steps .step-index{font-size:10px;font-weight:700;letter-spacing:.04em;color:var(--muted-soft)}.wizard-steps li[data-active=true] .step-index,.wizard-steps li[data-done=true] .step-index{color:inherit;opacity:.8}.wizard-steps .step-label{line-height:1.25}.field{display:grid;gap:var(--space-1);margin:0 0 var(--space-4);width:min(100%,520px)}.field span{font-size:12px;font-weight:600;color:var(--muted)}.field input,.field textarea,.field select{width:100%;border:1px solid var(--hairline-strong);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);transition:border-color var(--transition-fast),box-shadow var(--transition-fast)}.field input{min-height:40px;padding:0 var(--space-3)}.field select{min-height:40px;padding:0 var(--space-3);cursor:pointer}.field textarea{min-height:120px;padding:var(--space-3);resize:vertical}.field input:focus-visible,.field textarea:focus-visible,.field select:focus-visible{outline:none;border-color:var(--orange);box-shadow:0 0 0 3px var(--orange-soft)}.field input:disabled,.field textarea:disabled,.field select:disabled{background:var(--surface-muted);color:var(--muted);cursor:not-allowed}.workflow-hint{margin:calc(var(--space-1) * -1) 0 var(--space-3);color:var(--muted);font-size:12px}.meta-row{display:grid;gap:6px;margin:0 0 var(--space-4);padding:var(--space-3) var(--space-4);border:1px solid var(--hairline);border-radius:var(--radius-md);background:var(--canvas-soft);font-size:13px}.meta-row code{font-size:12px}.cta-row,.recovery-cta-row{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center}.cta-row{margin-top:var(--space-3)}.cta-row .cmd{flex:1 1 100%}.wizard-body{display:grid;gap:var(--space-3);justify-items:stretch;animation:panel-in .24s ease}.wizard-body>div{display:grid;gap:var(--space-3);justify-items:stretch}.wizard-body .field{width:100%;max-width:640px}.wizard-body .cta-row{margin-top:var(--space-1)}.interview-card{width:100%;padding:var(--space-5);border:1px solid var(--hairline);border-radius:var(--radius-lg);background:linear-gradient(180deg,var(--surface),var(--canvas-soft));box-shadow:var(--shadow-panel)}.interview-q{margin:0 0 var(--space-2);color:var(--ink)}.btn,button{appearance:none;display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:40px;padding:0 16px;border:1px solid var(--hairline-strong);border-radius:var(--radius-md);background:var(--surface);color:var(--ink);font-size:13px;font-weight:650;cursor:pointer;transition:border-color var(--transition-fast),background-color var(--transition-fast),color var(--transition-fast),box-shadow var(--transition-fast),transform var(--transition-fast)}.btn:hover:not(:disabled),button:hover:not(:disabled){border-color:color-mix(in srgb,var(--ink) 18%,var(--hairline-strong));background:var(--canvas-soft);outline:none}.btn:focus-visible:not(:disabled),button:focus-visible:not(:disabled){outline:none;box-shadow:0 0 0 3px var(--orange-soft)}.btn:active:not(:disabled),button:active:not(:disabled){transform:translateY(1px)}.btn:disabled,button:disabled{opacity:.48;cursor:not-allowed;transform:none}.btn-primary,button.btn-primary{border-color:var(--orange);background:var(--orange);color:#fff;box-shadow:0 6px 16px #f54e002e}.btn-primary:hover:not(:disabled),button.btn-primary:hover:not(:disabled){border-color:var(--orange-active);background:var(--orange-active);color:#fff}.btn-secondary,button.btn-secondary{border-color:var(--hairline-strong);background:var(--surface);color:var(--ink)}.btn-ghost,button.btn-ghost{border-color:transparent;background:transparent;color:var(--body)}.btn-ghost:hover:not(:disabled),button.btn-ghost:hover:not(:disabled){background:var(--surface-muted);color:var(--ink)}.recovery-cta-row{margin-top:var(--space-3)}.recovery-cta-row button[disabled]{opacity:.4}@keyframes flash-in{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}@keyframes panel-in{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}@media(max-width:800px){.console-shell{width:min(100% - 24px,1120px);padding-top:var(--space-5)}.console-header{grid-template-columns:1fr}.nav-link-external{margin-left:0}.kv div{grid-template-columns:1fr}.wizard-steps{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}
@@ -3,9 +3,9 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Loop Operator Console</title>
7
- <script type="module" crossorigin src="/assets/index-CbnMgdWa.js"></script>
8
- <link rel="stylesheet" crossorigin href="/assets/index-Dnj0RVs8.css">
6
+ <title>Loop 操作台 · Operator Console</title>
7
+ <script type="module" crossorigin src="/assets/index-KUSib7aM.js"></script>
8
+ <link rel="stylesheet" crossorigin href="/assets/index-ucIzpaGJ.css">
9
9
  </head>
10
10
  <body>
11
11
  <div id="root"></div>
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Console Happy Path workflow kinds → task.json.taskKind.
3
+ * Governance --profile stays separate (auto|minimal|standard|reviewed|supervised).
4
+ */
5
+ export const CONSOLE_WORKFLOW_KINDS = [
6
+ "standard",
7
+ "frontend-implementation",
8
+ "backend-test",
9
+ "frontend-test",
10
+ "knowledge-sync",
11
+ "knowledge-graph-bootstrap",
12
+ ];
13
+ /** Primary Happy Path choices shown in the create-task selector. */
14
+ export const CONSOLE_PRIMARY_WORKFLOWS = [
15
+ {
16
+ kind: "standard",
17
+ label: "后端研发(默认 DAG)",
18
+ hint: "standard → standard-dag / 治理 profile 推断",
19
+ },
20
+ {
21
+ kind: "frontend-implementation",
22
+ label: "前端研发",
23
+ hint: "frontend-implementation → frontend-implementation 模板",
24
+ },
25
+ {
26
+ kind: "backend-test",
27
+ label: "后端测试",
28
+ hint: "backend-test → backend-test-dag(pytest 等)",
29
+ },
30
+ {
31
+ kind: "frontend-test",
32
+ label: "前端测试",
33
+ hint: "frontend-test → frontend-test-dag(Playwright CLI)",
34
+ },
35
+ ];
36
+ const KIND_SET = new Set(CONSOLE_WORKFLOW_KINDS);
37
+ export function isConsoleWorkflowKind(value) {
38
+ return KIND_SET.has(value);
39
+ }
40
+ export function normalizeConsoleWorkflowKind(value, fallback = "standard") {
41
+ const token = (value ?? "").trim().split(/\s+/)[0] ?? "";
42
+ return isConsoleWorkflowKind(token) ? token : fallback;
43
+ }
44
+ export function consoleWorkflowKindPromptList() {
45
+ return CONSOLE_WORKFLOW_KINDS.join(" | ");
46
+ }
@@ -4,6 +4,9 @@ import path from "node:path";
4
4
  import YAML from "yaml";
5
5
  import { writeTaskConfig, writeTaskArtifactFile } from "../../infrastructure/harness/task-store.js";
6
6
  import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
7
+ import { adoptTaskContract } from "../../task/contract/adopt.js";
8
+ import { observeTaskContractState } from "../../task/contract/observe.js";
9
+ import { sha256Utf8 } from "../../task/contract/hash.js";
7
10
  import { resolveLoopAgentProfile } from "../profile-mapping.js";
8
11
  import { mapRiskLevelToComplexity } from "../task-spec/complexity-mapping.js";
9
12
  import { resolveWorkflow } from "../task-spec/workflow-routing.js";
@@ -89,6 +92,11 @@ export async function materializeTaskSpec(options) {
89
92
  }), "utf-8");
90
93
  await writeFile(sourcePaths.constraintsPath, renderConstraintsMarkdown(options.taskSpec), "utf-8");
91
94
  await writeFile(sourcePaths.taskYamlPath, YAML.stringify(options.taskSpec), "utf-8");
95
+ await ensureMaterializedTaskContractManaged({
96
+ repoRoot: options.repoRoot,
97
+ taskId: harnessTaskId,
98
+ taskSpecPath: options.taskSpecPath,
99
+ });
92
100
  const profileMapping = resolveLoopAgentProfile(options.taskSpec);
93
101
  const manifest = {
94
102
  schemaVersion: 1,
@@ -138,6 +146,32 @@ function slugify(value) {
138
146
  .replace(/-{2,}/g, "-");
139
147
  }
140
148
  /** Exported for unit tests: derived contract must point back to immutable references. */
149
+ async function ensureMaterializedTaskContractManaged(input) {
150
+ const state = await observeTaskContractState(input.repoRoot, input.taskId);
151
+ if (state.effectiveStatus === "managed")
152
+ return;
153
+ if (state.effectiveStatus === "transaction-incomplete") {
154
+ throw new Error(`task contract transaction incomplete for ${input.taskId}; recover before materializing`);
155
+ }
156
+ if (!state.observedCanonicalHash) {
157
+ throw new Error(`unable to observe task contract hash for ${input.taskId}: ${state.observationErrors.map((error) => error.message).join("; ")}`);
158
+ }
159
+ const expectedRevision = state.ref?.revision ?? 0;
160
+ const requestPayload = JSON.stringify({
161
+ taskId: input.taskId,
162
+ taskSpecPath: input.taskSpecPath,
163
+ expectedRevision,
164
+ observedCanonicalHash: state.observedCanonicalHash,
165
+ });
166
+ await adoptTaskContract({
167
+ repoRoot: input.repoRoot,
168
+ taskId: input.taskId,
169
+ expectedRevision,
170
+ expectedObservedHash: state.observedCanonicalHash,
171
+ requestId: `worker-materialize-${sha256Utf8(requestPayload).slice(0, 24)}`,
172
+ requestPayloadSha256: sha256Utf8(requestPayload),
173
+ });
174
+ }
141
175
  export function renderRequirementMarkdown(taskSpec, options = {}) {
142
176
  const lines = [
143
177
  `# ${taskSpec.title}`,
@@ -12,6 +12,7 @@ import { getTaskPoolRoot } from "../pool/run-store.js";
12
12
  import { isAllowedArtifactTextPath, resolveArtifactPath, resolveRepoFilePreview, toRepoRelativeArtifactPath, } from "./paths.js";
13
13
  import { extractSpecEvidence, extractSpecReadContent, } from "./spec-evidence.js";
14
14
  import { buildObserveHealthV1 } from "./health.js";
15
+ import { getTaskDir } from "../../task/runtime.js";
15
16
  const ARTIFACT_PREVIEW_MAX_BYTES = 64 * 1024;
16
17
  export function createObserveSnapshotCache() {
17
18
  return { expiresAt: 0 };
@@ -762,9 +763,15 @@ async function handleDagNodeSpecEvidenceFile(_req, res, match, ctx) {
762
763
  sendJson(res, 404, { error: "Path is not a bound source" });
763
764
  return;
764
765
  }
766
+ // sourceBinding.sources[].path is task-root-relative (.harness/tasks/<taskId>).
767
+ if (!isSafeObservabilityIdentifier(sourceBinding.taskId)) {
768
+ sendJson(res, 400, { error: "Invalid or unsafe path" });
769
+ return;
770
+ }
771
+ const taskRoot = getTaskDir(ctx.repoRoot, sourceBinding.taskId);
765
772
  let resolved;
766
773
  try {
767
- resolved = await resolveRepoFilePreview(ctx.repoRoot, rawPath);
774
+ resolved = await resolveRepoFilePreview(taskRoot, rawPath);
768
775
  }
769
776
  catch {
770
777
  sendJson(res, 400, { error: "Invalid or unsafe path" });
@@ -53,7 +53,7 @@
53
53
  >
54
54
  <header class="page-header dashboard-page-header">
55
55
  <div class="view-heading page-header-heading">
56
- <p class="view-kicker">OBSERVE</p>
56
+ <p class="view-kicker">Observe</p>
57
57
  <h2>运行总览</h2>
58
58
  </div>
59
59
  <div
@@ -91,7 +91,7 @@
91
91
 
92
92
  <section data-view="batch" id="view-batch" class="view" hidden>
93
93
  <div class="view-heading">
94
- <p class="view-kicker">BATCH</p>
94
+ <p class="view-kicker">Batch</p>
95
95
  <h2>批次详情</h2>
96
96
  <p>首轮失败定位:摘要、Task 诊断字段与安全证据入口同屏可见。</p>
97
97
  </div>
@@ -116,7 +116,7 @@
116
116
 
117
117
  <section data-view="run" id="view-run" class="view" hidden>
118
118
  <div class="view-heading">
119
- <p class="view-kicker">WORKER RUN</p>
119
+ <p class="view-kicker">Worker Run</p>
120
120
  <h2>运行详情</h2>
121
121
  <p>主事实 · 执行过程 · 诊断证据</p>
122
122
  </div>
@@ -157,7 +157,7 @@
157
157
 
158
158
  <section data-view="dag" id="view-dag" class="view" hidden>
159
159
  <div class="view-heading">
160
- <p class="view-kicker">AGENT DAG</p>
160
+ <p class="view-kicker">Agent DAG</p>
161
161
  <h2>DAG 详情</h2>
162
162
  </div>
163
163
  <section
@@ -180,7 +180,7 @@
180
180
 
181
181
  <section data-view="failures" id="view-failures" class="view" hidden>
182
182
  <div class="view-heading">
183
- <p class="view-kicker">ATTENTION QUEUE</p>
183
+ <p class="view-kicker">Attention Queue</p>
184
184
  <h2>异常 Task</h2>
185
185
  <p>按运行状态和建议跟进项集中处理需要人工介入的 Task。</p>
186
186
  </div>
@@ -193,7 +193,7 @@
193
193
  <section data-view="pool" id="view-pool" class="view view-pool" hidden>
194
194
  <header class="page-header pool-page-header">
195
195
  <div class="view-heading page-header-heading">
196
- <p class="view-kicker">TASK POOL</p>
196
+ <p class="view-kicker">Task Pool</p>
197
197
  <h2>资源池总览</h2>
198
198
  </div>
199
199
  <div id="pool-toolbar" class="pool-toolbar" aria-label="刷新控制">
@@ -268,7 +268,7 @@
268
268
 
269
269
  <section data-view="task" id="view-task" class="view" hidden>
270
270
  <div class="view-heading">
271
- <p class="view-kicker">WORKER TASK</p>
271
+ <p class="view-kicker">Worker Task</p>
272
272
  <h2 id="task-title">Task 详情</h2>
273
273
  <p>查看当前事实、真实关联与有界运行历史。</p>
274
274
  </div>
@@ -297,7 +297,7 @@
297
297
 
298
298
  <section data-view="feature" id="view-feature" class="view" hidden>
299
299
  <div class="view-heading">
300
- <p class="view-kicker">FEATURE</p>
300
+ <p class="view-kicker">Feature</p>
301
301
  <h2 id="feature-title">Feature 决策详情</h2>
302
302
  <p>只读决策投影:阻塞、AC、风险、建议命令与已执行事实分区展示。</p>
303
303
  </div>