@tea-agent/loop-agent 0.25.6 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +27 -1
- package/README.md +8 -3
- package/dist/cli/command-definitions.js +25 -10
- package/dist/cli/help.js +4 -3
- package/dist/cli/program.js +43 -17
- package/dist/commands/import-prd.js +7 -2
- package/dist/commands/init.js +7 -5
- package/dist/commands/task-source-prepare.js +468 -0
- package/dist/executors/dag-pi-executor.js +66 -25
- package/dist/executors/model-routing.js +34 -18
- package/dist/executors/shell-write-guard.js +161 -25
- package/dist/governance/manifest-types.js +33 -5
- package/dist/task/source-prepare/build-draft.js +215 -0
- package/dist/task/source-prepare/completeness.js +195 -0
- package/dist/task/source-prepare/index.js +7 -0
- package/dist/task/source-prepare/parse-intent.js +373 -0
- package/dist/task/source-prepare/path-policy.js +197 -0
- package/dist/task/source-prepare/prepare.js +506 -0
- package/dist/task/source-prepare/reference-integrity.js +274 -0
- package/dist/task/source-prepare/types.js +7 -0
- package/dist/task/task-demand-routing.js +3 -1
- package/dist/worker/console/chat/model-resolver.js +15 -3
- package/dist/worker/observe/static/constants.js +3 -2
- package/dist/worker/observe/static/dag-model.js +1 -0
- package/dist/worker/observe/static/styles.css +182 -42
- package/dist/workflows/dag/lifecycle.js +40 -30
- package/dist/workflows/dag/node-execution.js +13 -0
- package/dist/workflows/dag/types.js +59 -19
- package/docs/templates/harness.schema.json +29 -7
- package/docs/templates/init-managed-agents.md +10 -5
- package/harness.json +1 -2
- package/package.json +1 -1
- package/skills/loop-agent/SKILL.md +5 -2
- package/skills/loop-agent/references/command-reference.md +17 -15
- package/skills/loop-agent/references/harness-policy.md +3 -4
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/skills/loop-agent/references/model-routing.md +2 -0
- package/skills/loop-agent/references/post-implementation-and-patterns.md +1 -1
- package/skills/loop-agent/references/source-and-plan-practice.md +3 -2
- package/skills/loop-agent/references/task-workflow.md +7 -5
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, open, readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getTaskPaths } from "../runtime.js";
|
|
5
|
+
import { readSourceManifest, } from "../source-references.js";
|
|
6
|
+
import { PREPARE_MAX_FILE_BYTES, PREPARE_MAX_PARSE_DOCS, PREPARE_MAX_TOTAL_BYTES, } from "./types.js";
|
|
7
|
+
const PARSEABLE_EXTENSIONS = new Set([".md", ".markdown", ".txt"]);
|
|
8
|
+
const PARSEABLE_ROLES = new Set(["requirement", "acceptance"]);
|
|
9
|
+
function isSafeRelativeReferencePath(materializedPath) {
|
|
10
|
+
const normalized = materializedPath.replace(/\\/g, "/").trim();
|
|
11
|
+
if (!normalized)
|
|
12
|
+
return false;
|
|
13
|
+
if (normalized.includes("\0"))
|
|
14
|
+
return false;
|
|
15
|
+
if (path.isAbsolute(normalized) || /^[a-zA-Z]:[\\/]/.test(normalized)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (normalized.split("/").some((part) => part === ".."))
|
|
19
|
+
return false;
|
|
20
|
+
if (!normalized.startsWith("references/"))
|
|
21
|
+
return false;
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
async function streamSha256AndBytes(filePath, maxBytes) {
|
|
25
|
+
const handle = await open(filePath, "r");
|
|
26
|
+
try {
|
|
27
|
+
const hash = createHash("sha256");
|
|
28
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
29
|
+
let bytes = 0;
|
|
30
|
+
while (true) {
|
|
31
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, null);
|
|
32
|
+
if (bytesRead === 0)
|
|
33
|
+
break;
|
|
34
|
+
bytes += bytesRead;
|
|
35
|
+
if (maxBytes !== undefined && bytes > maxBytes) {
|
|
36
|
+
throw Object.assign(new Error("INPUT_TOO_LARGE"), {
|
|
37
|
+
code: "INPUT_TOO_LARGE",
|
|
38
|
+
bytes,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
42
|
+
}
|
|
43
|
+
return { sha256: hash.digest("hex"), bytes };
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
await handle.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isParseableDoc(doc) {
|
|
50
|
+
const ext = path.extname(doc.materializedPath).toLowerCase();
|
|
51
|
+
if (!PARSEABLE_EXTENSIONS.has(ext))
|
|
52
|
+
return false;
|
|
53
|
+
return PARSEABLE_ROLES.has(doc.role);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Validate source-manifest + reference files before deriving a draft.
|
|
57
|
+
* Fail-closed on taskId/path/hash/symlink/size issues.
|
|
58
|
+
*/
|
|
59
|
+
export async function validateImportedPrdReferences(input) {
|
|
60
|
+
const paths = getTaskPaths(input.repoRoot, input.taskId);
|
|
61
|
+
const issues = [];
|
|
62
|
+
const documents = [];
|
|
63
|
+
const manifest = await readSourceManifest(paths.sourceDir);
|
|
64
|
+
if (!manifest) {
|
|
65
|
+
issues.push({
|
|
66
|
+
code: "MANIFEST_MISSING",
|
|
67
|
+
level: "blocking",
|
|
68
|
+
message: "source-manifest.json missing; run import-prd before --use-imported-prd",
|
|
69
|
+
path: path.join(paths.sourceDir, "source-manifest.json"),
|
|
70
|
+
});
|
|
71
|
+
return { ok: false, issues, documents, parseableDocuments: [] };
|
|
72
|
+
}
|
|
73
|
+
if (manifest.schemaVersion !== 1) {
|
|
74
|
+
issues.push({
|
|
75
|
+
code: "MANIFEST_SCHEMA",
|
|
76
|
+
level: "blocking",
|
|
77
|
+
message: `unsupported source-manifest schemaVersion: ${String(manifest.schemaVersion)}`,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (manifest.taskId !== input.taskId) {
|
|
81
|
+
issues.push({
|
|
82
|
+
code: "MANIFEST_TASK_MISMATCH",
|
|
83
|
+
level: "blocking",
|
|
84
|
+
message: `manifest.taskId "${manifest.taskId}" does not match task ${input.taskId}`,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (!Array.isArray(manifest.documents)) {
|
|
88
|
+
issues.push({
|
|
89
|
+
code: "MANIFEST_INVALID",
|
|
90
|
+
level: "blocking",
|
|
91
|
+
message: "source-manifest.documents must be an array",
|
|
92
|
+
});
|
|
93
|
+
return { ok: false, issues, documents, parseableDocuments: [] };
|
|
94
|
+
}
|
|
95
|
+
// Stable order by materializedPath (import already sorts; re-sort for safety).
|
|
96
|
+
const ordered = [...manifest.documents].sort((a, b) => a.materializedPath < b.materializedPath
|
|
97
|
+
? -1
|
|
98
|
+
: a.materializedPath > b.materializedPath
|
|
99
|
+
? 1
|
|
100
|
+
: 0);
|
|
101
|
+
let totalParseBytes = 0;
|
|
102
|
+
let parseableCount = 0;
|
|
103
|
+
for (const doc of ordered) {
|
|
104
|
+
if (!doc ||
|
|
105
|
+
typeof doc.role !== "string" ||
|
|
106
|
+
typeof doc.name !== "string" ||
|
|
107
|
+
typeof doc.materializedPath !== "string" ||
|
|
108
|
+
typeof doc.sha256 !== "string" ||
|
|
109
|
+
typeof doc.bytes !== "number") {
|
|
110
|
+
issues.push({
|
|
111
|
+
code: "MANIFEST_DOC_INVALID",
|
|
112
|
+
level: "blocking",
|
|
113
|
+
message: "manifest document missing required fields",
|
|
114
|
+
});
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (!isSafeRelativeReferencePath(doc.materializedPath)) {
|
|
118
|
+
issues.push({
|
|
119
|
+
code: "REFERENCE_PATH_UNSAFE",
|
|
120
|
+
level: "blocking",
|
|
121
|
+
message: `reference path not contained under references/: ${doc.materializedPath}`,
|
|
122
|
+
path: doc.materializedPath,
|
|
123
|
+
});
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const absolutePath = path.resolve(paths.sourceDir, doc.materializedPath);
|
|
127
|
+
const relativeToSource = path
|
|
128
|
+
.relative(paths.sourceDir, absolutePath)
|
|
129
|
+
.split(path.sep)
|
|
130
|
+
.join("/");
|
|
131
|
+
if (relativeToSource.startsWith("..") ||
|
|
132
|
+
path.isAbsolute(relativeToSource)) {
|
|
133
|
+
issues.push({
|
|
134
|
+
code: "REFERENCE_PATH_ESCAPE",
|
|
135
|
+
level: "blocking",
|
|
136
|
+
message: `reference path escapes source dir: ${doc.materializedPath}`,
|
|
137
|
+
path: doc.materializedPath,
|
|
138
|
+
});
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
let stats;
|
|
142
|
+
try {
|
|
143
|
+
stats = await lstat(absolutePath);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
issues.push({
|
|
147
|
+
code: "REFERENCE_MISSING",
|
|
148
|
+
level: "blocking",
|
|
149
|
+
message: `reference file missing: ${doc.materializedPath}`,
|
|
150
|
+
path: doc.materializedPath,
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (stats.isSymbolicLink()) {
|
|
155
|
+
issues.push({
|
|
156
|
+
code: "REFERENCE_SYMLINK",
|
|
157
|
+
level: "blocking",
|
|
158
|
+
message: `reference must be a regular file (symlink rejected): ${doc.materializedPath}`,
|
|
159
|
+
path: doc.materializedPath,
|
|
160
|
+
});
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (!stats.isFile()) {
|
|
164
|
+
issues.push({
|
|
165
|
+
code: "REFERENCE_NOT_FILE",
|
|
166
|
+
level: "blocking",
|
|
167
|
+
message: `reference is not a regular file: ${doc.materializedPath}`,
|
|
168
|
+
path: doc.materializedPath,
|
|
169
|
+
});
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
const parseable = isParseableDoc(doc);
|
|
173
|
+
if (parseable) {
|
|
174
|
+
parseableCount += 1;
|
|
175
|
+
if (parseableCount > PREPARE_MAX_PARSE_DOCS) {
|
|
176
|
+
issues.push({
|
|
177
|
+
code: "INPUT_TOO_LARGE",
|
|
178
|
+
level: "blocking",
|
|
179
|
+
message: `more than ${PREPARE_MAX_PARSE_DOCS} parseable imported docs`,
|
|
180
|
+
});
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
let hashResult;
|
|
185
|
+
try {
|
|
186
|
+
hashResult = await streamSha256AndBytes(absolutePath, parseable ? PREPARE_MAX_FILE_BYTES : undefined);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
if (error &&
|
|
190
|
+
typeof error === "object" &&
|
|
191
|
+
"code" in error &&
|
|
192
|
+
error.code === "INPUT_TOO_LARGE") {
|
|
193
|
+
issues.push({
|
|
194
|
+
code: "INPUT_TOO_LARGE",
|
|
195
|
+
level: "blocking",
|
|
196
|
+
message: `reference exceeds ${PREPARE_MAX_FILE_BYTES} bytes: ${doc.materializedPath}`,
|
|
197
|
+
path: doc.materializedPath,
|
|
198
|
+
});
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
issues.push({
|
|
202
|
+
code: "REFERENCE_READ_FAILED",
|
|
203
|
+
level: "blocking",
|
|
204
|
+
message: `failed to hash reference: ${doc.materializedPath}`,
|
|
205
|
+
path: doc.materializedPath,
|
|
206
|
+
});
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (hashResult.bytes !== doc.bytes) {
|
|
210
|
+
issues.push({
|
|
211
|
+
code: "REFERENCE_BYTES_MISMATCH",
|
|
212
|
+
level: "blocking",
|
|
213
|
+
message: `bytes mismatch for ${doc.materializedPath}: manifest=${doc.bytes} disk=${hashResult.bytes}`,
|
|
214
|
+
path: doc.materializedPath,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
if (hashResult.sha256 !== doc.sha256) {
|
|
218
|
+
issues.push({
|
|
219
|
+
code: "REFERENCE_HASH_MISMATCH",
|
|
220
|
+
level: "blocking",
|
|
221
|
+
message: `sha256 mismatch for ${doc.materializedPath}`,
|
|
222
|
+
path: doc.materializedPath,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
let content;
|
|
226
|
+
if (parseable) {
|
|
227
|
+
totalParseBytes += hashResult.bytes;
|
|
228
|
+
if (totalParseBytes > PREPARE_MAX_TOTAL_BYTES) {
|
|
229
|
+
issues.push({
|
|
230
|
+
code: "INPUT_TOO_LARGE",
|
|
231
|
+
level: "blocking",
|
|
232
|
+
message: `total parseable reference bytes exceed ${PREPARE_MAX_TOTAL_BYTES}`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
content = await readFile(absolutePath, "utf-8");
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
documents.push({
|
|
240
|
+
role: doc.role,
|
|
241
|
+
name: doc.name,
|
|
242
|
+
materializedPath: doc.materializedPath,
|
|
243
|
+
absolutePath,
|
|
244
|
+
sha256: hashResult.sha256,
|
|
245
|
+
bytes: hashResult.bytes,
|
|
246
|
+
content,
|
|
247
|
+
parseable,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
const parseableDocuments = documents.filter((doc) => doc.parseable);
|
|
251
|
+
const hasRequirement = parseableDocuments.some((doc) => doc.role === "requirement");
|
|
252
|
+
if (input.requireParseableRequirement !== false && !hasRequirement) {
|
|
253
|
+
// Still allow if caller will supply supplemental flags/text; prepare.ts decides.
|
|
254
|
+
// Here we only mark warning-level absence; prepare elevates when needed.
|
|
255
|
+
issues.push({
|
|
256
|
+
code: "NO_PARSEABLE_REQUIREMENT",
|
|
257
|
+
level: "warning",
|
|
258
|
+
message: "no parseable requirement-role document under source/references (role=requirement, .md/.txt)",
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
const blocking = issues.some((issue) => issue.level === "blocking");
|
|
262
|
+
return {
|
|
263
|
+
ok: !blocking,
|
|
264
|
+
issues,
|
|
265
|
+
documents,
|
|
266
|
+
parseableDocuments,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
export function draftReferencesFromManifest(documents) {
|
|
270
|
+
return documents.map((doc) => ({
|
|
271
|
+
role: doc.role,
|
|
272
|
+
ref: doc.materializedPath,
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
@@ -156,7 +156,7 @@ function isDocumentationOnlyClause(clause) {
|
|
|
156
156
|
if (DOCUMENTATION_ONLY_MARKERS.test(clause)) {
|
|
157
157
|
return !PRODUCT_AND_DOCUMENT_DELIVERY.test(clause);
|
|
158
158
|
}
|
|
159
|
-
return TEST_ONLY_MARKERS.test(clause) && !PRODUCT_AND_TEST_DELIVERY.test(clause);
|
|
159
|
+
return (TEST_ONLY_MARKERS.test(clause) && !PRODUCT_AND_TEST_DELIVERY.test(clause));
|
|
160
160
|
}
|
|
161
161
|
function masksExistingBackendDependency(clause) {
|
|
162
162
|
return clause
|
|
@@ -335,6 +335,8 @@ export function classifyTaskDemand(input) {
|
|
|
335
335
|
}
|
|
336
336
|
const backendDelivery = titleSignals.backendDelivery || requirementSignals.backendDelivery;
|
|
337
337
|
const frontendProjectDefaultImplementation = hasStrongFrontendProjectEvidence &&
|
|
338
|
+
frontendPath &&
|
|
339
|
+
!hasBackendTaskType &&
|
|
338
340
|
!backendDelivery &&
|
|
339
341
|
!frontendNegated &&
|
|
340
342
|
!allowedPathsOnlyCoverNonProductArtifacts;
|
|
@@ -20,6 +20,18 @@ import { readFile } from "node:fs/promises";
|
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
/** Tier keys in harness executors.pi that may override the default. */
|
|
22
22
|
const EXECUTOR_TIERS = ["LOW", "MED", "HIGH"];
|
|
23
|
+
function modelIdFromTierValue(value) {
|
|
24
|
+
if (typeof value === "string") {
|
|
25
|
+
return value && value !== "default" ? value : undefined;
|
|
26
|
+
}
|
|
27
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const model = value.model;
|
|
31
|
+
return typeof model === "string" && model && model !== "default"
|
|
32
|
+
? model
|
|
33
|
+
: undefined;
|
|
34
|
+
}
|
|
23
35
|
/**
|
|
24
36
|
* Read executors.pi.<tier> from the repo harness.json. Returns undefined when
|
|
25
37
|
* harness.json is absent or the tier is unset / "default" sentinel.
|
|
@@ -33,9 +45,9 @@ export async function readHarnessPiModelTier(repoRoot, tier = "MED") {
|
|
|
33
45
|
const pi = manifest.executors?.pi;
|
|
34
46
|
if (!pi)
|
|
35
47
|
return undefined;
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
38
|
-
return
|
|
48
|
+
const tierModel = modelIdFromTierValue(pi[tier]);
|
|
49
|
+
if (tierModel)
|
|
50
|
+
return tierModel;
|
|
39
51
|
const defaultModel = pi.defaultModel;
|
|
40
52
|
if (defaultModel && defaultModel !== "default")
|
|
41
53
|
return defaultModel;
|
|
@@ -67,8 +67,8 @@ export const STATUS_LABELS = {
|
|
|
67
67
|
done: "完成",
|
|
68
68
|
failed: "失败",
|
|
69
69
|
error: "错误",
|
|
70
|
-
partial_failed: "
|
|
71
|
-
partialfailed: "
|
|
70
|
+
partial_failed: "部分成功",
|
|
71
|
+
partialfailed: "部分成功",
|
|
72
72
|
blocked: "阻塞",
|
|
73
73
|
stale: "心跳失联",
|
|
74
74
|
reused: "复用",
|
|
@@ -104,6 +104,7 @@ export const DAG_EFFECTIVE_STATUS_LABELS = {
|
|
|
104
104
|
interrupted: "执行已中断",
|
|
105
105
|
"remote-unknown": "远端状态未知",
|
|
106
106
|
finished: "已完成",
|
|
107
|
+
partial_failed: "部分成功",
|
|
107
108
|
failed: "执行失败",
|
|
108
109
|
superseded: "任务已另行完成",
|
|
109
110
|
abandoned: "已放弃",
|
|
@@ -47,8 +47,16 @@
|
|
|
47
47
|
|
|
48
48
|
html {
|
|
49
49
|
background:
|
|
50
|
-
radial-gradient(
|
|
51
|
-
|
|
50
|
+
radial-gradient(
|
|
51
|
+
1200px 480px at 8% -10%,
|
|
52
|
+
rgb(241 216 203 / 45%),
|
|
53
|
+
transparent 55%
|
|
54
|
+
),
|
|
55
|
+
radial-gradient(
|
|
56
|
+
900px 420px at 100% 0%,
|
|
57
|
+
rgb(216 234 213 / 35%),
|
|
58
|
+
transparent 50%
|
|
59
|
+
),
|
|
52
60
|
var(--canvas);
|
|
53
61
|
min-height: 100%;
|
|
54
62
|
scroll-behavior: smooth;
|
|
@@ -62,14 +70,8 @@ body {
|
|
|
62
70
|
color: var(--ink);
|
|
63
71
|
/* Keep system-ui first for shell contract tests; Chinese fonts follow. */
|
|
64
72
|
font-family:
|
|
65
|
-
system-ui,
|
|
66
|
-
-
|
|
67
|
-
"Segoe UI",
|
|
68
|
-
"PingFang SC",
|
|
69
|
-
"Hiragino Sans GB",
|
|
70
|
-
"Noto Sans SC",
|
|
71
|
-
BlinkMacSystemFont,
|
|
72
|
-
sans-serif;
|
|
73
|
+
system-ui, -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB",
|
|
74
|
+
"Noto Sans SC", BlinkMacSystemFont, sans-serif;
|
|
73
75
|
font-size: 14px;
|
|
74
76
|
line-height: 1.5;
|
|
75
77
|
}
|
|
@@ -868,7 +870,7 @@ td:first-child,
|
|
|
868
870
|
transition: opacity var(--transition-fast);
|
|
869
871
|
}
|
|
870
872
|
/* Five-column default: right two columns open inward (left) to avoid viewport overflow. */
|
|
871
|
-
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n-1) .kpi-tip,
|
|
873
|
+
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n - 1) .kpi-tip,
|
|
872
874
|
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n) .kpi-tip {
|
|
873
875
|
left: auto;
|
|
874
876
|
right: 12px;
|
|
@@ -1114,7 +1116,10 @@ td:first-child,
|
|
|
1114
1116
|
}
|
|
1115
1117
|
.feature-decision-row {
|
|
1116
1118
|
display: grid;
|
|
1117
|
-
grid-template-columns: minmax(120px, 0.9fr) minmax(88px, 0.55fr) minmax(
|
|
1119
|
+
grid-template-columns: minmax(120px, 0.9fr) minmax(88px, 0.55fr) minmax(
|
|
1120
|
+
0,
|
|
1121
|
+
1.6fr
|
|
1122
|
+
) auto;
|
|
1118
1123
|
align-items: center;
|
|
1119
1124
|
gap: var(--space-3);
|
|
1120
1125
|
padding: 12px 14px;
|
|
@@ -1254,8 +1259,8 @@ tr.clickable:focus-within {
|
|
|
1254
1259
|
background: color-mix(in srgb, var(--red) 8%, var(--surface));
|
|
1255
1260
|
}
|
|
1256
1261
|
.badge-partial-failed {
|
|
1257
|
-
color: color-mix(in srgb, var(--
|
|
1258
|
-
background: color-mix(in srgb, var(--
|
|
1262
|
+
color: color-mix(in srgb, var(--amber) 80%, var(--ink));
|
|
1263
|
+
background: color-mix(in srgb, var(--amber) 10%, var(--surface));
|
|
1259
1264
|
}
|
|
1260
1265
|
.badge-running,
|
|
1261
1266
|
.badge-started {
|
|
@@ -1494,7 +1499,11 @@ body.is-resizing-dag-graph {
|
|
|
1494
1499
|
border: 1px solid var(--hairline);
|
|
1495
1500
|
border-radius: var(--radius-lg);
|
|
1496
1501
|
background:
|
|
1497
|
-
radial-gradient(
|
|
1502
|
+
radial-gradient(
|
|
1503
|
+
800px 280px at 0% 0%,
|
|
1504
|
+
rgb(241 216 203 / 28%),
|
|
1505
|
+
transparent 55%
|
|
1506
|
+
),
|
|
1498
1507
|
var(--canvas-soft);
|
|
1499
1508
|
scroll-behavior: auto;
|
|
1500
1509
|
box-shadow: inset 0 1px 0 rgb(255 255 255 / 60%);
|
|
@@ -2007,11 +2016,25 @@ body.is-resizing-dag-graph {
|
|
|
2007
2016
|
font-weight: 700;
|
|
2008
2017
|
}
|
|
2009
2018
|
|
|
2010
|
-
.spec-evidence-section {
|
|
2011
|
-
|
|
2012
|
-
|
|
2019
|
+
.spec-evidence-section {
|
|
2020
|
+
padding-bottom: var(--space-4);
|
|
2021
|
+
border-bottom: 1px solid var(--hairline);
|
|
2022
|
+
}
|
|
2023
|
+
.spec-evidence-section + .spec-evidence-section {
|
|
2024
|
+
margin-top: var(--space-4);
|
|
2025
|
+
}
|
|
2026
|
+
.spec-evidence-section h4 {
|
|
2027
|
+
margin: 0 0 var(--space-2);
|
|
2028
|
+
color: var(--ink);
|
|
2029
|
+
font-size: 12px;
|
|
2030
|
+
font-weight: 700;
|
|
2031
|
+
}
|
|
2013
2032
|
|
|
2014
|
-
.node-input-root {
|
|
2033
|
+
.node-input-root {
|
|
2034
|
+
display: flex;
|
|
2035
|
+
flex-direction: column;
|
|
2036
|
+
gap: var(--space-3);
|
|
2037
|
+
}
|
|
2015
2038
|
.node-input-semantics {
|
|
2016
2039
|
margin: 0;
|
|
2017
2040
|
font-size: 12px;
|
|
@@ -2084,24 +2107,141 @@ body.is-resizing-dag-graph {
|
|
|
2084
2107
|
color: var(--danger, #b42318);
|
|
2085
2108
|
font-size: 13px;
|
|
2086
2109
|
}
|
|
2087
|
-
.spec-evidence-summary {
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
.spec-evidence-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
.spec-evidence-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2110
|
+
.spec-evidence-summary {
|
|
2111
|
+
margin: var(--space-2) 0 0;
|
|
2112
|
+
color: var(--body);
|
|
2113
|
+
font-size: 12px;
|
|
2114
|
+
line-height: 1.6;
|
|
2115
|
+
}
|
|
2116
|
+
.spec-evidence-list {
|
|
2117
|
+
display: grid;
|
|
2118
|
+
gap: 6px;
|
|
2119
|
+
margin: var(--space-2) 0 0;
|
|
2120
|
+
padding: 0;
|
|
2121
|
+
list-style: none;
|
|
2122
|
+
}
|
|
2123
|
+
.spec-evidence-list li {
|
|
2124
|
+
display: flex;
|
|
2125
|
+
align-items: flex-start;
|
|
2126
|
+
gap: 6px;
|
|
2127
|
+
color: var(--body);
|
|
2128
|
+
font-size: 12px;
|
|
2129
|
+
line-height: 1.5;
|
|
2130
|
+
overflow-wrap: anywhere;
|
|
2131
|
+
}
|
|
2132
|
+
.spec-evidence-list li > i {
|
|
2133
|
+
flex-shrink: 0;
|
|
2134
|
+
margin-top: 2px;
|
|
2135
|
+
color: var(--muted);
|
|
2136
|
+
font-size: 13px;
|
|
2137
|
+
}
|
|
2138
|
+
.spec-evidence-list code {
|
|
2139
|
+
font-size: 11px;
|
|
2140
|
+
}
|
|
2141
|
+
.spec-evidence-time {
|
|
2142
|
+
margin-left: auto;
|
|
2143
|
+
color: var(--muted);
|
|
2144
|
+
font-size: 10px;
|
|
2145
|
+
font-variant-numeric: tabular-nums;
|
|
2146
|
+
white-space: nowrap;
|
|
2147
|
+
}
|
|
2148
|
+
.spec-evidence-warning {
|
|
2149
|
+
display: flex;
|
|
2150
|
+
align-items: flex-start;
|
|
2151
|
+
gap: 8px;
|
|
2152
|
+
margin-top: var(--space-4);
|
|
2153
|
+
padding: 10px 12px;
|
|
2154
|
+
border: 1px solid var(--hairline);
|
|
2155
|
+
border-left: 3px solid color-mix(in srgb, var(--amber) 60%, var(--hairline));
|
|
2156
|
+
border-radius: var(--radius-md);
|
|
2157
|
+
background: color-mix(in srgb, var(--amber) 8%, var(--surface));
|
|
2158
|
+
color: var(--body);
|
|
2159
|
+
font-size: 12px;
|
|
2160
|
+
line-height: 1.6;
|
|
2161
|
+
}
|
|
2162
|
+
.spec-evidence-warning > i {
|
|
2163
|
+
flex-shrink: 0;
|
|
2164
|
+
margin-top: 2px;
|
|
2165
|
+
color: var(--amber);
|
|
2166
|
+
}
|
|
2167
|
+
.spec-evidence-file-btn {
|
|
2168
|
+
display: flex;
|
|
2169
|
+
align-items: flex-start;
|
|
2170
|
+
gap: 6px;
|
|
2171
|
+
width: 100%;
|
|
2172
|
+
padding: 6px 8px;
|
|
2173
|
+
background: none;
|
|
2174
|
+
border: none;
|
|
2175
|
+
text-align: left;
|
|
2176
|
+
cursor: pointer;
|
|
2177
|
+
color: inherit;
|
|
2178
|
+
font: inherit;
|
|
2179
|
+
line-height: 1.5;
|
|
2180
|
+
overflow-wrap: anywhere;
|
|
2181
|
+
border-radius: var(--radius-md);
|
|
2182
|
+
}
|
|
2183
|
+
.spec-evidence-file-btn:hover,
|
|
2184
|
+
.spec-evidence-file-btn:focus-visible {
|
|
2185
|
+
background: var(--orange-soft);
|
|
2186
|
+
outline: none;
|
|
2187
|
+
}
|
|
2188
|
+
.spec-evidence-file-btn:focus-visible {
|
|
2189
|
+
box-shadow: 0 0 0 2px color-mix(in srgb, var(--orange) 35%, transparent);
|
|
2190
|
+
}
|
|
2191
|
+
.spec-evidence-detail {
|
|
2192
|
+
display: grid;
|
|
2193
|
+
gap: var(--space-3);
|
|
2194
|
+
}
|
|
2195
|
+
.spec-evidence-back {
|
|
2196
|
+
display: inline-flex;
|
|
2197
|
+
align-items: center;
|
|
2198
|
+
gap: 6px;
|
|
2199
|
+
padding: 6px 12px;
|
|
2200
|
+
border: 1px solid var(--hairline);
|
|
2201
|
+
border-radius: var(--radius-pill);
|
|
2202
|
+
background: var(--surface);
|
|
2203
|
+
color: var(--ink);
|
|
2204
|
+
font-size: 12px;
|
|
2205
|
+
font-weight: 650;
|
|
2206
|
+
cursor: pointer;
|
|
2207
|
+
}
|
|
2208
|
+
.spec-evidence-back:hover,
|
|
2209
|
+
.spec-evidence-back:focus-visible {
|
|
2210
|
+
background: var(--orange-soft);
|
|
2211
|
+
border-color: var(--orange);
|
|
2212
|
+
color: var(--orange-active);
|
|
2213
|
+
outline: none;
|
|
2214
|
+
}
|
|
2215
|
+
.spec-evidence-detail-meta {
|
|
2216
|
+
display: flex;
|
|
2217
|
+
flex-wrap: wrap;
|
|
2218
|
+
align-items: center;
|
|
2219
|
+
gap: 8px;
|
|
2220
|
+
font-size: 12px;
|
|
2221
|
+
color: var(--body);
|
|
2222
|
+
}
|
|
2223
|
+
.spec-evidence-detail-content {
|
|
2224
|
+
min-height: 0;
|
|
2225
|
+
max-width: 100%;
|
|
2226
|
+
margin: 0;
|
|
2227
|
+
padding: var(--space-3);
|
|
2228
|
+
border: 1px solid var(--hairline);
|
|
2229
|
+
border-radius: var(--radius-md);
|
|
2230
|
+
background: var(--canvas-soft);
|
|
2231
|
+
overflow-wrap: anywhere;
|
|
2232
|
+
font-size: 12px;
|
|
2233
|
+
line-height: 1.6;
|
|
2234
|
+
}
|
|
2235
|
+
.spec-evidence-detail-error {
|
|
2236
|
+
margin: 0;
|
|
2237
|
+
color: var(--red, #c00);
|
|
2238
|
+
font-size: 12px;
|
|
2239
|
+
}
|
|
2240
|
+
.spec-evidence-detail-truncated {
|
|
2241
|
+
margin: 0;
|
|
2242
|
+
color: var(--muted);
|
|
2243
|
+
font-size: 11px;
|
|
2244
|
+
}
|
|
2105
2245
|
|
|
2106
2246
|
.run-layer-process {
|
|
2107
2247
|
display: grid;
|
|
@@ -2312,10 +2452,12 @@ body.is-resizing-dag-graph {
|
|
|
2312
2452
|
background: color-mix(in srgb, var(--green) 75%, white);
|
|
2313
2453
|
}
|
|
2314
2454
|
.dag-run-timeline-dot.status-failed,
|
|
2315
|
-
.dag-run-timeline-dot.status-error
|
|
2316
|
-
.dag-run-timeline-dot.status-partial_failed {
|
|
2455
|
+
.dag-run-timeline-dot.status-error {
|
|
2317
2456
|
background: color-mix(in srgb, var(--red) 75%, white);
|
|
2318
2457
|
}
|
|
2458
|
+
.dag-run-timeline-dot.status-partial-failed {
|
|
2459
|
+
background: color-mix(in srgb, var(--amber) 75%, white);
|
|
2460
|
+
}
|
|
2319
2461
|
.dag-run-timeline-dot.status-running,
|
|
2320
2462
|
.dag-run-timeline-dot.status-started {
|
|
2321
2463
|
background: var(--orange);
|
|
@@ -2374,7 +2516,7 @@ body.is-resizing-dag-graph {
|
|
|
2374
2516
|
border-left: 0;
|
|
2375
2517
|
}
|
|
2376
2518
|
/* Three-column: reset five-column tip edges, then rightmost column opens inward. */
|
|
2377
|
-
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n-1) .kpi-tip,
|
|
2519
|
+
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n - 1) .kpi-tip,
|
|
2378
2520
|
.kpi-grid:not(.pool-health-grid) > .kpi-card:nth-child(5n) .kpi-tip {
|
|
2379
2521
|
left: 12px;
|
|
2380
2522
|
right: auto;
|
|
@@ -2662,8 +2804,6 @@ button.copy-command:focus-visible {
|
|
|
2662
2804
|
margin: 0.15rem 0 0.35rem;
|
|
2663
2805
|
}
|
|
2664
2806
|
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
2807
|
/* R5: unified view states + information hierarchy */
|
|
2668
2808
|
.view-state {
|
|
2669
2809
|
border-radius: var(--radius-lg);
|