@piagent/platform 1.48.5 → 1.49.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/CHANGELOG.md +29 -0
- package/docs/command-reference-vietnamese.md +2 -2
- package/docs/distribution-standard.md +6 -6
- package/docs/harness-conformance.md +3 -3
- package/docs/operator-manual-vietnamese.md +7 -7
- package/docs/publishing-for-teams.md +2 -0
- package/docs/release-install-policy.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs-site/architecture.html +1 -1
- package/docs-site/assets/search-en.json +1 -1
- package/docs-site/assets/search-vi.json +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/ecosystem.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/ecosystem.html +1 -1
- package/docs-site/en/index.html +15 -15
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/update.html +3 -3
- package/docs-site/en/whats-new.html +55 -30
- package/docs-site/en/windows.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +15 -15
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/releases.json +77 -0
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/update.html +3 -3
- package/docs-site/whats-new.html +55 -30
- package/docs-site/windows.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +1 -1
- package/packages/piagent-core/managed/check-questions.mjs +133 -0
- package/packages/piagent-core/managed/command-tools.mjs +2 -0
- package/packages/piagent-core/managed/fast-mode.mjs +19 -16
- package/packages/piagent-core/managed/helper-run.mjs +20 -3
- package/packages/piagent-core/managed/repository-checks.mjs +80 -0
- package/packages/piagent-core/managed/request-stream.mjs +2 -2
- package/packages/piagent-core/managed/session.mjs +2 -2
- package/packages/piagent-core/managed/workflow.mjs +41 -88
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-webui/dist/client/assets/ActivityPanel-CmjxPmNT.js +1 -0
- package/packages/piagent-webui/dist/client/assets/{App-DqN7B1JR.js → App-JrRb7jgn.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/GatewayApp-BmkqeXih.js +63 -0
- package/packages/piagent-webui/dist/client/assets/{InspectionApp-BKQiA0Ce.js → InspectionApp-9jBsgkVJ.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{SessionAgentWorkspace-ZAdRbxVA.js → SessionAgentWorkspace-RQ0N65d9.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{SessionInspectorDrawer-B2e4-1FG.js → SessionInspectorDrawer-BWyEN4bf.js} +2 -2
- package/packages/piagent-webui/dist/client/assets/{SettingsNavigation-C3MC0X4P.js → SettingsNavigation-Wx-q5QQk.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{SettingsPage-D4os5vHj.js → SettingsPage-xAt6kSyZ.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{TerminalPanel-CVYUiDkn.js → TerminalPanel-DzHdSjpN.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{WorkspaceCodeEditor-E0Zopcke.js → WorkspaceCodeEditor-CEuPTTBd.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{WorkspaceExplorer-DldLoJyf.js → WorkspaceExplorer-B_NwYf4S.js} +3 -3
- package/packages/piagent-webui/dist/client/assets/{index-CjM70M7D.css → index-De7bIeMc.css} +1 -1
- package/packages/piagent-webui/dist/client/assets/{index-DcrpSyBK.js → index-fUpioGMZ.js} +2 -2
- package/packages/piagent-webui/dist/client/index.html +2 -2
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +28 -0
- package/packages/piagent-webui/gateway/managed-projection.ts +8 -1
- package/packages/piagent-webui/gateway/session-catalog.ts +2 -2
- package/packages/piagent-webui/server/tool-call-summary.ts +13 -0
- package/packages/piagent-webui/server/transcript-projection.ts +13 -5
- package/packages/piagent-webui/shared/process-copy.ts +3 -2
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +2 -0
- package/schemas/piagent-webui/session-catalog-v1.schema.json +1 -1
- package/schemas/piagent-webui/transcript-v1.schema.json +18 -1
- package/scripts/company-terminal.mjs +2 -2
- package/scripts/runtime-advisory-exceptions.json +8 -0
- package/packages/piagent-webui/dist/client/assets/ActivityPanel-Bv-jc1s3.js +0 -1
- package/packages/piagent-webui/dist/client/assets/GatewayApp-B3--WjSd.js +0 -63
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { discoverDeliveryContext } from './delivery-context.mjs';
|
|
4
|
+
|
|
5
|
+
// Checks come from the repository: a "## Checks" section in AGENTS.md (code
|
|
6
|
+
// block lines or `command` bullets), else what its manifests imply.
|
|
7
|
+
const HEADING = /^#{1,6}\s+(checks?|verification|kiểm tra)\s*$/i;
|
|
8
|
+
export function declaredChecks(agentsFiles = []) {
|
|
9
|
+
const out = [];
|
|
10
|
+
for (const file of agentsFiles) {
|
|
11
|
+
let inside = false, fence = false;
|
|
12
|
+
for (const line of String(file.content ?? '').split('\n')) {
|
|
13
|
+
if (!fence && /^#{1,6}\s/.test(line)) { inside = HEADING.test(line.trim()); continue; }
|
|
14
|
+
if (!inside) continue;
|
|
15
|
+
if (/^\s*(```|~~~)/.test(line)) { fence = !fence; continue; }
|
|
16
|
+
const command = (fence ? line : line.match(/^\s*[-*]\s+`([^`]+)`/)?.[1] ?? '').trim();
|
|
17
|
+
if (command && !command.startsWith('#') && command.length <= 200 && !out.includes(command) && out.length < 10) out.push(command);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
export function detectedChecks(dir) {
|
|
23
|
+
const has = name => { try { const s = fs.lstatSync(path.join(dir, name)); return s.isFile() && s.size <= 1024 * 1024; } catch { return false; } };
|
|
24
|
+
const out = [];
|
|
25
|
+
if (has('package.json')) {
|
|
26
|
+
try {
|
|
27
|
+
const scripts = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).scripts ?? {};
|
|
28
|
+
const runner = has('pnpm-lock.yaml') ? 'pnpm' : has('yarn.lock') ? 'yarn' : has('bun.lock') || has('bun.lockb') ? 'bun' : 'npm';
|
|
29
|
+
for (const name of ['typecheck', 'type-check', 'lint', 'test', 'test:unit', 'test:integration', 'test:api', 'test:contract', 'test:e2e']) if (typeof scripts[name] === 'string') out.push(name === 'test' && runner === 'npm' ? 'npm test' : `${runner} run ${name}`);
|
|
30
|
+
} catch { /* not JSON: nothing implied */ }
|
|
31
|
+
}
|
|
32
|
+
if (has('go.mod')) out.push('go vet ./...', 'go test ./...');
|
|
33
|
+
if (has('Package.swift')) out.push('swift build', 'swift test');
|
|
34
|
+
if (has('Cargo.toml')) out.push('cargo check', 'cargo test');
|
|
35
|
+
if (has('pyproject.toml') || has('pytest.ini')) out.push(has('uv.lock') ? 'uv run pytest' : 'pytest');
|
|
36
|
+
if (has('pom.xml')) out.push(has('mvnw') ? './mvnw verify' : 'mvn verify');
|
|
37
|
+
if (has('build.gradle') || has('build.gradle.kts')) out.push(has('gradlew') ? './gradlew test' : 'gradle test');
|
|
38
|
+
return out.slice(0, 16);
|
|
39
|
+
}
|
|
40
|
+
export function repositoryChecks(agentsFiles, cwd, top = cwd) {
|
|
41
|
+
const declared = declaredChecks(agentsFiles);
|
|
42
|
+
if (declared.length) return { source: 'agents', commands: declared };
|
|
43
|
+
const detected = detectedChecks(cwd);
|
|
44
|
+
// Include package/repository-local checks even when the workspace root has a
|
|
45
|
+
// lint command. These are candidates, not a request to test unrelated repos.
|
|
46
|
+
const quote = value => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
47
|
+
const nested = discoverDeliveryContext(cwd).units.filter(unit => unit.path !== '.').flatMap(unit =>
|
|
48
|
+
detectedChecks(path.join(cwd, unit.path)).map(command => `cd ${quote(unit.path)} && ${command}`));
|
|
49
|
+
const commands = [...new Set([...detected, ...nested])].slice(0, 64);
|
|
50
|
+
return { source: 'detected', commands: commands.length || cwd === top ? commands : detectedChecks(top) };
|
|
51
|
+
}
|
|
52
|
+
// A command that verifies code: a declared check, or a known test, type,
|
|
53
|
+
// lint or build runner. Text tools that merely mention one do not count.
|
|
54
|
+
const RUNNER = /\b(npm|pnpm|yarn|bun)\s+(run\s+)?(test|lint|typecheck|type-check|check|build|verify)\b|\bnode\s+--test\b|\b(tsc|vitest|jest|eslint|biome|ruff|mypy|pyright|pytest|rspec|phpunit)\b|\bplaywright\s+test\b|\bpython3?\s+-m\s+(pytest|unittest|mypy)\b|\bgo\s+(test|vet|build)\b|\bcargo\s+(test|check|clippy|build)\b|\bswift\s+(test|build)\b|\bxcodebuild\b.*\b(test|build)\b|\bmake\s+(test|check|lint)\b|\b(gradle|gradlew|mvn|mvnw)\b.*\b(test|check|verify|compile|package|build)\b|\bdotnet\s+(test|build)\b|\bdeno\s+(test|check|lint)\b/;
|
|
55
|
+
// Files no check covers: documentation, pictures and documents,
|
|
56
|
+
// and Spec Kit's .specify/ state. A turn that changed only these changed no
|
|
57
|
+
// code: no check is required of it (one is still reported if it ran).
|
|
58
|
+
const NON_CODE = /\.(md|mdx|markdown|rst|adoc|asciidoc|org|png|jpe?g|gif|webp|bmp|ico|pdf|docx?|xlsx?|pptx?|odt|ods|odp|rtf)$/i;
|
|
59
|
+
export function nonCodePath(file) {
|
|
60
|
+
const name = String(file ?? '').replaceAll('\\', '/');
|
|
61
|
+
return !!name && (NON_CODE.test(name) || /(^|\/)\.specify\//.test(name) || /(^|\/)(LICENSE|NOTICE|AUTHORS|CODEOWNERS)$/.test(name));
|
|
62
|
+
}
|
|
63
|
+
export const onlyNonCode = paths => { const list = [...(paths ?? [])]; return list.length > 0 && list.every(nonCodePath); };
|
|
64
|
+
|
|
65
|
+
// A command that only reads: each part starts with a reading tool and nothing
|
|
66
|
+
// is written to a file. Anything else may change files. Git may name the
|
|
67
|
+
// repository first (git -C <folder>): a workspace folder holding several
|
|
68
|
+
// repositories is read that way.
|
|
69
|
+
const GIT_READ = String.raw`git(\s+(-C\s+('[^']*'|"[^"]*"|\S+)|-c\s+\S+|--no-pager))*\s+(status|diff|log|show|branch|ls-files|rev-parse|rev-list|blame|describe|shortlog|cat-file|for-each-ref|reflog(?!\s+(expire|delete))|remote(\s+(-v|--verbose|show|get-url)\b|$))`;
|
|
70
|
+
const READER = new RegExp(String.raw`^(ls|cat|head|tail|wc|grep|egrep|rg|find|fd|sed|awk|cut|sort|uniq|tr|echo|printf|pwd|which|stat|file|du|df|tree|jq|diff|cmp|basename|dirname|realpath|date|true|cd|${GIT_READ})(\b|$)`);
|
|
71
|
+
export function isReadOnlyCommand(command) {
|
|
72
|
+
const text = String(command ?? '').trim();
|
|
73
|
+
if (!text || />(?!>|\s*&\d|\s*\/dev\/null)|\btee\b|\bsed\s+(-[a-zA-Z]*i|--in-place)|\s-(delete|exec|execdir|fprint|ok)\b|\bxargs\b|\$\(|`/.test(text)) return false;
|
|
74
|
+
return text.split(/&&|\|\||;|\||\n/).every(part => !part.trim() || READER.test(part.trim()));
|
|
75
|
+
}
|
|
76
|
+
export function isCheckCommand(command, declared = []) {
|
|
77
|
+
const text = String(command ?? '').replace(/\s+/g, ' ').trim();
|
|
78
|
+
if (!text || /^(echo|printf|grep|rg|cat|sed|awk|head|tail|less|git|ls|find)\b/.test(text)) return false;
|
|
79
|
+
return declared.some(d => text.includes(d)) || RUNNER.test(text);
|
|
80
|
+
}
|
|
@@ -92,7 +92,7 @@ export function wrapRoleStreams(owner, runtime, role, { provider, verifyGrant })
|
|
|
92
92
|
// same company account and prompt cache; a helper names its role.
|
|
93
93
|
const conversation = role === 'main' ? conversationSession(owner) : null;
|
|
94
94
|
// Fast mode, where the member turned it on and the model serves it (fast-mode.mjs).
|
|
95
|
-
const onPayload = fastPayload(owner,
|
|
95
|
+
const onPayload = fastPayload(owner, route, options?.onPayload), fast = onPayload !== options?.onPayload;
|
|
96
96
|
// Studio's id for this request, from its answer's headers: a
|
|
97
97
|
// stream that ends here without an answer is ended there too.
|
|
98
98
|
let studioRequest = null;
|
|
@@ -118,7 +118,7 @@ export function wrapRoleStreams(owner, runtime, role, { provider, verifyGrant })
|
|
|
118
118
|
out.end(told(result)); return;
|
|
119
119
|
}
|
|
120
120
|
// Refused with the fast tier: asked again at standard speed.
|
|
121
|
-
if (fast && fastRefusal(failureCode(refusal.errorMessage))) { letGo(); declineFast(owner, failureCode(refusal.errorMessage)); continue; }
|
|
121
|
+
if (fast && fastRefusal(failureCode(refusal.errorMessage))) { letGo(); declineFast(owner, failureCode(refusal.errorMessage), role, route); continue; }
|
|
122
122
|
refused = String(refusal.errorMessage ?? '');
|
|
123
123
|
// A lapsed or replaced grant: the next ask renews it, not this share.
|
|
124
124
|
const stale = share; letGo();
|
|
@@ -482,10 +482,10 @@ export class ManagedSession {
|
|
|
482
482
|
publishHelpers() {
|
|
483
483
|
// One of each enabled helper role can run at a time.
|
|
484
484
|
const maximum = helperRoles(this.manifest).length;
|
|
485
|
-
this.session.sessionManager.appendCustomEntry('agent-watch-helpers', {active:this.helpers.size,maximum});
|
|
485
|
+
this.session.sessionManager.appendCustomEntry('agent-watch-helpers', {active:this.helpers.size,maximum,roles:[...this.helpers.keys()]});
|
|
486
486
|
// A helper runs while the main agent is quiet: listeners (the WebUI's
|
|
487
487
|
// "1/4 running") are told, since no agent event would say so.
|
|
488
|
-
this.notify({ type: 'managed_helpers', active: this.helpers.size, maximum });
|
|
488
|
+
this.notify({ type: 'managed_helpers', active: this.helpers.size, maximum, roles: [...this.helpers.keys()] });
|
|
489
489
|
}
|
|
490
490
|
notify(event) {
|
|
491
491
|
for (const listener of [...this.listeners]) { try { listener(event); } catch { /* a listener's failure is its own */ } }
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import { reviewUnavailableReason } from './patch-snapshot.mjs';
|
|
4
2
|
import { failureCode, unnamedFailureText } from '../runtime/managed-failure.mjs';
|
|
5
3
|
import { TsDiagnostics } from './ts-diagnostics.mjs';
|
|
6
4
|
import { checkpointsOf } from './checkpoints.mjs';
|
|
7
|
-
import {
|
|
5
|
+
import { declaredChecks, detectedChecks, repositoryChecks, isCheckCommand, isReadOnlyCommand, nonCodePath, onlyNonCode } from './repository-checks.mjs';
|
|
6
|
+
export { declaredChecks, detectedChecks, repositoryChecks, isCheckCommand, isReadOnlyCommand, nonCodePath, onlyNonCode };
|
|
7
|
+
import { askCheckScope, asksFullChecks, beforeCheckOrPush, checkScope } from './check-questions.mjs';
|
|
8
8
|
import { LANE_LIMITS, patchStats, changeLane, lanePolicy } from './risk-lanes.mjs';
|
|
9
9
|
import { briefCounts, decisionsText, ledgerGuard, ledgerOf, ledgerWords, reviewFocus, saveLedger, setStatus, settleLedger } from './ledger.mjs';
|
|
10
10
|
|
|
@@ -22,83 +22,6 @@ export function workflowPolicy(manifest) {
|
|
|
22
22
|
return { plan: mode(w.plan), verify: mode(w.verify), review: config.review ? mode(w.review) : 'off', maxFixLoops: loops, lanes: w.lanes !== false };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// Checks come from the repository: a "## Checks" section in AGENTS.md (code
|
|
26
|
-
// block lines or `command` bullets), else what its manifests imply.
|
|
27
|
-
const HEADING = /^#{1,6}\s+(checks?|verification|kiểm tra)\s*$/i;
|
|
28
|
-
export function declaredChecks(agentsFiles = []) {
|
|
29
|
-
const out = [];
|
|
30
|
-
for (const file of agentsFiles) {
|
|
31
|
-
let inside = false, fence = false;
|
|
32
|
-
for (const line of String(file.content ?? '').split('\n')) {
|
|
33
|
-
if (!fence && /^#{1,6}\s/.test(line)) { inside = HEADING.test(line.trim()); continue; }
|
|
34
|
-
if (!inside) continue;
|
|
35
|
-
if (/^\s*(```|~~~)/.test(line)) { fence = !fence; continue; }
|
|
36
|
-
const command = (fence ? line : line.match(/^\s*[-*]\s+`([^`]+)`/)?.[1] ?? '').trim();
|
|
37
|
-
if (command && !command.startsWith('#') && command.length <= 200 && !out.includes(command) && out.length < 10) out.push(command);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return out;
|
|
41
|
-
}
|
|
42
|
-
export function detectedChecks(dir) {
|
|
43
|
-
const has = name => { try { const s = fs.lstatSync(path.join(dir, name)); return s.isFile() && s.size <= 1024 * 1024; } catch { return false; } };
|
|
44
|
-
const out = [];
|
|
45
|
-
if (has('package.json')) {
|
|
46
|
-
try {
|
|
47
|
-
const scripts = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8')).scripts ?? {};
|
|
48
|
-
const runner = has('pnpm-lock.yaml') ? 'pnpm' : has('yarn.lock') ? 'yarn' : has('bun.lock') || has('bun.lockb') ? 'bun' : 'npm';
|
|
49
|
-
for (const name of ['typecheck', 'type-check', 'lint', 'test', 'test:unit', 'test:integration', 'test:api', 'test:contract', 'test:e2e']) if (typeof scripts[name] === 'string') out.push(name === 'test' && runner === 'npm' ? 'npm test' : `${runner} run ${name}`);
|
|
50
|
-
} catch { /* not JSON: nothing implied */ }
|
|
51
|
-
}
|
|
52
|
-
if (has('go.mod')) out.push('go vet ./...', 'go test ./...');
|
|
53
|
-
if (has('Package.swift')) out.push('swift build', 'swift test');
|
|
54
|
-
if (has('Cargo.toml')) out.push('cargo check', 'cargo test');
|
|
55
|
-
if (has('pyproject.toml') || has('pytest.ini')) out.push(has('uv.lock') ? 'uv run pytest' : 'pytest');
|
|
56
|
-
if (has('pom.xml')) out.push(has('mvnw') ? './mvnw verify' : 'mvn verify');
|
|
57
|
-
if (has('build.gradle') || has('build.gradle.kts')) out.push(has('gradlew') ? './gradlew test' : 'gradle test');
|
|
58
|
-
return out.slice(0, 16);
|
|
59
|
-
}
|
|
60
|
-
export function repositoryChecks(agentsFiles, cwd, top = cwd) {
|
|
61
|
-
const declared = declaredChecks(agentsFiles);
|
|
62
|
-
if (declared.length) return { source: 'agents', commands: declared };
|
|
63
|
-
const detected = detectedChecks(cwd);
|
|
64
|
-
// Include package/repository-local checks even when the workspace root has a
|
|
65
|
-
// lint command. These are candidates, not a request to test unrelated repos.
|
|
66
|
-
const quote = value => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
67
|
-
const nested = discoverDeliveryContext(cwd).units.filter(unit => unit.path !== '.').flatMap(unit =>
|
|
68
|
-
detectedChecks(path.join(cwd, unit.path)).map(command => `cd ${quote(unit.path)} && ${command}`));
|
|
69
|
-
const commands = [...new Set([...detected, ...nested])].slice(0, 64);
|
|
70
|
-
return { source: 'detected', commands: commands.length || cwd === top ? commands : detectedChecks(top) };
|
|
71
|
-
}
|
|
72
|
-
// A command that verifies code: a declared check, or a known test, type,
|
|
73
|
-
// lint or build runner. Text tools that merely mention one do not count.
|
|
74
|
-
const RUNNER = /\b(npm|pnpm|yarn|bun)\s+(run\s+)?(test|lint|typecheck|type-check|check|build|verify)\b|\bnode\s+--test\b|\b(tsc|vitest|jest|eslint|biome|ruff|mypy|pyright|pytest|rspec|phpunit)\b|\bplaywright\s+test\b|\bpython3?\s+-m\s+(pytest|unittest|mypy)\b|\bgo\s+(test|vet|build)\b|\bcargo\s+(test|check|clippy|build)\b|\bswift\s+(test|build)\b|\bxcodebuild\b.*\b(test|build)\b|\bmake\s+(test|check|lint)\b|\b(gradle|gradlew|mvn|mvnw)\b.*\b(test|check|verify|compile|package|build)\b|\bdotnet\s+(test|build)\b|\bdeno\s+(test|check|lint)\b/;
|
|
75
|
-
// Files no check covers: documentation, pictures and documents,
|
|
76
|
-
// and Spec Kit's .specify/ state. A turn that changed only these changed no
|
|
77
|
-
// code: no check is required of it (one is still reported if it ran).
|
|
78
|
-
const NON_CODE = /\.(md|mdx|markdown|rst|adoc|asciidoc|org|png|jpe?g|gif|webp|bmp|ico|pdf|docx?|xlsx?|pptx?|odt|ods|odp|rtf)$/i;
|
|
79
|
-
export function nonCodePath(file) {
|
|
80
|
-
const name = String(file ?? '').replaceAll('\\', '/');
|
|
81
|
-
return !!name && (NON_CODE.test(name) || /(^|\/)\.specify\//.test(name) || /(^|\/)(LICENSE|NOTICE|AUTHORS|CODEOWNERS)$/.test(name));
|
|
82
|
-
}
|
|
83
|
-
export const onlyNonCode = paths => { const list = [...(paths ?? [])]; return list.length > 0 && list.every(nonCodePath); };
|
|
84
|
-
|
|
85
|
-
// A command that only reads: each part starts with a reading tool and nothing
|
|
86
|
-
// is written to a file. Anything else may change files. Git may name the
|
|
87
|
-
// repository first (git -C <folder>): a workspace folder holding several
|
|
88
|
-
// repositories is read that way.
|
|
89
|
-
const GIT_READ = String.raw`git(\s+(-C\s+('[^']*'|"[^"]*"|\S+)|-c\s+\S+|--no-pager))*\s+(status|diff|log|show|branch|ls-files|rev-parse|rev-list|blame|describe|shortlog|cat-file|for-each-ref|reflog(?!\s+(expire|delete))|remote(\s+(-v|--verbose|show|get-url)\b|$))`;
|
|
90
|
-
const READER = new RegExp(String.raw`^(ls|cat|head|tail|wc|grep|egrep|rg|find|fd|sed|awk|cut|sort|uniq|tr|echo|printf|pwd|which|stat|file|du|df|tree|jq|diff|cmp|basename|dirname|realpath|date|true|cd|${GIT_READ})(\b|$)`);
|
|
91
|
-
export function isReadOnlyCommand(command) {
|
|
92
|
-
const text = String(command ?? '').trim();
|
|
93
|
-
if (!text || />(?!>|\s*&\d|\s*\/dev\/null)|\btee\b|\bsed\s+(-[a-zA-Z]*i|--in-place)|\s-(delete|exec|execdir|fprint|ok)\b|\bxargs\b|\$\(|`/.test(text)) return false;
|
|
94
|
-
return text.split(/&&|\|\||;|\||\n/).every(part => !part.trim() || READER.test(part.trim()));
|
|
95
|
-
}
|
|
96
|
-
export function isCheckCommand(command, declared = []) {
|
|
97
|
-
const text = String(command ?? '').replace(/\s+/g, ' ').trim();
|
|
98
|
-
if (!text || /^(echo|printf|grep|rg|cat|sed|awk|head|tail|less|git|ls|find)\b/.test(text)) return false;
|
|
99
|
-
return declared.some(d => text.includes(d)) || RUNNER.test(text);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
25
|
// update_plan: the agent's checklist, kept as a session entry so it survives
|
|
103
26
|
// compaction and "continue", and shown to the member beside the conversation.
|
|
104
27
|
export const PLAN_ENTRY = 'agent-watch-plan';
|
|
@@ -148,7 +71,7 @@ export function workflowPrompt(policy, checks) {
|
|
|
148
71
|
if (policy.plan !== 'off') lines.push(`- Plan: for work with several steps keep a checklist with update_plan and update it as you go. When the member asks for several items (plans, files, tickets), put every item in the checklist and work through all of them; do not end your turn to report between items.${policy.plan === 'require' ? ' For a complex task, file edits are refused until a plan exists. A checklist left with pending steps sends you back to the next one until every step is completed or deferred: if you cannot go on without the member, ask with ask_user. Mark a step deferred only when the member asked to leave it for later or it is no longer needed; never mark completed a step you did not do.' : ''}`);
|
|
149
72
|
if (policy.verify !== 'off') {
|
|
150
73
|
const list = checks.commands.length ? `${checks.commands.map(c => '`' + c + '`').join(', ')}${checks.source === 'detected' ? ' (detected; the repository declares none in AGENTS.md "## Checks")' : ''}` : 'none declared: choose the tests, type check or build that cover the change';
|
|
151
|
-
lines.push(`- Verify: after changing code, run the
|
|
74
|
+
lines.push(`- Verify: after changing code, run the tests that cover the change (one file, a filter, one package) and fix failures before you finish; after a failure rerun the part that failed. Do not choose the whole test suite yourself: it can take long, so the harness asks the member before a full run and before a push. Repository checks: ${list}.${policy.verify === 'require' ? ' The turn does not end until a check has passed on the final code.' : ''} Only the member may skip the checks: when they ask to, call waive_checks with their words; never skip them on your own.`);
|
|
152
75
|
}
|
|
153
76
|
// What each severity asks: blocking is fixed or argued, major decided, minor only listed.
|
|
154
77
|
const severities = ' Blocking findings: fix them, or reject one with resolve_objection and the reviewer judges your reason; a disagreement that remains goes to the member. Major findings: fix them or reject them with resolve_objection; no new review follows. Minor findings are listed for the member and need nothing.';
|
|
@@ -251,6 +174,8 @@ export class RunProcess {
|
|
|
251
174
|
// its answer and see whether the code moved).
|
|
252
175
|
ledger: [], disputes: [], verifyFails: [], claimsAsked: false, claimsAt: null, claimsRechecked: false, claimsDone: false, mainReviews: 0, minorFindings: 0,
|
|
253
176
|
sentAt: null, sentDigest: null, rejudged: false, reviewSettled: false, decideAsked: false,
|
|
177
|
+
// How much of the suite the member wants run (check-questions.mjs).
|
|
178
|
+
scopeChoice: null, scopeWords: null, fullAsked: asksFullChecks(request), pushAllowedOn: undefined,
|
|
254
179
|
// The patch's risk lane (workflow.lanes) and the main agent's own web lookups.
|
|
255
180
|
lane: null, laneStats: null, laneDigest: null, webLookups: 0 });
|
|
256
181
|
}
|
|
@@ -275,17 +200,25 @@ export class RunProcess {
|
|
|
275
200
|
async afterTool(name, args, ok, digestOf) {
|
|
276
201
|
if (['write', 'edit'].includes(name) && ok) this.edited = true;
|
|
277
202
|
if (name === 'bash' && isCheckCommand(args?.command, this.checks.commands)) {
|
|
203
|
+
const scope = checkScope(args.command, this.checks.commands), digest = await digestOf(), last = this.lastCheck;
|
|
278
204
|
this.checksRun += 1; if (!ok) this.checksFailed += 1;
|
|
279
|
-
|
|
205
|
+
// A lint after a partial test run does not make it the whole suite.
|
|
206
|
+
this.lastCheck = { ok, digest, mutations: this.mutations, scope: scope ?? (last?.scope === 'partial' && last.digest === digest ? 'partial' : 'other') };
|
|
207
|
+
// The member's "run it all" covers one run; after a fix they are asked again.
|
|
208
|
+
if (scope === 'suite' && this.scopeChoice === 'full') this.scopeChoice = null;
|
|
280
209
|
// A command may change files, so it counts. Without a git repository to
|
|
281
210
|
// compare the code against, one that only reads does not: counting every
|
|
282
211
|
// command reported code changes in turns that only read and checked.
|
|
283
212
|
} else if (['write', 'edit'].includes(name) && ok || name === 'bash' && (this.startDigest !== null || !isReadOnlyCommand(args?.command))) this.mutations += 1;
|
|
284
213
|
}
|
|
285
|
-
|
|
214
|
+
passedOn(digest) {
|
|
286
215
|
const c = this.lastCheck;
|
|
287
216
|
return !!c?.ok && (digest !== null && c.digest !== null ? c.digest === digest : c.mutations === this.mutations);
|
|
288
217
|
}
|
|
218
|
+
partOnlyOn(digest) { return this.passedOn(digest) && this.lastCheck.scope === 'partial'; }
|
|
219
|
+
suitePassedOn(digest) { return this.passedOn(digest) && this.lastCheck.scope === 'suite'; }
|
|
220
|
+
// A partial run counts unless the member wants the whole suite.
|
|
221
|
+
verifiedOn(digest) { return this.passedOn(digest) && !(this.partOnlyOn(digest) && (this.fullAsked || this.scopeChoice === 'full')); }
|
|
289
222
|
// Version 2 (a broker advertising "process-v2") adds whether code was
|
|
290
223
|
// changed without the required plan and how many calls named no tool.
|
|
291
224
|
// Version 3 adds helper objections, those the agent answered and the
|
|
@@ -351,10 +284,26 @@ export async function completionGate(managed, run, signal) {
|
|
|
351
284
|
// The member declined the approval a check needed (a JDK, Docker, the
|
|
352
285
|
// network): asking again would only repeat the same answer.
|
|
353
286
|
if (policy.verify === 'require' && !run.verified && run.approvalDeclined && run.fixLoops > 0) verifyGaveUp = true;
|
|
354
|
-
|
|
287
|
+
// "Do not run" covered the code it was given for; new code is asked about again.
|
|
288
|
+
if (run.scopeChoice === 'skip' && run.waivedDigest !== digest) run.scopeChoice = null;
|
|
289
|
+
// How much to test is the member's call; asked once, not while a check is failing.
|
|
290
|
+
if (policy.verify === 'require' && !run.docsOnly && !verifyGaveUp && run.waivedDigest !== digest && !run.scopeChoice && !run.fullAsked && run.lastCheck?.ok !== false && (!run.verified || run.partOnlyOn(digest))) {
|
|
291
|
+
const answer = await askCheckScope(managed, { partPassed: run.partOnlyOn(digest), signal });
|
|
292
|
+
if (stopped()) { interrupted = true; break; }
|
|
293
|
+
if (answer) {
|
|
294
|
+
run.scopeChoice = answer.choice; run.scopeWords = answer.words ?? null;
|
|
295
|
+
if (answer.choice === 'skip') run.waivedDigest = digest; // the same record as waive_checks: this exact code, reported unverified
|
|
296
|
+
run.verified = run.verifiedOn(digest);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (policy.verify === 'require' && (!run.verified || run.scopeWords) && !run.docsOnly && !verifyGaveUp && run.waivedDigest !== digest) {
|
|
355
300
|
run.fixLoops += 1;
|
|
356
301
|
const list = run.checks.commands.length ? run.checks.commands.map(c => '`' + c + '`').join(', ') : 'the tests, type check or build that cover the change';
|
|
357
|
-
|
|
302
|
+
const words = run.scopeWords; run.scopeWords = null;
|
|
303
|
+
const ask = words ? `The member answered about testing: "${String(words).slice(0, 500)}". Follow that and fix what fails.`
|
|
304
|
+
: run.scopeChoice === 'partial' ? 'Run only the tests that cover your change (one file, a filter, one package), not the whole suite, and fix what fails.'
|
|
305
|
+
: `Run the checks that cover your change (${list}) and fix what fails.${run.fullAsked || run.scopeChoice === 'full' ? ' The member wants the whole suite: a partial run does not count.' : ''}`;
|
|
306
|
+
await send(`Harness process check: code changed in this turn, but no check has passed on the current code. ${ask} If a check cannot run because the machine lacks something (a runtime such as a JDK, Docker, a database or another service, the network), request the approval that makes it run (run_with_network, run_with_docker, start_docker, run_on_host) or ask the member with ask_user, then run it; install or start nothing without that approval. If it fails for a reason unrelated to your change, say so plainly.`,
|
|
358
307
|
{ phase: 'verify', loop: run.fixLoops, maxLoops: policy.maxFixLoops, lastCheckFailed: run.lastCheck?.ok === false });
|
|
359
308
|
continue;
|
|
360
309
|
}
|
|
@@ -363,8 +312,11 @@ export async function completionGate(managed, run, signal) {
|
|
|
363
312
|
const answer = run.sentAt !== null ? answerSince(run.sentAt) + decisionsText(run, 'review') : '';
|
|
364
313
|
// After a fix the reviewer checks its blocking findings and the files changed since.
|
|
365
314
|
const focus = run.sentAt !== null ? await reviewFocus(managed) : null, previous = (review?.findings ?? []).filter(f => f.severity === 'blocking').map((f, i) => `${i + 1}. ${findingText(f)}`).join('\n');
|
|
366
|
-
const brief = `Review the current patch against the member's request.\n\nChecks: ${run.verified ? 'a repository check passed on this code' : 'no check has passed on this code'}.${answer ? `\n\nThe main agent's answer to your previous blocking findings (the code changed after it):\n${answer}` : ''}${focus ? `\n\nSince your last review only these files changed: ${focus.files.join(', ')}. Judge whether each previous blocking finding is fixed (repeat it when it is not) and review the changes in these files; the rest of the patch was reviewed and has not changed.${previous ? `\nYour previous blocking findings:\n${previous}` : ''}` : ''}`;
|
|
315
|
+
const brief = `Review the current patch against the member's request.\n\nChecks: ${run.partOnlyOn(digest) ? 'only part of the test suite passed on this code' : run.verified ? 'a repository check passed on this code' : 'no check has passed on this code'}.${answer ? `\n\nThe main agent's answer to your previous blocking findings (the code changed after it):\n${answer}` : ''}${focus ? `\n\nSince your last review only these files changed: ${focus.files.join(', ')}. Judge whether each previous blocking finding is fixed (repeat it when it is not) and review the changes in these files; the rest of the patch was reviewed and has not changed.${previous ? `\nYour previous blocking findings:\n${previous}` : ''}` : ''}`;
|
|
367
316
|
managed.reviewFocus = focus;
|
|
317
|
+
// The answer so far is not the result yet: the dashboard marks it so.
|
|
318
|
+
// An entry, not a message: the model has nothing to do with it.
|
|
319
|
+
session.sessionManager.appendCustomEntry('agent-watch-review-start', {});
|
|
368
320
|
try { await managed.delegate({ role: 'review', task: brief }, signal, { harness: true }); }
|
|
369
321
|
catch (error) {
|
|
370
322
|
run.reviewUnavailable = !stopped(); run.reviewUnavailableReason = reviewUnavailableReason(error?.message); interrupted = stopped();
|
|
@@ -443,8 +395,8 @@ export async function completionGate(managed, run, signal) {
|
|
|
443
395
|
if (!run.changed && !run.disputes.length && !ledgerOf(run).length) return;
|
|
444
396
|
const finalPlan = currentPlan(session.sessionManager), planOpen = run.planUpdated ? finalPlan?.plan?.filter(p => p.status === 'pending' || p.status === 'in_progress').length ?? 0 : 0;
|
|
445
397
|
const status = { verified: run.verified, reviewed: run.reviewed, blockingOpen: run.blockingOpen, checks: run.checksRun, fixLoops: run.fixLoops,
|
|
446
|
-
...(planOpen ? { planOpen } : {}), ...(run.planSkipped ? { planSkipped: true } : {}), ...(!run.verified && run.waivedDigest === digest ? { checksWaived: true } : {}), ...(run.disputes.length ? { disputes: run.disputes.length } : {}), ...(run.changed ? {} : { unchanged: true }) };
|
|
447
|
-
const words = [...(run.changed ? [run.verified ? 'a check passed on the final code' : policy.verify === 'off' ? null : run.docsOnly ? 'only documentation changed: no check needed' : run.waivedDigest === digest ? 'no check passed on the final code: the member waived the checks' : 'no check passed on the final code',
|
|
398
|
+
...(planOpen ? { planOpen } : {}), ...(run.planSkipped ? { planSkipped: true } : {}), ...(!run.verified && run.waivedDigest === digest ? { checksWaived: true } : {}), ...(run.verified && run.partOnlyOn(digest) ? { partOnly: true } : {}), ...(run.disputes.length ? { disputes: run.disputes.length } : {}), ...(run.changed ? {} : { unchanged: true }) };
|
|
399
|
+
const words = [...(run.changed ? [run.partOnlyOn(digest) && run.verified ? 'only part of the test suite passed on the final code' : run.verified ? 'a check passed on the final code' : policy.verify === 'off' ? null : run.docsOnly ? 'only documentation changed: no check needed' : run.waivedDigest === digest ? 'no check passed on the final code: the member waived the checks' : 'no check passed on the final code',
|
|
448
400
|
run.reviewed ? (run.blockingOpen ? `review: ${run.blockingOpen} blocking issue(s) still open` : 'review: no blocking issue') : policy.review === 'off' ? null : run.reviewUnavailable ? `review could not run (${run.reviewUnavailableReason})` : 'not reviewed',
|
|
449
401
|
run.planSkipped ? 'code was changed before the required plan existed' : null, planOpen ? `plan: ${planOpen} step(s) not completed` : null] : []),
|
|
450
402
|
run.disputes.length ? `${run.disputes.length} disagreement(s) between the main agent and a subagent go to the member` : null, ledgerWords(run)].filter(Boolean);
|
|
@@ -465,6 +417,7 @@ export function processEditTools(session, tools) {
|
|
|
465
417
|
session.run?.beforeEdit(tool.name, plan, session.mainAnswers);
|
|
466
418
|
// An open premise objection is resolved before files change (ledger.mjs).
|
|
467
419
|
if (tool.name !== 'bash' || !isReadOnlyCommand(args[1]?.command)) ledgerGuard(session.run, session.mainAnswers);
|
|
420
|
+
if (tool.name === 'bash') await beforeCheckOrPush(session, args[1]?.command, args[4], args[2]);
|
|
468
421
|
// While a required plan is missing, a command that changes files
|
|
469
422
|
// counts as an edit: the code is compared after each command.
|
|
470
423
|
const watch = tool.name === 'bash' && !!session.run?.planMissing(plan) && !session.run.unplannedChange && session.run.startDigest !== null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,r as t}from"./rolldown-runtime-hePW80VL.js";import{An as n,Rn as r}from"./vendor-DNZbaWhT.js";import{i,n as a}from"./ui-preferences-OfS30-ZV.js";import{B as o,K as s,x as c}from"./api-BQS4Dtrr.js";import{c as l}from"./view-model-CWNiS4JN.js";var u=t(r(),1);function d(e,t=`vi`){let n=e.state===`running`?`running`:e.state===`failed`?`failed`:`passed`,r=n===`running`?t===`vi`?`đang chạy`:`running`:n===`failed`?t===`vi`?`thất bại`:`failed`:t===`vi`?`hoàn tất`:`completed`,i=e.startedAt??e.finishedAt??``;return{activityRef:`live.${e.toolCallRef}`,kind:[`bash`,`shell`,`exec`].includes(e.toolLabel)?`command`:`tool`,state:n,label:`${e.agent?`${e.agent} · `:``}${e.toolLabel} ${r}`,preview:e.detail??e.fileLabel??(t===`vi`?`Cập nhật trực tiếp từ Gateway`:`Live update from the Gateway`),toolCallId:e.toolCallRef,toolName:e.toolLabel,commandDigest:null,logRef:null,exitCode:null,exitCodeExact:!1,startedAt:i,finishedAt:n===`running`?null:e.finishedAt??i}}function f(e,t=`vi`){let n=e.state===`failed`?t===`vi`?`Lượt chạy gặp lỗi`:`Operation failed`:e.state===`blocked`?t===`vi`?`Lượt chạy bị chặn`:`Operation blocked`:e.state===`aborted`?t===`vi`?`Lượt chạy đã dừng`:`Operation stopped`:t===`vi`?`Chưa xác định kết quả lượt chạy`:`Operation outcome unknown`;return{activityRef:e.activityRef,kind:`system`,state:e.state,label:n,preview:e.reasonCode,toolCallId:null,toolName:null,commandDigest:null,logRef:null,exitCode:null,exitCodeExact:!1,startedAt:e.settledAt,finishedAt:e.settledAt}}function p(e,t,n,r=`vi`,i){let a=[...e,...t],o=new Set(a.map(e=>e.activityRef)),s=n.filter(e=>!o.has(e.activityRef)).map(e=>f(e,r));if(i===void 0)return{rows:[...e,...s,...t],terminalCount:s.length,runningCount:e.length};let c=i.filter(e=>e.state===`running`).map(e=>d(e,r));return{rows:[...c,...s,...t],terminalCount:s.length,runningCount:c.length}}function m(e,t=`vi`){return e.state===`passed`&&/ recovered$/i.test(e.label)?t===`vi`?`Đã khôi phục`:`Recovered`:e.state===`passed`&&/ warning$/i.test(e.label)?t===`vi`?`Đã xử lý cảnh báo`:`Handled warning`:e.state===`passed`&&e.exitCodeExact?`Pass · exit ${e.exitCode??0}`:e.state===`failed`&&e.exitCodeExact?`Fail · exit ${e.exitCode??`?`}`:e.state===`blocked`?`Blocked`:e.state===`aborted`?t===`vi`?`Đã dừng`:`Stopped`:e.state===`unknown`?t===`vi`?`Chưa xác định`:`Unknown`:e.state===`failed`?t===`vi`?`Thất bại`:`Failed`:e.state===`running`?t===`vi`?`Đang chạy`:`Running`:e.state.replaceAll(`-`,` `)}function h(e,t=`vi`){let n=Date.parse(e.startedAt),r=e.finishedAt?Date.parse(e.finishedAt):Date.now();if(!Number.isFinite(n)||!Number.isFinite(r)||r<n)return t===`vi`?`Thời gian chưa xác định`:`Unknown duration`;let i=Math.round((r-n)/1e3);return i<60?`${i}s`:`${Math.floor(i/60)}m ${i%60}s`}function g(e,t=[]){let n=new Map([[`main`,{agent:`main`,steps:[],total:0}]]),r=e=>{let t=n.get(e);if(t)return t;let r={agent:e,steps:[],total:0};return n.set(e,r),r};for(let t of e){if(t.helperRun){let e=r(t.helperRun.agent);e.total+=t.helperRun.total,e.steps.push(...t.helperRun.steps.map((e,n)=>({key:`${t.messageRef}:${n}`,toolName:e.toolName,target:e.target,state:`completed`,at:e.at})));continue}if(t.role!==`assistant`)continue;let e=r(`main`);for(let n of t.toolCalls)e.total+=1,e.steps.push({key:`${t.messageRef}:${n.toolCallRef}`,toolName:n.toolName,target:n.summary?.target??n.summary?.detail??null,state:n.state===`failed`||n.result?.isError?`failed`:`completed`,at:t.recordedAt})}for(let e of t){if(!e.agent||e.state===`running`)continue;let t=r(e.agent);t.total+=1,t.steps.push({key:e.toolCallRef,toolName:e.toolLabel,target:e.detail??null,state:e.state,at:e.startedAt??``})}for(let e of n.values())e.steps.sort((e,t)=>t.at.localeCompare(e.at));return[...n.values()].filter(e=>e.steps.length>0)}var _=n();function v(e,t){return e===`main`?a(t,`Main agent`,`Main agent`):`Subagent ${e}`}function y(e,t){let n=new Date(e);return Number.isFinite(n.getTime())?n.toLocaleTimeString(t===`vi`?`vi-VN`:`en-US`,{hour:`2-digit`,minute:`2-digit`,second:`2-digit`}):``}var b=5e3;function x({sessionRef:e,running:t,live:n,locale:r,empty:i}){let[o,c]=(0,u.useState)([]);(0,u.useEffect)(()=>{let n=new AbortController,r=()=>void s(e,null,200,n.signal).then(e=>c(e.items)).catch(()=>void 0);r();let i=t?window.setInterval(r,b):void 0;return()=>{n.abort(),i&&window.clearInterval(i)}},[e,t]);let d=(0,u.useMemo)(()=>g(o,n),[o,n]);return d.length?(0,_.jsx)(`div`,{className:`agent-activity-log`,children:d.map(e=>(0,_.jsxs)(`details`,{open:e.agent===`main`||t,className:`agent-activity-group`,children:[(0,_.jsxs)(`summary`,{children:[(0,_.jsx)(`strong`,{children:v(e.agent,r)}),` `,(0,_.jsxs)(`span`,{children:[e.total,` `,a(r,`lần gọi`,`calls`)]}),e.total>e.steps.length&&(0,_.jsxs)(`span`,{children:[` · `,a(r,`hiện ${e.steps.length} gần nhất`,`showing the latest ${e.steps.length}`)]})]}),e.steps.map(e=>(0,_.jsxs)(`article`,{className:`activity-row`,children:[(0,_.jsx)(`span`,{className:`activity-state tone-${l(e.state)}`,"aria-label":e.state}),(0,_.jsxs)(`div`,{className:`activity-copy`,children:[(0,_.jsx)(`div`,{children:(0,_.jsx)(`strong`,{children:e.toolName})}),e.target&&(0,_.jsx)(`p`,{children:e.target})]}),(0,_.jsx)(`div`,{className:`activity-result`,children:(0,_.jsx)(`span`,{children:y(e.at,r)})})]},e.key))]},e.agent))}):i?(0,_.jsx)(`div`,{className:`activity-empty`,children:i}):null}var S=e({ActivityPanel:()=>w});function C({activity:e,preview:t,onPreview:n,locale:r}){return(0,_.jsxs)(`article`,{className:`activity-row`,children:[(0,_.jsx)(`span`,{className:`activity-state tone-${l(e.state)}`,"aria-label":e.state}),(0,_.jsxs)(`div`,{className:`activity-copy`,children:[(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`strong`,{children:e.label}),(0,_.jsx)(`span`,{children:e.kind})]}),(0,_.jsx)(`p`,{children:e.preview||a(r,`Không có mô tả ngắn`,`No short description`)}),t&&(0,_.jsxs)(`div`,{className:`log-preview ${t.state===`error`?`error-state`:``}`,children:[t.state===`loading`?a(r,`Đang tải log ngắn…`,`Loading log preview…`):t.state===`error`?a(r,`Log preview không còn sẵn sàng.`,`Log preview is no longer available.`):t.value?.preview??`${a(r,`Không có nội dung log`,`No log content`)} · ${t.value?.reasonCode??`unavailable`}`,t.value?.truncated&&(0,_.jsx)(`small`,{children:a(r,`Preview đã được rút gọn`,`Preview truncated`)})]})]}),(0,_.jsxs)(`div`,{className:`activity-result`,children:[(0,_.jsx)(`strong`,{children:m(e,r)}),(0,_.jsx)(`span`,{children:h(e,r)}),e.logRef&&n&&(0,_.jsx)(`button`,{type:`button`,onClick:n,"aria-expanded":!!t,children:t?a(r,`Ẩn log`,`Hide log`):a(r,`Xem log`,`View log`)})]})]})}function w({snapshot:e,sessionRef:t,terminalActivities:n=[],liveActivities:r}){let{locale:s}=i(),[l,d]=(0,u.useState)({}),f=e?.activity.running??[],m=e?.activity.recent??[],{rows:h,terminalCount:g,runningCount:v}=p(f,m,n,s,r),y=e=>{if(l[e.activityRef]){d(t=>({...t,[e.activityRef]:void 0}));return}d(t=>({...t,[e.activityRef]:{state:`loading`}})),(t?o(t,e.activityRef):c(e.activityRef)).then(t=>d(n=>({...n,[e.activityRef]:{state:`ready`,value:t}}))).catch(()=>d(t=>({...t,[e.activityRef]:{state:`error`}})))};return(0,_.jsxs)(`section`,{className:`activity-panel surface`,"aria-labelledby":`activity-title`,children:[(0,_.jsxs)(`header`,{className:`panel-heading`,children:[(0,_.jsxs)(`div`,{children:[(0,_.jsx)(`p`,{className:`section-kicker`,children:`Activity`}),(0,_.jsx)(`h2`,{id:`activity-title`,children:a(s,`Tool, command và kết quả lượt chạy`,`Tools, commands, and operation outcomes`)})]}),(0,_.jsxs)(`div`,{children:[(0,_.jsxs)(`span`,{className:`running-count`,children:[v,` `,a(s,`đang chạy`,`running`)]}),(0,_.jsxs)(`span`,{children:[m.length+g,` `,a(s,`gần đây`,`recent`)]})]})]}),(0,_.jsxs)(`div`,{className:`activity-list`,children:[h.map(e=>(0,_.jsx)(C,{activity:e,preview:l[e.activityRef],onPreview:e.logRef?()=>y(e):void 0,locale:s},e.activityRef)),t&&(0,_.jsx)(x,{sessionRef:t,running:!!r,live:r,locale:s,empty:h.length===0?a(s,`Chưa có tool, command hoặc kết quả lượt chạy nào trong timeline hiện tại.`,`No tools, commands, or operation outcomes in the current timeline.`):void 0}),h.length===0&&!t&&(0,_.jsx)(`div`,{className:`activity-empty`,children:a(s,`Chưa có tool, command hoặc kết quả lượt chạy nào trong timeline hiện tại.`,`No tools, commands, or operation outcomes in the current timeline.`)})]}),e?.activity.page.truncated&&(0,_.jsx)(`footer`,{className:`panel-footnote`,children:a(s,`Timeline đã được rút gọn theo giới hạn local.`,`The timeline was truncated to local limits.`)})]})}export{S as n,w as t};
|