fix: 收敛性能采集控制面输出
This commit is contained in:
@@ -165,14 +165,15 @@ function renderWebObserveStatusTable(value: Record<string, unknown>): string {
|
|||||||
] : []),
|
] : []),
|
||||||
...(Object.keys(exactCommand).length > 0 ? [
|
...(Object.keys(exactCommand).length > 0 ? [
|
||||||
"Exact command:",
|
"Exact command:",
|
||||||
webObserveTable(["COMMAND", "TYPE", "PHASE", "OK", "STATUS", "REPORT_SHA", "DETAIL"], [[
|
webObserveTable(["COMMAND", "TYPE", "PHASE", "OK", "STATUS", "PROFILE_SHA", "REPORT_SHA", "DETAIL"], [[
|
||||||
webObserveShort(webObserveText(exactCommand.commandId), 40),
|
webObserveShort(webObserveText(exactCommand.commandId), 40),
|
||||||
exactCommand.type,
|
exactCommand.type,
|
||||||
exactCommand.phase,
|
exactCommand.phase,
|
||||||
exactCommand.ok,
|
exactCommand.ok,
|
||||||
exactResult?.status,
|
exactResult?.status,
|
||||||
|
webObserveShort(webObserveText(exactResult?.profileSha256 ?? exactErrorDetails?.profileSha256), 32),
|
||||||
webObserveShort(webObserveText(exactResult?.reportSha256 ?? exactErrorDetails?.reportSha256), 32),
|
webObserveShort(webObserveText(exactResult?.reportSha256 ?? exactErrorDetails?.reportSha256), 32),
|
||||||
webObserveShort(webObserveText(exactError.message ?? exactResult?.reportPath ?? exactErrorDetails?.reportPath), 96),
|
webObserveShort(webObserveText(exactError.message ?? exactResult?.reportPath ?? exactErrorDetails?.reportPath ?? exactResult?.profilePath ?? exactErrorDetails?.profilePath), 96),
|
||||||
]]),
|
]]),
|
||||||
"",
|
"",
|
||||||
] : []),
|
] : []),
|
||||||
@@ -436,6 +437,7 @@ function renderWebObserveCommandTable(value: Record<string, unknown>): string {
|
|||||||
const details = nullableRecord(error?.details);
|
const details = nullableRecord(error?.details);
|
||||||
const replayEvidence = result ?? details;
|
const replayEvidence = result ?? details;
|
||||||
const replayScreenshot = record(result?.screenshot ?? details?.screenshot);
|
const replayScreenshot = record(result?.screenshot ?? details?.screenshot);
|
||||||
|
const performanceCapture = observerCommand?.type === "performanceCapture" ? result ?? details : null;
|
||||||
const rawReadiness = record(error?.navigationReadiness) ?? record(details?.readiness) ?? record(details?.readinessAfterWait) ?? record(details?.readinessBeforeClick);
|
const rawReadiness = record(error?.navigationReadiness) ?? record(details?.readiness) ?? record(details?.readinessAfterWait) ?? record(details?.readinessBeforeClick);
|
||||||
const readiness = record(rawReadiness?.snapshot) ?? rawReadiness;
|
const readiness = record(rawReadiness?.snapshot) ?? rawReadiness;
|
||||||
const id = webObserveText(value.id);
|
const id = webObserveText(value.id);
|
||||||
@@ -454,6 +456,17 @@ function renderWebObserveCommandTable(value: Record<string, unknown>): string {
|
|||||||
webObserveShort(webObserveText(asyncTurn?.traceId), 24),
|
webObserveShort(webObserveText(asyncTurn?.traceId), 24),
|
||||||
webObserveShort(webObserveText(result?.mark ?? result?.currentUrl ?? observer?.error ?? observer?.queued), 80),
|
webObserveShort(webObserveText(result?.mark ?? result?.currentUrl ?? observer?.error ?? observer?.queued), 80),
|
||||||
]]),
|
]]),
|
||||||
|
...(performanceCapture !== null ? [
|
||||||
|
"",
|
||||||
|
"Performance capture:",
|
||||||
|
webObserveTable(["CAPTURE", "DURATION_MS", "PROFILE_SHA", "REPORT_SHA", "COLLECT"], [[
|
||||||
|
performanceCapture.captureId,
|
||||||
|
performanceCapture.durationMs,
|
||||||
|
webObserveShort(webObserveText(performanceCapture.profileSha256), 32),
|
||||||
|
webObserveShort(webObserveText(performanceCapture.reportSha256), 32),
|
||||||
|
`observe collect ${id} --file ${webObserveText(performanceCapture.reportPath)}`,
|
||||||
|
]]),
|
||||||
|
] : []),
|
||||||
...(observerCommand?.type === "validateWorkbenchKafkaDebugReplay" && replayEvidence !== null ? [
|
...(observerCommand?.type === "validateWorkbenchKafkaDebugReplay" && replayEvidence !== null ? [
|
||||||
"",
|
"",
|
||||||
...renderWorkbenchKafkaDebugReplayEvidence(
|
...renderWorkbenchKafkaDebugReplayEvidence(
|
||||||
|
|||||||
@@ -25,6 +25,36 @@ test("observe command renderer separates control completion from async turn term
|
|||||||
assert.match(text, /--view turn-summary --command-id cmd-fixture/u);
|
assert.match(text, /--view turn-summary --command-id cmd-fixture/u);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("observe command renderer exposes bounded performance capture artifact drill-down", () => {
|
||||||
|
const rendered = withWebObserveCommandRendered({
|
||||||
|
ok: true,
|
||||||
|
status: "control-completed",
|
||||||
|
command: "web-probe observe command",
|
||||||
|
id: "webobs-fixture",
|
||||||
|
commandId: "cmd-performance",
|
||||||
|
observerCommand: { type: "performanceCapture" },
|
||||||
|
observer: {
|
||||||
|
ok: true,
|
||||||
|
completedAt: "2026-07-13T12:00:00.000Z",
|
||||||
|
result: {
|
||||||
|
captureId: "cpu-fixture",
|
||||||
|
durationMs: 1200,
|
||||||
|
profileSha256: "sha256:profile",
|
||||||
|
reportPath: ".state/web-observe/performance/cpu-fixture/summary.json",
|
||||||
|
reportSha256: "sha256:report",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
control: { executionStatus: "completed", businessTurnTerminalImplied: false },
|
||||||
|
asyncTurn: { applicable: false, submissionStatus: "not-applicable", terminalStatus: null, terminalObserved: false },
|
||||||
|
});
|
||||||
|
const text = String(rendered.renderedText);
|
||||||
|
assert.match(text, /Performance capture:/u);
|
||||||
|
assert.match(text, /sha256:profile/u);
|
||||||
|
assert.match(text, /sha256:report/u);
|
||||||
|
assert.match(text, /observe collect webobs-fixture --file .*summary\.json/u);
|
||||||
|
assert.doesNotMatch(text, /nodes.*callFrame/u);
|
||||||
|
});
|
||||||
|
|
||||||
test("observe status renderer exposes the exact command phase and failed report evidence", () => {
|
test("observe status renderer exposes the exact command phase and failed report evidence", () => {
|
||||||
const rendered = withWebObserveStatusRendered({
|
const rendered = withWebObserveStatusRendered({
|
||||||
ok: true,
|
ok: true,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { tmpdir } from "node:os";
|
|||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { test } from "bun:test";
|
import { test } from "bun:test";
|
||||||
|
|
||||||
import { nodeWebObserveStatusNodeScript } from "./web-observe-scripts";
|
import { nodeWebObserveStatusNodeScript, nodeWebObserveWaitCommandShell } from "./web-observe-scripts";
|
||||||
|
|
||||||
async function runStatusScript(stateDir: string, commandId: string): Promise<Record<string, any>> {
|
async function runStatusScript(stateDir: string, commandId: string): Promise<Record<string, any>> {
|
||||||
const child = Bun.spawn(["bash", "-lc", nodeWebObserveStatusNodeScript(1, "NC01", "v03", commandId)], {
|
const child = Bun.spawn(["bash", "-lc", nodeWebObserveStatusNodeScript(1, "NC01", "v03", commandId)], {
|
||||||
@@ -58,6 +58,77 @@ test("observe status returns one exact completed command without prompt payloads
|
|||||||
assert.equal(status.exactCommand.valuesRedacted, true);
|
assert.equal(status.exactCommand.valuesRedacted, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("observe status summarizes performance capture artifact without embedding CPU profile", async () => {
|
||||||
|
const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-performance-status-"));
|
||||||
|
const commandId = "cmd-performance";
|
||||||
|
await mkdir(join(stateDir, "commands", "done"), { recursive: true });
|
||||||
|
await writeFile(join(stateDir, "commands", "done", `${commandId}.json`), JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
commandId,
|
||||||
|
type: "performanceCapture",
|
||||||
|
completedAt: "2026-07-13T12:00:00.000Z",
|
||||||
|
result: {
|
||||||
|
ok: true,
|
||||||
|
captureId: "cpu-fixture",
|
||||||
|
durationMs: 1200,
|
||||||
|
profile: { nodes: [{ id: 1, callFrame: { functionName: "large-profile-payload" } }] },
|
||||||
|
artifact: {
|
||||||
|
path: ".state/web-observe/performance/cpu-fixture/profile.cpuprofile",
|
||||||
|
sha256: "sha256:profile",
|
||||||
|
summaryPath: ".state/web-observe/performance/cpu-fixture/summary.json",
|
||||||
|
summarySha256: "sha256:report",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const status = await runStatusScript(stateDir, commandId);
|
||||||
|
assert.equal(status.exactCommand.result.captureId, "cpu-fixture");
|
||||||
|
assert.equal(status.exactCommand.result.profileSha256, "sha256:profile");
|
||||||
|
assert.equal(status.exactCommand.result.reportSha256, "sha256:report");
|
||||||
|
assert.equal(JSON.stringify(status).includes("large-profile-payload"), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("observe command wait emits bounded performance capture summary", async () => {
|
||||||
|
const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-performance-wait-"));
|
||||||
|
const commandId = "cmd-performance";
|
||||||
|
await mkdir(join(stateDir, "commands", "done"), { recursive: true });
|
||||||
|
await writeFile(join(stateDir, "commands", "done", `${commandId}.json`), JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
commandId,
|
||||||
|
type: "performanceCapture",
|
||||||
|
completedAt: "2026-07-13T12:00:00.000Z",
|
||||||
|
result: {
|
||||||
|
ok: true,
|
||||||
|
captureId: "cpu-fixture",
|
||||||
|
durationMs: 1200,
|
||||||
|
profile: { nodes: [{ id: 1, callFrame: { functionName: "large-profile-payload" } }] },
|
||||||
|
artifact: {
|
||||||
|
path: ".state/web-observe/performance/cpu-fixture/profile.cpuprofile",
|
||||||
|
sha256: "sha256:profile",
|
||||||
|
summaryPath: ".state/web-observe/performance/cpu-fixture/summary.json",
|
||||||
|
summarySha256: "sha256:report",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const child = Bun.spawn(["bash", "-lc", nodeWebObserveWaitCommandShell(commandId, 1000)], {
|
||||||
|
env: { ...process.env, state_dir: stateDir },
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe",
|
||||||
|
});
|
||||||
|
const [stdout, stderr, exitCode] = await Promise.all([
|
||||||
|
new Response(child.stdout).text(),
|
||||||
|
new Response(child.stderr).text(),
|
||||||
|
child.exited,
|
||||||
|
]);
|
||||||
|
assert.equal(exitCode, 0, stderr);
|
||||||
|
const summary = JSON.parse(stdout);
|
||||||
|
assert.equal(summary.status, "done");
|
||||||
|
assert.equal(summary.result.profileSha256, "sha256:profile");
|
||||||
|
assert.equal(summary.result.reportSha256, "sha256:report");
|
||||||
|
assert.equal(stdout.includes("large-profile-payload"), false);
|
||||||
|
assert.ok(Buffer.byteLength(stdout) < 2048);
|
||||||
|
});
|
||||||
|
|
||||||
test("observe status preserves bounded existing-session refresh evidence", async () => {
|
test("observe status preserves bounded existing-session refresh evidence", async () => {
|
||||||
const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-existing-refresh-status-"));
|
const stateDir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-existing-refresh-status-"));
|
||||||
const commandId = "cmd-existing-refresh";
|
const commandId = "cmd-existing-refresh";
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export function nodeWebObserveStatusNodeScript(tailLines: number, node: string,
|
|||||||
const compactTracePhase=(value)=>{const item=value&&typeof value==='object'?value:null;return item?{observed:item.observed??null,stable:item.stable??null,reason:item.reason||null,status:item.status||null,elapsedMs:item.elapsedMs??null,sampleCount:item.sampleCount??null,snapshot:compactTraceSnapshot(item.snapshot),valuesRedacted:true}:null;};
|
const compactTracePhase=(value)=>{const item=value&&typeof value==='object'?value:null;return item?{observed:item.observed??null,stable:item.stable??null,reason:item.reason||null,status:item.status||null,elapsedMs:item.elapsedMs??null,sampleCount:item.sampleCount??null,snapshot:compactTraceSnapshot(item.snapshot),valuesRedacted:true}:null;};
|
||||||
const compactTraceReadabilityEvidence=(value)=>{const item=value&&typeof value==='object'?value:null;if(!item)return{};const scope=item.scope&&typeof item.scope==='object'?item.scope:null;const disclosure=item.disclosure&&typeof item.disclosure==='object'?item.disclosure:null;const retention=item.runningRetention&&typeof item.runningRetention==='object'?item.runningRetention:null;return{startedDuringRunning:item.startedDuringRunning??null,forcedExpansion:item.forcedExpansion??null,scope:scope?{selector:short(scope.selector),conversationCount:scope.conversationCount??null,productCardCount:scope.productCardCount??null,debugPanelCountInsideConversation:scope.debugPanelCountInsideConversation??null,isolatedDebugExcluded:scope.isolatedDebugExcluded??null,valuesRedacted:true}:null,disclosure:disclosure?{openBefore:disclosure.openBefore??null,openAfterExpansion:disclosure.openAfterExpansion??null,openAfterTerminal:disclosure.openAfterTerminal??null,autoReadable:disclosure.autoReadable??null,valuesRedacted:true}:null,running:compactTracePhase(item.running),terminalArrival:compactTracePhase(item.terminalArrival),terminal:compactTracePhase(item.terminal),runningRetention:retention?{mode:retention.mode||null,passed:retention.passed??null,retainedRowCount:retention.retainedRowCount??null,runningEventCount:retention.runningEventCount??null,terminalEventCount:retention.terminalEventCount??null,runningSourceSeqMin:retention.runningSourceSeqMin??null,runningSourceSeqMax:retention.runningSourceSeqMax??null,terminalSourceSeqMin:retention.terminalSourceSeqMin??null,terminalSourceSeqMax:retention.terminalSourceSeqMax??null,valuesRedacted:true}:null,retainedRunningRowCount:item.retainedRunningRowCount??null,failures:Array.isArray(item.failures)?item.failures.slice(0,8).map((failure)=>({code:failure&&failure.code||null,message:short(failure&&failure.message),valuesRedacted:true})):null};};
|
const compactTraceReadabilityEvidence=(value)=>{const item=value&&typeof value==='object'?value:null;if(!item)return{};const scope=item.scope&&typeof item.scope==='object'?item.scope:null;const disclosure=item.disclosure&&typeof item.disclosure==='object'?item.disclosure:null;const retention=item.runningRetention&&typeof item.runningRetention==='object'?item.runningRetention:null;return{startedDuringRunning:item.startedDuringRunning??null,forcedExpansion:item.forcedExpansion??null,scope:scope?{selector:short(scope.selector),conversationCount:scope.conversationCount??null,productCardCount:scope.productCardCount??null,debugPanelCountInsideConversation:scope.debugPanelCountInsideConversation??null,isolatedDebugExcluded:scope.isolatedDebugExcluded??null,valuesRedacted:true}:null,disclosure:disclosure?{openBefore:disclosure.openBefore??null,openAfterExpansion:disclosure.openAfterExpansion??null,openAfterTerminal:disclosure.openAfterTerminal??null,autoReadable:disclosure.autoReadable??null,valuesRedacted:true}:null,running:compactTracePhase(item.running),terminalArrival:compactTracePhase(item.terminalArrival),terminal:compactTracePhase(item.terminal),runningRetention:retention?{mode:retention.mode||null,passed:retention.passed??null,retainedRowCount:retention.retainedRowCount??null,runningEventCount:retention.runningEventCount??null,terminalEventCount:retention.terminalEventCount??null,runningSourceSeqMin:retention.runningSourceSeqMin??null,runningSourceSeqMax:retention.runningSourceSeqMax??null,terminalSourceSeqMin:retention.terminalSourceSeqMin??null,terminalSourceSeqMax:retention.terminalSourceSeqMax??null,valuesRedacted:true}:null,retainedRunningRowCount:item.retainedRunningRowCount??null,failures:Array.isArray(item.failures)?item.failures.slice(0,8).map((failure)=>({code:failure&&failure.code||null,message:short(failure&&failure.message),valuesRedacted:true})):null};};
|
||||||
const compactScreenshot=(value)=>value&&typeof value==='object'?{path:value.path||null,sha256:value.sha256||null,available:value.available??null,valuesRedacted:true}:null;
|
const compactScreenshot=(value)=>value&&typeof value==='object'?{path:value.path||null,sha256:value.sha256||null,available:value.available??null,valuesRedacted:true}:null;
|
||||||
|
const compactPerformanceCapture=(value)=>{const item=value&&typeof value==='object'?value:null;const artifact=item&&item.artifact&&typeof item.artifact==='object'?item.artifact:null;if(!item&&!artifact)return{};return{captureId:item&&item.captureId||artifact&&artifact.captureId||null,durationMs:item&&item.durationMs!==undefined?item.durationMs:artifact&&artifact.durationMs!==undefined?artifact.durationMs:null,profilePath:artifact&&artifact.path||null,profileSha256:artifact&&artifact.sha256||null,reportPath:artifact&&artifact.summaryPath||null,reportSha256:artifact&&artifact.summarySha256||null,valuesRedacted:true};};
|
||||||
const commandSummary=(name)=>{
|
const commandSummary=(name)=>{
|
||||||
const root=commandDir(name); let entries=[];
|
const root=commandDir(name); let entries=[];
|
||||||
try{entries=fs.readdirSync(root).filter((item)=>item.endsWith('.json')).sort();}catch{}
|
try{entries=fs.readdirSync(root).filter((item)=>item.endsWith('.json')).sort();}catch{}
|
||||||
@@ -115,7 +116,7 @@ export function nodeWebObserveStatusNodeScript(tailLines: number, node: string,
|
|||||||
if(!parsed)continue;
|
if(!parsed)continue;
|
||||||
const result=parsed.result&&typeof parsed.result==='object'?parsed.result:null;
|
const result=parsed.result&&typeof parsed.result==='object'?parsed.result:null;
|
||||||
const error=parsed.error&&typeof parsed.error==='object'?parsed.error:null;
|
const error=parsed.error&&typeof parsed.error==='object'?parsed.error:null;
|
||||||
return {commandId:exactCommandId,phase:bucket,ok:parsed.ok??null,type:parsed.type||null,createdAt:parsed.createdAt||null,completedAt:parsed.completedAt||null,failedAt:parsed.failedAt||null,result:result?{ok:result.ok??null,status:result.status||null,profile:result.profile||null,sessionId:result.sessionId||null,traceId:result.traceId||null,traceIds:Array.isArray(result.traceIds)?result.traceIds.slice(0,4):[],runIds:Array.isArray(result.runIds)?result.runIds.slice(0,4):[],commandIds:Array.isArray(result.commandIds)?result.commandIds.slice(0,4):[],topic:result.topic||null,groupId:result.groupId||null,groupPrefix:result.groupPrefix||null,receivedCount:result.receivedCount??null,appliedCount:result.appliedCount??null,terminalStatus:result.terminalStatus||null,...compactReplayEvidence(result),...(parsed.type==='validateExistingSessionRefresh'?compactExistingSessionRefreshEvidence(result):{}),...compactTraceReadabilityEvidence(result),warmRunnerReused:result.warmRunnerReused??null,forbiddenRequestCount:result.forbiddenRequestCount??null,replayedEventCount:result.replayedEventCount??null,reportPath:result.reportPath||null,reportSha256:result.reportSha256||null,screenshot:compactScreenshot(result.screenshot),valuesRedacted:true}:null,error:error?{name:error.name||null,message:short(error.message,200),details:error.details&&typeof error.details==='object'?{profile:error.details.profile||null,...compactReplayEvidence(error.details),...(parsed.type==='validateExistingSessionRefresh'?compactExistingSessionRefreshEvidence(error.details):{}),...compactTraceReadabilityEvidence(error.details),sessionId:error.details.sessionId||null,traceId:error.details.traceId||null,traceIds:Array.isArray(error.details.traceIds)?error.details.traceIds.slice(0,4):[],topic:error.details.topic||null,groupId:error.details.groupId||null,receivedCount:error.details.receivedCount??null,appliedCount:error.details.appliedCount??null,terminalStatus:error.details.terminalStatus||null,forbiddenRequestCount:error.details.forbiddenRequestCount??null,reportPath:error.details.reportPath||null,reportSha256:error.details.reportSha256||null,screenshot:compactScreenshot(error.details.screenshot),valuesRedacted:true}:null}:null,valuesRedacted:true};
|
return {commandId:exactCommandId,phase:bucket,ok:parsed.ok??null,type:parsed.type||null,createdAt:parsed.createdAt||null,completedAt:parsed.completedAt||null,failedAt:parsed.failedAt||null,result:result?{ok:result.ok??null,status:result.status||null,sessionId:result.sessionId||null,traceId:result.traceId||null,traceIds:Array.isArray(result.traceIds)?result.traceIds.slice(0,4):[],runIds:Array.isArray(result.runIds)?result.runIds.slice(0,4):[],commandIds:Array.isArray(result.commandIds)?result.commandIds.slice(0,4):[],topic:result.topic||null,groupId:result.groupId||null,groupPrefix:result.groupPrefix||null,receivedCount:result.receivedCount??null,appliedCount:result.appliedCount??null,terminalStatus:result.terminalStatus||null,...compactReplayEvidence(result),...(parsed.type==='validateExistingSessionRefresh'?compactExistingSessionRefreshEvidence(result):{}),...compactTraceReadabilityEvidence(result),warmRunnerReused:result.warmRunnerReused??null,forbiddenRequestCount:result.forbiddenRequestCount??null,replayedEventCount:result.replayedEventCount??null,reportPath:result.reportPath||null,reportSha256:result.reportSha256||null,screenshot:compactScreenshot(result.screenshot),...(parsed.type==='performanceCapture'?compactPerformanceCapture(result):{profile:result.profile||null}),valuesRedacted:true}:null,error:error?{name:error.name||null,message:short(error.message,200),details:error.details&&typeof error.details==='object'?{...compactReplayEvidence(error.details),...(parsed.type==='validateExistingSessionRefresh'?compactExistingSessionRefreshEvidence(error.details):{}),...compactTraceReadabilityEvidence(error.details),sessionId:error.details.sessionId||null,traceId:error.details.traceId||null,traceIds:Array.isArray(error.details.traceIds)?error.details.traceIds.slice(0,4):[],topic:error.details.topic||null,groupId:error.details.groupId||null,receivedCount:error.details.receivedCount??null,appliedCount:error.details.appliedCount??null,terminalStatus:error.details.terminalStatus||null,forbiddenRequestCount:error.details.forbiddenRequestCount??null,reportPath:error.details.reportPath||null,reportSha256:error.details.reportSha256||null,screenshot:compactScreenshot(error.details.screenshot),...(parsed.type==='performanceCapture'?compactPerformanceCapture(error.details):{profile:error.details.profile||null}),valuesRedacted:true}:null}:null,valuesRedacted:true};
|
||||||
}
|
}
|
||||||
return {commandId:exactCommandId,phase:'not-found',ok:false,valuesRedacted:true};
|
return {commandId:exactCommandId,phase:'not-found',ok:false,valuesRedacted:true};
|
||||||
};
|
};
|
||||||
@@ -425,12 +426,22 @@ export function nodeWebObserveWaitCommandShell(commandId: string, waitMs: number
|
|||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
const waitSeconds = Math.max(1, Math.ceil(waitMs / 1000));
|
const waitSeconds = Math.max(1, Math.ceil(waitMs / 1000));
|
||||||
|
const printCommandSummary = `node -e ${shellQuote(`
|
||||||
|
const fs=require('fs');
|
||||||
|
const file=process.argv[1];
|
||||||
|
const phase=process.argv[2];
|
||||||
|
const parsed=JSON.parse(fs.readFileSync(file,'utf8'));
|
||||||
|
const result=parsed&&parsed.result&&typeof parsed.result==='object'?parsed.result:null;
|
||||||
|
const artifact=result&&result.artifact&&typeof result.artifact==='object'?result.artifact:null;
|
||||||
|
const summary={ok:parsed.ok!==false,queued:false,commandId:parsed.commandId||parsed.id||null,type:parsed.type||null,status:phase,completedAt:parsed.completedAt||null,failedAt:parsed.failedAt||null,result:result?{ok:result.ok??null,status:result.status||null,captureId:result.captureId||artifact&&artifact.captureId||null,durationMs:result.durationMs!==undefined?result.durationMs:artifact&&artifact.durationMs!==undefined?artifact.durationMs:null,profilePath:artifact&&artifact.path||null,profileSha256:artifact&&artifact.sha256||null,reportPath:artifact&&artifact.summaryPath||result.reportPath||null,reportSha256:artifact&&artifact.summarySha256||result.reportSha256||null,valuesRedacted:true}:null,error:parsed.error?{name:parsed.error.name||null,message:String(parsed.error.message||'').replace(/\\s+/g,' ').slice(0,200),valuesRedacted:true}:null,valuesRedacted:true};
|
||||||
|
console.log(JSON.stringify(summary));
|
||||||
|
`)}`;
|
||||||
return [
|
return [
|
||||||
`command_id=${shellQuote(commandId)}`,
|
`command_id=${shellQuote(commandId)}`,
|
||||||
`deadline=$(( $(date +%s) + ${waitSeconds} ))`,
|
`deadline=$(( $(date +%s) + ${waitSeconds} ))`,
|
||||||
"while [ \"$(date +%s)\" -le \"$deadline\" ]; do",
|
"while [ \"$(date +%s)\" -le \"$deadline\" ]; do",
|
||||||
" if [ -f \"$state_dir/commands/done/${command_id}.json\" ]; then cat \"$state_dir/commands/done/${command_id}.json\"; exit 0; fi",
|
` if [ -f "$state_dir/commands/done/\${command_id}.json" ]; then ${printCommandSummary} "$state_dir/commands/done/\${command_id}.json" done; exit 0; fi`,
|
||||||
" if [ -f \"$state_dir/commands/failed/${command_id}.json\" ]; then cat \"$state_dir/commands/failed/${command_id}.json\"; exit 2; fi",
|
` if [ -f "$state_dir/commands/failed/\${command_id}.json" ]; then ${printCommandSummary} "$state_dir/commands/failed/\${command_id}.json" failed; exit 2; fi`,
|
||||||
" sleep 1",
|
" sleep 1",
|
||||||
"done",
|
"done",
|
||||||
"printf '{\"ok\":true,\"queued\":true,\"waitTimedOut\":true,\"commandId\":\"%s\",\"stateDir\":\"%s\"}\\n' \"$command_id\" \"$state_dir\"",
|
"printf '{\"ok\":true,\"queued\":true,\"waitTimedOut\":true,\"commandId\":\"%s\",\"stateDir\":\"%s\"}\\n' \"$command_id\" \"$state_dir\"",
|
||||||
|
|||||||
@@ -110,6 +110,32 @@ test("web observe action JSON recovery accepts concrete start contract", () => {
|
|||||||
assert.equal(resolved.diagnostics.stdoutContractAccepted, true);
|
assert.equal(resolved.diagnostics.stdoutContractAccepted, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("web observe action JSON recovery accepts bounded performance capture command summary", () => {
|
||||||
|
const resolved = resolveWebObserveActionJson({
|
||||||
|
stdout: JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
queued: false,
|
||||||
|
commandId: "cmd-performance",
|
||||||
|
type: "performanceCapture",
|
||||||
|
status: "done",
|
||||||
|
result: {
|
||||||
|
captureId: "cpu-fixture",
|
||||||
|
profileSha256: "sha256:profile",
|
||||||
|
reportSha256: "sha256:report",
|
||||||
|
valuesRedacted: true,
|
||||||
|
},
|
||||||
|
valuesRedacted: true,
|
||||||
|
}),
|
||||||
|
stderr: "",
|
||||||
|
exitCode: 0,
|
||||||
|
timedOut: false,
|
||||||
|
}, "command");
|
||||||
|
|
||||||
|
assert.equal(resolved.source, "stdout");
|
||||||
|
assert.equal(resolved.parsed?.commandId, "cmd-performance");
|
||||||
|
assert.equal(resolved.diagnostics.stdoutContractAccepted, true);
|
||||||
|
});
|
||||||
|
|
||||||
test("web observe action JSON recovery reads dump wrapper for status contract", async () => {
|
test("web observe action JSON recovery reads dump wrapper for status contract", async () => {
|
||||||
const dir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-status-dump-"));
|
const dir = await mkdtemp(join(tmpdir(), "unidesk-web-observe-status-dump-"));
|
||||||
const dumpPath = join(dir, "status.json");
|
const dumpPath = join(dir, "status.json");
|
||||||
|
|||||||
Reference in New Issue
Block a user