@oqtopus-team/pi-qdash 0.1.10 → 0.1.11-beta.20
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/README.md +1 -1
- package/extensions/lib/links.ts +0 -2
- package/extensions/lib/qcal-evidence.ts +164 -0
- package/extensions/qdash.ts +90 -107
- package/extensions/tools/calibration-pipeline.ts +4 -2
- package/package.json +2 -2
- package/skills/qdash/SKILL.md +2 -3
- package/skills/qdash-calibration-agent/SKILL.md +5 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ It uses [`@oqtopus-team/qdash-client`](https://www.npmjs.com/package/@oqtopus-te
|
|
|
16
16
|
|
|
17
17
|
## Key Features
|
|
18
18
|
|
|
19
|
-
- **Read-only investigation**: dedicated tools to inspect chips, qubits, couplings, calibration task results, task knowledge (`qdash_get_task_knowledge`), executions,
|
|
19
|
+
- **Read-only investigation**: dedicated tools to inspect chips, qubits, couplings, calibration task results, task knowledge (`qdash_get_task_knowledge`), executions, and Forum posts. Secrets are redacted from tool output.
|
|
20
20
|
- **Dashboards and insights**: compact dashboards, triage overviews, target-level incident reports, degradation reports, and wiring insights.
|
|
21
21
|
- **Agent calibration workflow**: create agent sessions, submit and track agent actions, and commit/apply calibration candidates. Write operations are approval-gated.
|
|
22
22
|
- **Calibration pipelines**: compose a declarative pipeline spec from QDash's step catalog (`qdash_get_pipeline_catalog`), validate it (`qdash_plan_pipeline`), and run it as one execution (`qdash_run_pipeline`, approval-gated).
|
package/extensions/lib/links.ts
CHANGED
|
@@ -44,12 +44,10 @@ export function qdashObjectLinks(client: QDashClient, object: Record<string, unk
|
|
|
44
44
|
const executionId = firstString(object, ["execution_id", "executionId"]);
|
|
45
45
|
const chipId = firstString(object, ["chip_id", "chipId"]);
|
|
46
46
|
const postId = firstString(object, ["post_id", "forum_post_id", "id"]);
|
|
47
|
-
const issueId = firstString(object, ["issue_id"]);
|
|
48
47
|
const sessionId = firstString(object, ["session_id", "sessionId"]);
|
|
49
48
|
if (taskId) links.task_result = qdashWebUrl(client, `/task-results/${encodeURIComponent(taskId)}`);
|
|
50
49
|
if (executionId) links.execution = qdashWebUrl(client, executionPagePath(executionId, chipId));
|
|
51
50
|
if (postId) links.forum_post = qdashWebUrl(client, forumPostPagePath(postId));
|
|
52
|
-
if (issueId) links.issue = qdashWebUrl(client, `/issues/${encodeURIComponent(issueId)}`);
|
|
53
51
|
if (sessionId) links.agent_session = qdashWebUrl(client, `/agent-sessions/${encodeURIComponent(sessionId)}`);
|
|
54
52
|
return links;
|
|
55
53
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evidence text for pi-qcaleval's `qcal_evaluate`: what a task result's
|
|
3
|
+
* measurement settings were, read from the run parameters QDash records.
|
|
4
|
+
*
|
|
5
|
+
* The evaluation model is meant to read the plot, so the context names the
|
|
6
|
+
* task, the target and the sweep (what was varied, over which range, in how
|
|
7
|
+
* many points), not the fitted results. The numbers come from the recorded
|
|
8
|
+
* parameters, so they are exact and need no hand-written summary.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** One parameter as QDash records it on a task result. */
|
|
12
|
+
export interface RecordedParameter {
|
|
13
|
+
value: unknown;
|
|
14
|
+
unit: string;
|
|
15
|
+
value_type: string;
|
|
16
|
+
description: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A swept parameter: the axis a plot was taken along. */
|
|
20
|
+
export interface Sweep {
|
|
21
|
+
name: string;
|
|
22
|
+
unit: string;
|
|
23
|
+
description: string;
|
|
24
|
+
/** How the points are spaced. */
|
|
25
|
+
spacing: "linear" | "log" | "list";
|
|
26
|
+
/** First and last point, in the parameter's unit. */
|
|
27
|
+
start: number;
|
|
28
|
+
stop: number;
|
|
29
|
+
points: number;
|
|
30
|
+
/** Spacing between points for a linear sweep. */
|
|
31
|
+
step?: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** The recorded parameters of a task result, as a name → record map. */
|
|
35
|
+
export function recordedParameters(value: unknown): Record<string, RecordedParameter> {
|
|
36
|
+
const output: Record<string, RecordedParameter> = {};
|
|
37
|
+
const input = value && typeof value === "object" ? (value as Record<string, unknown>) : {};
|
|
38
|
+
for (const [name, raw] of Object.entries(input)) {
|
|
39
|
+
if (raw && typeof raw === "object" && "value" in raw) {
|
|
40
|
+
const record = raw as Record<string, unknown>;
|
|
41
|
+
output[name] = {
|
|
42
|
+
value: record.value,
|
|
43
|
+
unit: typeof record.unit === "string" ? record.unit : "",
|
|
44
|
+
value_type: typeof record.value_type === "string" ? record.value_type : "",
|
|
45
|
+
description: typeof record.description === "string" ? record.description : "",
|
|
46
|
+
};
|
|
47
|
+
} else if (typeof raw === "number" || typeof raw === "string" || typeof raw === "boolean" || Array.isArray(raw)) {
|
|
48
|
+
output[name] = { value: raw, unit: "", value_type: Array.isArray(raw) ? "list" : typeof raw, description: "" };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function numbers(value: unknown): number[] | undefined {
|
|
55
|
+
if (!Array.isArray(value)) return undefined;
|
|
56
|
+
const list = value.map((item) => (typeof item === "number" ? item : typeof item === "string" ? Number(item) : Number.NaN));
|
|
57
|
+
return list.length > 0 && list.every((item) => Number.isFinite(item)) ? list : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The sweeps among the recorded parameters, in record order, read the way
|
|
62
|
+
* QDash's `get_value` expands them: `range` / `np.arange` as (start, stop,
|
|
63
|
+
* step), `np.linspace` as (start, stop, num), `np.logspace` as (log10 start,
|
|
64
|
+
* log10 stop, num), and `list` as the points themselves.
|
|
65
|
+
*/
|
|
66
|
+
export function sweepsOf(parameters: Record<string, RecordedParameter>): Sweep[] {
|
|
67
|
+
const sweeps: Sweep[] = [];
|
|
68
|
+
for (const [name, parameter] of Object.entries(parameters)) {
|
|
69
|
+
const values = numbers(parameter.value);
|
|
70
|
+
if (!values) continue;
|
|
71
|
+
const base = { name, unit: parameter.unit, description: parameter.description };
|
|
72
|
+
const type = parameter.value_type;
|
|
73
|
+
if ((type === "range" || type === "np.arange") && values.length === 3) {
|
|
74
|
+
const [start, stop, step] = values as [number, number, number];
|
|
75
|
+
if (step === 0 || (stop - start) / step <= 0) continue;
|
|
76
|
+
const points = Math.ceil((stop - start) / step - 1e-9);
|
|
77
|
+
sweeps.push({ ...base, spacing: "linear", start, stop: start + (points - 1) * step, points, step });
|
|
78
|
+
} else if (type === "np.linspace" && values.length === 3) {
|
|
79
|
+
const [start, stop, num] = values as [number, number, number];
|
|
80
|
+
const points = Math.max(1, Math.round(num));
|
|
81
|
+
sweeps.push({ ...base, spacing: "linear", start, stop, points, ...(points > 1 ? { step: (stop - start) / (points - 1) } : {}) });
|
|
82
|
+
} else if (type === "np.logspace" && values.length === 3) {
|
|
83
|
+
const [start, stop, num] = values as [number, number, number];
|
|
84
|
+
sweeps.push({ ...base, spacing: "log", start: 10 ** start, stop: 10 ** stop, points: Math.max(1, Math.round(num)) });
|
|
85
|
+
} else if (type === "list" || type === "" || values.length !== 3) {
|
|
86
|
+
if (values.length < 2) continue;
|
|
87
|
+
sweeps.push({ ...base, spacing: "list", start: Math.min(...values), stop: Math.max(...values), points: values.length });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return sweeps;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A number for the model: up to four significant digits, no trailing zeros. */
|
|
94
|
+
export function formatNumber(value: number): string {
|
|
95
|
+
if (!Number.isFinite(value)) return String(value);
|
|
96
|
+
if (Number.isInteger(value) && Math.abs(value) < 1e15) return String(value);
|
|
97
|
+
const text = Math.abs(value) >= 1e6 || Math.abs(value) < 1e-4 ? value.toExponential(3) : value.toPrecision(4);
|
|
98
|
+
return text.includes("e") ? text.replace(/\.?0+e/, "e") : text.replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function withUnit(value: number, unit: string): string {
|
|
102
|
+
const text = formatNumber(value);
|
|
103
|
+
return unit && unit !== "a.u." ? `${text} ${unit}` : text;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** One sweep as text, e.g. "time_range 0 to 400 ns in steps of 8 ns (51 points)". */
|
|
107
|
+
export function formatSweep(sweep: Sweep): string {
|
|
108
|
+
const range = `${sweep.name} ${formatNumber(sweep.start)} to ${withUnit(sweep.stop, sweep.unit)}`;
|
|
109
|
+
if (sweep.spacing === "log") return `${range}, ${sweep.points} points log-spaced`;
|
|
110
|
+
if (sweep.spacing === "list") return `${range}, ${sweep.points} points`;
|
|
111
|
+
return sweep.step === undefined ? `${range}, ${sweep.points} points` : `${range} in steps of ${withUnit(sweep.step, sweep.unit)} (${sweep.points} points)`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Scalar settings as "name value unit" items, numbers and short strings only. */
|
|
115
|
+
export function formatSettings(parameters: Record<string, RecordedParameter>, skip: ReadonlySet<string> = new Set()): string[] {
|
|
116
|
+
const items: string[] = [];
|
|
117
|
+
for (const [name, parameter] of Object.entries(parameters)) {
|
|
118
|
+
if (skip.has(name)) continue;
|
|
119
|
+
const value = parameter.value;
|
|
120
|
+
if (typeof value === "number") items.push(`${name} ${withUnit(value, parameter.unit)}`);
|
|
121
|
+
else if (typeof value === "boolean") items.push(`${name} ${value}`);
|
|
122
|
+
else if (typeof value === "string" && value.length <= 40) items.push(`${name} ${value}`);
|
|
123
|
+
}
|
|
124
|
+
return items;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export interface QcalContext {
|
|
128
|
+
taskName?: string;
|
|
129
|
+
status?: string;
|
|
130
|
+
targetKind?: string;
|
|
131
|
+
targetId?: string;
|
|
132
|
+
chipId?: string;
|
|
133
|
+
runParameters: Record<string, RecordedParameter>;
|
|
134
|
+
/** Calibration inputs (previous values), shown only on request. */
|
|
135
|
+
inputParameters?: Record<string, RecordedParameter>;
|
|
136
|
+
/** Fitted results, shown only on request. */
|
|
137
|
+
metrics?: Record<string, RecordedParameter>;
|
|
138
|
+
message?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The context for `qcal_evaluate`: the task and target, then the sweep and
|
|
143
|
+
* the other run settings. Inputs and fitted results are added only when the
|
|
144
|
+
* caller asks, since the model is meant to read the plot.
|
|
145
|
+
*/
|
|
146
|
+
export function qcalContextText(context: QcalContext): string {
|
|
147
|
+
let head = `Task ${context.taskName ?? "unknown"}`;
|
|
148
|
+
if (context.targetId) {
|
|
149
|
+
head += ` on ${context.targetKind && context.targetKind !== "system" ? `${context.targetKind} ${context.targetId}` : context.targetId}`;
|
|
150
|
+
}
|
|
151
|
+
if (context.chipId) head += ` (chip ${context.chipId})`;
|
|
152
|
+
if (context.status) head += `, status ${context.status}`;
|
|
153
|
+
const lines = [`${head}.`];
|
|
154
|
+
const sweeps = sweepsOf(context.runParameters);
|
|
155
|
+
if (sweeps.length > 0) lines.push(`Sweep: ${sweeps.map(formatSweep).join("; ")}.`);
|
|
156
|
+
const settings = formatSettings(context.runParameters, new Set(sweeps.map((sweep) => sweep.name)));
|
|
157
|
+
if (settings.length > 0) lines.push(`Settings: ${settings.join("; ")}.`);
|
|
158
|
+
const inputs = context.inputParameters ? formatSettings(context.inputParameters) : [];
|
|
159
|
+
if (inputs.length > 0) lines.push(`Calibration inputs: ${inputs.join("; ")}.`);
|
|
160
|
+
const metrics = context.metrics ? formatSettings(context.metrics) : [];
|
|
161
|
+
if (metrics.length > 0) lines.push(`Fitted results: ${metrics.join("; ")}.`);
|
|
162
|
+
if (context.message?.trim()) lines.push(`Task message: ${context.message.trim()}`);
|
|
163
|
+
return lines.join("\n");
|
|
164
|
+
}
|
package/extensions/qdash.ts
CHANGED
|
@@ -25,6 +25,7 @@ import { toFigureToolResult, toTextToolResult, toToolResult } from "./lib/result
|
|
|
25
25
|
import { ansi, boxLinesToWidth, boxed, formatNumber, textComponent } from "./lib/render.js";
|
|
26
26
|
import { plotSeriesLines, timeseriesPlotComponent, timeseriesPoints } from "./lib/timeseries-plot.js";
|
|
27
27
|
import { installQDashWriteGate } from "./lib/write-gate.js";
|
|
28
|
+
import { qcalContextText, recordedParameters, sweepsOf, type RecordedParameter, type Sweep } from "./lib/qcal-evidence.js";
|
|
28
29
|
import { registerCalibrationPipelineTools } from "./tools/calibration-pipeline.js";
|
|
29
30
|
import { registerTaskKnowledgeTool } from "./tools/task-knowledge.js";
|
|
30
31
|
import { registerTimeseriesComparisonTool } from "./tools/timeseries.js";
|
|
@@ -52,7 +53,6 @@ type QDashQueryParams = {
|
|
|
52
53
|
| "task_results"
|
|
53
54
|
| "task_result"
|
|
54
55
|
| "task_note"
|
|
55
|
-
| "task_result_issues"
|
|
56
56
|
| "qubit_latest"
|
|
57
57
|
| "qubit_history"
|
|
58
58
|
| "coupling_latest"
|
|
@@ -65,8 +65,6 @@ type QDashQueryParams = {
|
|
|
65
65
|
| "files_tree"
|
|
66
66
|
| "file_content"
|
|
67
67
|
| "git_status"
|
|
68
|
-
| "issues"
|
|
69
|
-
| "issue_knowledge"
|
|
70
68
|
| "flows"
|
|
71
69
|
| "flow"
|
|
72
70
|
| "flow_templates"
|
|
@@ -108,7 +106,6 @@ type QDashQueryParams = {
|
|
|
108
106
|
endAt?: string;
|
|
109
107
|
startFrom?: string;
|
|
110
108
|
startTo?: string;
|
|
111
|
-
isClosed?: boolean;
|
|
112
109
|
withinHours?: number;
|
|
113
110
|
limit?: number;
|
|
114
111
|
skip?: number;
|
|
@@ -294,7 +291,6 @@ const QueryAction = Type.Union([
|
|
|
294
291
|
Type.Literal("task_results"),
|
|
295
292
|
Type.Literal("task_result"),
|
|
296
293
|
Type.Literal("task_note"),
|
|
297
|
-
Type.Literal("task_result_issues"),
|
|
298
294
|
Type.Literal("qubit_latest"),
|
|
299
295
|
Type.Literal("qubit_history"),
|
|
300
296
|
Type.Literal("coupling_latest"),
|
|
@@ -307,8 +303,6 @@ const QueryAction = Type.Union([
|
|
|
307
303
|
Type.Literal("files_tree"),
|
|
308
304
|
Type.Literal("file_content"),
|
|
309
305
|
Type.Literal("git_status"),
|
|
310
|
-
Type.Literal("issues"),
|
|
311
|
-
Type.Literal("issue_knowledge"),
|
|
312
306
|
Type.Literal("flows"),
|
|
313
307
|
Type.Literal("flow"),
|
|
314
308
|
Type.Literal("flow_templates"),
|
|
@@ -371,7 +365,6 @@ const querySchema = Type.Object({
|
|
|
371
365
|
endAt: Type.Optional(Type.String()),
|
|
372
366
|
startFrom: Type.Optional(Type.String()),
|
|
373
367
|
startTo: Type.Optional(Type.String()),
|
|
374
|
-
isClosed: Type.Optional(Type.Boolean()),
|
|
375
368
|
withinHours: Type.Optional(Type.Number()),
|
|
376
369
|
limit: Type.Optional(Type.Number()),
|
|
377
370
|
skip: Type.Optional(Type.Number()),
|
|
@@ -413,6 +406,14 @@ type CalibrationEvidence = {
|
|
|
413
406
|
task?: Record<string, unknown>;
|
|
414
407
|
metrics?: Record<string, number | string | boolean | null>;
|
|
415
408
|
parameters?: Record<string, number | string | boolean | null>;
|
|
409
|
+
/** The run parameters as recorded, with units and value types. */
|
|
410
|
+
runParameters?: Record<string, RecordedParameter>;
|
|
411
|
+
/** The calibration inputs as recorded (previous values), with units. */
|
|
412
|
+
inputParameters?: Record<string, RecordedParameter>;
|
|
413
|
+
/** The output parameters as recorded (fitted results), with units. */
|
|
414
|
+
metricRecords?: Record<string, RecordedParameter>;
|
|
415
|
+
/** The swept parameters among them: the axes the plot was taken along. */
|
|
416
|
+
sweeps?: Sweep[];
|
|
416
417
|
resultJson?: unknown;
|
|
417
418
|
figures?: Array<Record<string, unknown>>;
|
|
418
419
|
logs?: string[];
|
|
@@ -439,23 +440,24 @@ function firstStringField(object: Record<string, unknown>, keys: string[]): stri
|
|
|
439
440
|
return undefined;
|
|
440
441
|
}
|
|
441
442
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
"",
|
|
451
|
-
"
|
|
452
|
-
|
|
453
|
-
"",
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
443
|
+
/**
|
|
444
|
+
* The evidence text, used as the context of `qcal_evaluate`: the task, the
|
|
445
|
+
* target, the sweep and the run settings. Fitted results and calibration
|
|
446
|
+
* inputs are left out unless asked for, since the evaluation model is meant
|
|
447
|
+
* to read them off the plot.
|
|
448
|
+
*/
|
|
449
|
+
function qcalEvidenceText(evidence: CalibrationEvidence, include: { inputs?: boolean; metrics?: boolean } = {}): string {
|
|
450
|
+
return qcalContextText({
|
|
451
|
+
taskName: typeof evidence.task?.name === "string" ? evidence.task.name : undefined,
|
|
452
|
+
status: typeof evidence.task?.status === "string" ? evidence.task.status : undefined,
|
|
453
|
+
targetKind: typeof evidence.target?.kind === "string" ? evidence.target.kind : undefined,
|
|
454
|
+
targetId: typeof evidence.target?.id === "string" ? evidence.target.id : undefined,
|
|
455
|
+
chipId: typeof evidence.target?.chipId === "string" ? evidence.target.chipId : undefined,
|
|
456
|
+
runParameters: evidence.runParameters ?? {},
|
|
457
|
+
...(include.inputs ? { inputParameters: recordedParameters(evidence.inputParameters) } : {}),
|
|
458
|
+
...(include.metrics ? { metrics: recordedParameters(evidence.metricRecords) } : {}),
|
|
459
|
+
message: typeof evidence.task?.message === "string" ? evidence.task.message : undefined,
|
|
460
|
+
});
|
|
459
461
|
}
|
|
460
462
|
|
|
461
463
|
function cleanQuery(values: Record<string, unknown> = {}): Record<string, unknown> {
|
|
@@ -578,25 +580,25 @@ async function buildDashboard(params: { profile?: string; configPath?: string; u
|
|
|
578
580
|
const limit = params.limit ?? 5;
|
|
579
581
|
const client = await makeClient(params);
|
|
580
582
|
const chipId = await defaultChipId(client, params.chipId);
|
|
581
|
-
const [chips,
|
|
583
|
+
const [chips, openThreads, recentExecutions, failedTaskResults, provenanceStats] = await Promise.allSettled([
|
|
582
584
|
client.listChips(),
|
|
583
|
-
client.
|
|
585
|
+
client.listForumPosts({ chipId, status: "open", limit }),
|
|
584
586
|
client.api.listExecutions({ chip_id: chipId, limit }),
|
|
585
587
|
client.api.listTaskResults({ chip_id: chipId, status: "failed", limit }),
|
|
586
588
|
client.getProvenanceStats(),
|
|
587
589
|
]);
|
|
588
590
|
const value = <T>(result: PromiseSettledResult<T>): T | { error: string } => result.status === "fulfilled" ? result.value : { error: result.reason instanceof Error ? result.reason.message : String(result.reason) };
|
|
589
|
-
const
|
|
591
|
+
const threadsPayload = value(openThreads);
|
|
590
592
|
const executionsPayload = value(recentExecutions);
|
|
591
593
|
const failedTasksPayload = value(failedTaskResults);
|
|
592
594
|
return {
|
|
593
595
|
context: { ...currentContext, profile: params.profile ?? currentContext.profile, chipId },
|
|
594
596
|
chips: value(chips),
|
|
595
|
-
|
|
596
|
-
count: payloadTotal(
|
|
597
|
-
shown: arrayFromPayload(
|
|
598
|
-
items: compactItems(
|
|
599
|
-
raw:
|
|
597
|
+
openThreads: {
|
|
598
|
+
count: payloadTotal(threadsPayload),
|
|
599
|
+
shown: arrayFromPayload(threadsPayload).length,
|
|
600
|
+
items: compactItems(threadsPayload, ["number", "title", "category", "target_type", "target_id", "created_at"], limit),
|
|
601
|
+
raw: threadsPayload,
|
|
600
602
|
},
|
|
601
603
|
recentExecutions: {
|
|
602
604
|
count: payloadTotal(executionsPayload),
|
|
@@ -624,14 +626,14 @@ function dashboardLines(dashboard: Awaited<ReturnType<typeof buildDashboard>>, c
|
|
|
624
626
|
const success = (text: string) => color ? ansi("32", text) : text;
|
|
625
627
|
const warning = (text: string) => color ? ansi("33", text) : text;
|
|
626
628
|
const error = (text: string) => color ? ansi("31", text) : text;
|
|
627
|
-
const
|
|
629
|
+
const threadCount = dashboard.openThreads.count > 0 ? warning(String(dashboard.openThreads.count)) : success(String(dashboard.openThreads.count));
|
|
628
630
|
const failedCount = dashboard.failedTaskResults.count > 0 ? error(`${dashboard.failedTaskResults.shown}/${dashboard.failedTaskResults.count}`) : success(`${dashboard.failedTaskResults.shown}/${dashboard.failedTaskResults.count}`);
|
|
629
631
|
return boxed("QDash Harness", [
|
|
630
632
|
`${muted("profile")} ${accent(profile)} ${muted("chip")} ${accent(dashboard.context.chipId)} ${muted("session")} ${dim(session)}`,
|
|
631
|
-
`${muted("chips")} ${success(String(chips.length))} ${muted("open
|
|
633
|
+
`${muted("chips")} ${success(String(chips.length))} ${muted("open threads")} ${threadCount} ${muted("executions")} ${accent(`${dashboard.recentExecutions.shown}/${dashboard.recentExecutions.count}`)} ${muted("failed tasks")} ${failedCount}`,
|
|
632
634
|
"",
|
|
633
|
-
accent("Open
|
|
634
|
-
...(dashboard.
|
|
635
|
+
accent("Open Forum threads"),
|
|
636
|
+
...(dashboard.openThreads.items.length > 0 ? dashboard.openThreads.items.map((item, index) => ` ${warning(formatItem(item, `thread-${index + 1}`))}`) : [` ${dim("none")}`]),
|
|
635
637
|
"",
|
|
636
638
|
accent("Recent executions"),
|
|
637
639
|
...(dashboard.recentExecutions.items.length > 0 ? dashboard.recentExecutions.items.map((item, index) => ` ${muted(formatItem(item, `exec-${index + 1}`))}`) : [` ${dim("none")}`]),
|
|
@@ -645,7 +647,7 @@ function styledDashboardLines(dashboard: Awaited<ReturnType<typeof buildDashboar
|
|
|
645
647
|
const chips = arrayFromPayload(dashboard.chips);
|
|
646
648
|
const profile = dashboard.context.profile ?? "env/default";
|
|
647
649
|
const session = dashboard.context.agentSessionId ? shortId(dashboard.context.agentSessionId) : "none";
|
|
648
|
-
const
|
|
650
|
+
const threadColor = dashboard.openThreads.count > 0 ? "warning" : "success";
|
|
649
651
|
const failedColor = dashboard.failedTaskResults.count > 0 ? "error" : "success";
|
|
650
652
|
return [
|
|
651
653
|
theme.fg("accent", theme.bold("QDash Harness")),
|
|
@@ -653,14 +655,14 @@ function styledDashboardLines(dashboard: Awaited<ReturnType<typeof buildDashboar
|
|
|
653
655
|
"",
|
|
654
656
|
[
|
|
655
657
|
`${theme.fg("muted", "chips")} ${theme.fg("success", String(chips.length))}`,
|
|
656
|
-
`${theme.fg("muted", "open
|
|
658
|
+
`${theme.fg("muted", "open threads")} ${theme.fg(threadColor, String(dashboard.openThreads.count))}`,
|
|
657
659
|
`${theme.fg("muted", "executions")} ${theme.fg("accent", `${dashboard.recentExecutions.shown}/${dashboard.recentExecutions.count}`)}`,
|
|
658
660
|
`${theme.fg("muted", "failed tasks")} ${theme.fg(failedColor, `${dashboard.failedTaskResults.shown}/${dashboard.failedTaskResults.count}`)}`,
|
|
659
661
|
].join(" "),
|
|
660
662
|
"",
|
|
661
|
-
theme.fg("borderAccent", "Open
|
|
662
|
-
...(dashboard.
|
|
663
|
-
? dashboard.
|
|
663
|
+
theme.fg("borderAccent", "Open Forum threads"),
|
|
664
|
+
...(dashboard.openThreads.items.length > 0
|
|
665
|
+
? dashboard.openThreads.items.map((item, index) => ` ${theme.fg("warning", formatItem(item, `thread-${index + 1}`))}`)
|
|
664
666
|
: [` ${theme.fg("dim", "none")}`]),
|
|
665
667
|
"",
|
|
666
668
|
theme.fg("borderAccent", "Recent executions"),
|
|
@@ -871,7 +873,6 @@ type TargetOperationsReport = {
|
|
|
871
873
|
latestTarget: unknown;
|
|
872
874
|
recentResults: Record<string, unknown>[];
|
|
873
875
|
failures: Record<string, unknown>[];
|
|
874
|
-
issues: Record<string, unknown>[];
|
|
875
876
|
forumPosts: Record<string, unknown>[];
|
|
876
877
|
recommendation: RecommendedNextAction | null;
|
|
877
878
|
};
|
|
@@ -922,19 +923,12 @@ async function buildTargetOperationsReport(params: { profile?: string; configPat
|
|
|
922
923
|
const end = new Date();
|
|
923
924
|
const start = new Date(end.getTime() - (params.withinHours ?? 168) * 3600_000);
|
|
924
925
|
const limit = Math.max(1, Math.min(100, params.limit ?? 10));
|
|
925
|
-
const [latestTarget, results,
|
|
926
|
+
const [latestTarget, results, forumPosts] = await Promise.all([
|
|
926
927
|
kind === "qubit" ? client.getChipQubit(chipId, id) : client.getChipCoupling(chipId, id),
|
|
927
928
|
client.listTaskResults({ chipId, qid, couplingId, startAt: start.toISOString(), endAt: end.toISOString(), limit }),
|
|
928
|
-
client.api.listIssues({ is_closed: false, limit: 100 }),
|
|
929
929
|
client.listForumPosts({ chipId, status: "open", targetType: kind, targetId: id, limit }),
|
|
930
930
|
]);
|
|
931
931
|
const recentResults = arrayFromPayload(results).filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object");
|
|
932
|
-
const taskIds = new Set(recentResults.map((item) => firstString(item, ["task_id", "taskId"])).filter((item): item is string => Boolean(item)));
|
|
933
|
-
const targetIssues = arrayFromPayload(issues).filter((item): item is Record<string, unknown> => {
|
|
934
|
-
if (!item || typeof item !== "object") return false;
|
|
935
|
-
const issue = item as Record<string, unknown>;
|
|
936
|
-
return (firstString(issue, ["task_id", "taskId"]) && taskIds.has(firstString(issue, ["task_id", "taskId"])!)) || targetFromText(firstString(issue, ["title", "summary"]) ?? "") === label;
|
|
937
|
-
});
|
|
938
932
|
const summary: RecentCalibrationSummary = { context: { profile: params.profile ?? currentContext.profile, chipId, webBaseUrl: qdashWebBaseUrl(client) }, items: recentResults, groups: [] };
|
|
939
933
|
const groups = new Map<string, Record<string, unknown>[]>();
|
|
940
934
|
groups.set(label, recentResults);
|
|
@@ -946,7 +940,6 @@ async function buildTargetOperationsReport(params: { profile?: string; configPat
|
|
|
946
940
|
latestTarget,
|
|
947
941
|
recentResults,
|
|
948
942
|
failures: recentResults.filter((item) => firstString(item, ["status"])?.toLowerCase() === "failed"),
|
|
949
|
-
issues: targetIssues,
|
|
950
943
|
forumPosts: forumPostsFromPayload(forumPosts),
|
|
951
944
|
recommendation,
|
|
952
945
|
};
|
|
@@ -965,7 +958,7 @@ function targetOperationsReportLines(report: TargetOperationsReport, color = fal
|
|
|
965
958
|
};
|
|
966
959
|
return boxed(`QDash Target Report ${report.target.label}`, [
|
|
967
960
|
`${muted("profile")} ${accent(report.context.profile ?? "env/default")} ${muted("chip")} ${accent(report.context.chipId)}`,
|
|
968
|
-
`${muted("results")} ${report.recentResults.length} ${muted("failures")} ${report.failures.length ? error(String(report.failures.length)) : "0"} ${muted("
|
|
961
|
+
`${muted("results")} ${report.recentResults.length} ${muted("failures")} ${report.failures.length ? error(String(report.failures.length)) : "0"} ${muted("forum")} ${report.forumPosts.length ? warn(String(report.forumPosts.length)) : "0"}`,
|
|
969
962
|
"",
|
|
970
963
|
accent("Recent results"),
|
|
971
964
|
...(report.recentResults.length ? report.recentResults.slice(0, 10).map(resultLine) : [` ${muted("none")}`]),
|
|
@@ -1097,7 +1090,6 @@ type CalibrationValidation = {
|
|
|
1097
1090
|
status: "passed" | "failed" | "needs_review" | "unknown";
|
|
1098
1091
|
gates: Array<{ name: string; passed: boolean; detail: string }>;
|
|
1099
1092
|
figurePaths: string[];
|
|
1100
|
-
issues: Record<string, unknown>[];
|
|
1101
1093
|
comparison?: unknown;
|
|
1102
1094
|
next: string;
|
|
1103
1095
|
links: Record<string, string>;
|
|
@@ -1109,8 +1101,6 @@ async function buildCalibrationValidation(params: { profile?: string; configPath
|
|
|
1109
1101
|
const task = await client.getTaskResult(params.taskId) as unknown as Record<string, unknown>;
|
|
1110
1102
|
const status = firstString(task, ["status"])?.toLowerCase() ?? "unknown";
|
|
1111
1103
|
const figurePaths = taskFigurePaths(task, 20);
|
|
1112
|
-
const issuesPayload = await client.api.getTaskResultIssues(params.taskId).catch(() => []);
|
|
1113
|
-
const issues = arrayFromPayload(issuesPayload).filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object");
|
|
1114
1104
|
const afterExecutionId = firstString(task, ["execution_id", "executionId"]);
|
|
1115
1105
|
const comparison = params.beforeExecutionId && afterExecutionId
|
|
1116
1106
|
? await client.compareExecutions(params.beforeExecutionId, afterExecutionId).catch(() => undefined)
|
|
@@ -1119,18 +1109,17 @@ async function buildCalibrationValidation(params: { profile?: string; configPath
|
|
|
1119
1109
|
const changedParameters = comparisonRecord ? firstNumber(comparisonRecord, ["changed_count"]) ?? (Array.isArray(comparisonRecord.changed_parameters) ? (comparisonRecord.changed_parameters as unknown[]).length : undefined) : undefined;
|
|
1120
1110
|
const gates = [
|
|
1121
1111
|
{ name: "task completed", passed: ["completed", "success", "succeeded"].includes(status), detail: status },
|
|
1122
|
-
{ name: "no task issues", passed: issues.length === 0, detail: issues.length ? `${issues.length} issue(s) require review` : "none" },
|
|
1123
1112
|
{ name: "figures available", passed: figurePaths.length > 0, detail: figurePaths.length ? `${figurePaths.length} figure(s)` : "no figures attached" },
|
|
1124
1113
|
...(params.beforeExecutionId ? [{ name: "execution comparison", passed: comparison !== undefined, detail: comparison === undefined ? "comparison unavailable" : `${changedParameters ?? 0} changed parameter(s)` }] : []),
|
|
1125
1114
|
];
|
|
1126
|
-
const
|
|
1115
|
+
const figuresGate = gates.find((gate) => gate.name === "figures available");
|
|
1127
1116
|
const comparisonGate = gates.find((gate) => gate.name === "execution comparison");
|
|
1128
|
-
const validationStatus: CalibrationValidation["status"] = !gates[0].passed ? "failed" :
|
|
1117
|
+
const validationStatus: CalibrationValidation["status"] = !gates[0].passed ? "failed" : figuresGate?.passed === false || comparisonGate?.passed === false ? "needs_review" : "passed";
|
|
1129
1118
|
const next = validationStatus === "passed"
|
|
1130
1119
|
? "Inspect candidates and ask for explicit confirmation before commit/apply. Preserve evidence in the target Forum thread when appropriate."
|
|
1131
1120
|
: validationStatus === "failed"
|
|
1132
1121
|
? "Stop the recovery sequence; inspect the task message and figures before another operational action."
|
|
1133
|
-
: "Review the
|
|
1122
|
+
: "Review the figures and execution comparison before treating this result as authoritative.";
|
|
1134
1123
|
return {
|
|
1135
1124
|
context: { profile: params.profile ?? currentContext.profile, chipId: firstString(task, ["chip_id", "chipId"]) ?? currentContext.chipId, webBaseUrl: qdashWebBaseUrl(client) },
|
|
1136
1125
|
taskId: params.taskId,
|
|
@@ -1138,7 +1127,6 @@ async function buildCalibrationValidation(params: { profile?: string; configPath
|
|
|
1138
1127
|
status: validationStatus,
|
|
1139
1128
|
gates,
|
|
1140
1129
|
figurePaths,
|
|
1141
|
-
issues,
|
|
1142
1130
|
comparison,
|
|
1143
1131
|
next,
|
|
1144
1132
|
links: qdashObjectLinks(client, task),
|
|
@@ -1240,9 +1228,8 @@ async function buildDashboardInsights(params: { profile?: string; configPath?: s
|
|
|
1240
1228
|
const limit = params.limit ?? 30;
|
|
1241
1229
|
const end = new Date();
|
|
1242
1230
|
const start = new Date(end.getTime() - (params.withinHours ?? 168) * 3600_000);
|
|
1243
|
-
const [forums,
|
|
1231
|
+
const [forums, failed, metrics] = await Promise.allSettled([
|
|
1244
1232
|
client.listForumPosts({ chipId, status: "open", limit }),
|
|
1245
|
-
client.api.listIssues({ is_closed: false, limit }),
|
|
1246
1233
|
client.api.listTaskResults({ chip_id: chipId, status: "failed", start_from: start.toISOString(), start_to: end.toISOString(), limit }),
|
|
1247
1234
|
client.getChipMetrics(chipId),
|
|
1248
1235
|
]);
|
|
@@ -1260,13 +1247,6 @@ async function buildDashboardInsights(params: { profile?: string; configPath?: s
|
|
|
1260
1247
|
addEvidence(insightMap, target, severity, title, `forum: ${title}`, "Open forum context should be reviewed before calibration changes.", qdashObjectLinks(client, post));
|
|
1261
1248
|
}
|
|
1262
1249
|
|
|
1263
|
-
for (const issue of arrayFromPayload(value(issues)).filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object")) {
|
|
1264
|
-
const title = firstString(issue, ["title", "summary"]) ?? "open issue";
|
|
1265
|
-
const target = targetFromText(title) ?? firstString(issue, ["qid"]);
|
|
1266
|
-
if (!target) continue;
|
|
1267
|
-
addEvidence(insightMap, target.startsWith("q") || target.startsWith("c") ? target : `q${target}`, "warning", title, `open issue: ${title}`, "Resolve or account for the open issue before trusting metrics.", qdashObjectLinks(client, issue));
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
1250
|
for (const task of arrayFromPayload(value(failed)).filter((item): item is Record<string, unknown> => Boolean(item) && typeof item === "object")) {
|
|
1271
1251
|
const target = calibrationTarget(task);
|
|
1272
1252
|
const taskName = firstString(task, ["task_name", "name"]) ?? "task";
|
|
@@ -1465,7 +1445,6 @@ async function executeQuery(params: QDashQueryParams) {
|
|
|
1465
1445
|
case "task_results": return rawGet(client, "/task-results", { chip_id: params.chipId, task_name: params.taskName, qid: params.qid, coupling_id: params.couplingId, execution_id: params.executionId, username: params.username, status: params.status, start_from: params.startFrom ?? params.startAt, start_to: params.startTo ?? params.endAt, message_contains: params.messageContains, limit: params.limit, skip: params.skip });
|
|
1466
1446
|
case "task_result": return client.getTaskResult(requireValue(params.taskId, "taskId"));
|
|
1467
1447
|
case "task_note": return client.api.getTaskNote(requireValue(params.taskId, "taskId"));
|
|
1468
|
-
case "task_result_issues": return client.api.getTaskResultIssues(requireValue(params.taskId, "taskId"));
|
|
1469
1448
|
case "qubit_latest": return client.api.getLatestQubitTaskResults({ chip_id: await defaultChipId(client, params.chipId), task: requireValue(params.task ?? params.taskName, "task") });
|
|
1470
1449
|
case "qubit_history": return rawGet(client, `/task-results/qubits/${pathPart(requireValue(params.qid, "qid"))}/history`, { chip_id: await defaultChipId(client, params.chipId), task: requireValue(params.task ?? params.taskName, "task"), date: requireValue(params.date, "date") });
|
|
1471
1450
|
case "coupling_latest": return client.api.getLatestCouplingTaskResults({ chip_id: await defaultChipId(client, params.chipId), task: requireValue(params.task ?? params.taskName, "task") });
|
|
@@ -1478,8 +1457,6 @@ async function executeQuery(params: QDashQueryParams) {
|
|
|
1478
1457
|
case "files_tree": return client.getFilesTree();
|
|
1479
1458
|
case "file_content": return client.getFileContent(requireValue(params.path, "path"));
|
|
1480
1459
|
case "git_status": return client.getGitStatus();
|
|
1481
|
-
case "issues": return client.api.listIssues({ task_id: params.taskId, is_closed: params.isClosed, limit: params.limit, skip: params.skip });
|
|
1482
|
-
case "issue_knowledge": return client.api.listIssueKnowledge({ status: params.status, task_name: params.taskName, limit: params.limit, skip: params.skip });
|
|
1483
1460
|
case "flows": return client.listFlows();
|
|
1484
1461
|
case "flow": return client.getFlow(requireValue(params.flowName, "flowName"));
|
|
1485
1462
|
case "flow_templates": return client.listFlowTemplates();
|
|
@@ -1532,7 +1509,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
1532
1509
|
description: "Run common read-only QDash queries via @oqtopus-team/qdash-client.",
|
|
1533
1510
|
promptSnippet: "Query QDash chips, cryostats, cooldown wiring, metrics, tasks, files, flows, executions, projects, forum posts, and provenance",
|
|
1534
1511
|
promptGuidelines: [
|
|
1535
|
-
"Prefer dedicated QDash tools such as qdash_list_chips, qdash_get_chip_metrics, qdash_list_task_results,
|
|
1512
|
+
"Prefer dedicated QDash tools such as qdash_list_chips, qdash_get_chip_metrics, qdash_list_task_results, qdash_list_forum_posts, qdash_list_flows, and qdash_list_executions when they match the task.",
|
|
1536
1513
|
"Use qdash_query for read-only QDash data access not covered by a dedicated tool, instead of curl or scraping the UI.",
|
|
1537
1514
|
"Ask for confirmation before using operational or write APIs; qdash_query intentionally exposes read-only operations only.",
|
|
1538
1515
|
"Never expose QDash tokens, passwords, or Cloudflare Access secrets.",
|
|
@@ -1808,15 +1785,6 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
1808
1785
|
parameters: Type.Object({ ...connectionParams, taskId: Type.String() }),
|
|
1809
1786
|
});
|
|
1810
1787
|
|
|
1811
|
-
registerQueryTool({
|
|
1812
|
-
name: "qdash_list_issues",
|
|
1813
|
-
label: "QDash List Issues",
|
|
1814
|
-
description: "List QDash issues with optional task/closed filters.",
|
|
1815
|
-
promptSnippet: "List QDash issues",
|
|
1816
|
-
action: "issues",
|
|
1817
|
-
parameters: Type.Object({ ...connectionParams, taskId: Type.Optional(Type.String()), isClosed: Type.Optional(Type.Boolean()), limit: Type.Optional(Type.Number()), skip: Type.Optional(Type.Number()) }),
|
|
1818
|
-
});
|
|
1819
|
-
|
|
1820
1788
|
registerQueryTool({
|
|
1821
1789
|
name: "qdash_list_flows",
|
|
1822
1790
|
label: "QDash List Flows",
|
|
@@ -2094,21 +2062,24 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2094
2062
|
pi.registerTool({
|
|
2095
2063
|
name: "qdash_execute_agent_action",
|
|
2096
2064
|
label: "QDash Execute Agent Action",
|
|
2097
|
-
description: "
|
|
2098
|
-
promptSnippet: "
|
|
2099
|
-
promptGuidelines: [
|
|
2065
|
+
description: "Run an authorized QDash agent action's task the way the Task Workbench runs it: inputs from the current calibration state, outputs staged for the candidate gate. This is a write operation and requires confirmation.",
|
|
2066
|
+
promptSnippet: "Run a QDash agent action's task after explicit user confirmation",
|
|
2067
|
+
promptGuidelines: [
|
|
2068
|
+
"Use qdash_execute_agent_action only after the user confirms the action, its task, target, and overrides.",
|
|
2069
|
+
"The task runs on the current calibration state; pass sourceExecutionId only to record which execution the action was decided from.",
|
|
2070
|
+
],
|
|
2100
2071
|
parameters: Type.Object({
|
|
2101
2072
|
...connectionParams,
|
|
2102
2073
|
sessionId: Type.String(),
|
|
2103
2074
|
actionId: Type.String(),
|
|
2104
|
-
sourceExecutionId: Type.String(),
|
|
2075
|
+
sourceExecutionId: Type.Optional(Type.String({ description: "Execution the action was decided from, recorded for provenance. The run does not restore it." })),
|
|
2105
2076
|
updateParams: Type.Optional(Type.Boolean()),
|
|
2106
2077
|
reconfigure: Type.Optional(Type.Boolean()),
|
|
2107
2078
|
confirmWrite: Type.Optional(Type.Boolean()),
|
|
2108
2079
|
}),
|
|
2109
|
-
async execute(_toolCallId, params: ConfirmableParams & { profile?: string; configPath?: string; useEnv?: boolean; sessionId: string; actionId: string; sourceExecutionId
|
|
2080
|
+
async execute(_toolCallId, params: ConfirmableParams & { profile?: string; configPath?: string; useEnv?: boolean; sessionId: string; actionId: string; sourceExecutionId?: string; updateParams?: boolean; reconfigure?: boolean }, _signal, _onUpdate, ctx) {
|
|
2110
2081
|
if (!params.confirmWrite) {
|
|
2111
|
-
if (!ctx.hasUI || !(await ctx.ui.confirm("Execute QDash agent action?", `
|
|
2082
|
+
if (!ctx.hasUI || !(await ctx.ui.confirm("Execute QDash agent action?", `Run action '${params.actionId}' on the current calibration state? Outputs stay staged until a candidate is committed.`))) {
|
|
2112
2083
|
throw new Error("qdash_execute_agent_action requires explicit confirmation");
|
|
2113
2084
|
}
|
|
2114
2085
|
}
|
|
@@ -2613,7 +2584,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2613
2584
|
pi.registerTool({
|
|
2614
2585
|
name: "qdash_build_qcal_evidence",
|
|
2615
2586
|
label: "QDash Build QCal Evidence",
|
|
2616
|
-
description: "Summarize a QDash task result (target,
|
|
2587
|
+
description: "Summarize a QDash task result (target, sweep and run settings) as evidence for pi-qcaleval's qcal_evaluate, returning the task figure as image content. Fitted results are left out unless includeMetrics is set.",
|
|
2617
2588
|
promptSnippet: "Summarize a QDash calibration task result as evidence for qcal_evaluate",
|
|
2618
2589
|
promptGuidelines: [
|
|
2619
2590
|
"Use qdash_build_qcal_evidence before qcal_evaluate when the user asks to evaluate a QDash calibration task with pi-qcaleval; pass the returned summary as the context of qcal_evaluate.",
|
|
@@ -2627,8 +2598,10 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2627
2598
|
includeFigureAnalysis: Type.Optional(Type.Boolean({ description: "Analyze Plotly JSON figures and include summaries/warnings in notes. Defaults to true." })),
|
|
2628
2599
|
maxFigures: Type.Optional(Type.Number({ description: "Maximum image figures to embed. Defaults to 1." })),
|
|
2629
2600
|
includeRawTaskResult: Type.Optional(Type.Boolean({ description: "Include the full raw task result in resultJson. Defaults to false." })),
|
|
2601
|
+
includeInputs: Type.Optional(Type.Boolean({ description: "Add the calibration inputs (previous values) to the evidence text. Defaults to false: the evaluation model reads the plot." })),
|
|
2602
|
+
includeMetrics: Type.Optional(Type.Boolean({ description: "Add the fitted output parameters to the evidence text. Defaults to false: the evaluation model reads the plot." })),
|
|
2630
2603
|
}),
|
|
2631
|
-
async execute(_toolCallId, params: { profile?: string; configPath?: string; useEnv?: boolean; taskId: string; includeFigures?: boolean; includeFigureAnalysis?: boolean; maxFigures?: number; includeRawTaskResult?: boolean }) {
|
|
2604
|
+
async execute(_toolCallId, params: { profile?: string; configPath?: string; useEnv?: boolean; taskId: string; includeFigures?: boolean; includeFigureAnalysis?: boolean; maxFigures?: number; includeRawTaskResult?: boolean; includeInputs?: boolean; includeMetrics?: boolean }) {
|
|
2632
2605
|
const client = await makeClient(params);
|
|
2633
2606
|
const task = await client.getTaskResult(params.taskId) as unknown as Record<string, unknown>;
|
|
2634
2607
|
const figures: Array<Record<string, unknown>> = [];
|
|
@@ -2673,6 +2646,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2673
2646
|
|
|
2674
2647
|
const couplingId = firstStringField(task, ["coupling_id", "couplingId", "qid"]);
|
|
2675
2648
|
const qid = firstStringField(task, ["qid"]);
|
|
2649
|
+
const runParameters = recordedParameters(task.run_parameters);
|
|
2676
2650
|
const evidence: CalibrationEvidence = {
|
|
2677
2651
|
id: params.taskId,
|
|
2678
2652
|
source: "external",
|
|
@@ -2690,9 +2664,14 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2690
2664
|
startedAt: firstStringField(task, ["start_at", "startAt"]),
|
|
2691
2665
|
completedAt: firstStringField(task, ["end_at", "endAt"]),
|
|
2692
2666
|
status: firstStringField(task, ["status"]),
|
|
2667
|
+
message: typeof task.message === "string" && task.message.trim() ? task.message.trim() : undefined,
|
|
2693
2668
|
},
|
|
2694
2669
|
metrics: parameterValueRecord(task.output_parameters),
|
|
2695
2670
|
parameters: { ...parameterValueRecord(task.input_parameters), ...parameterValueRecord(task.run_parameters) },
|
|
2671
|
+
runParameters,
|
|
2672
|
+
sweeps: sweepsOf(runParameters),
|
|
2673
|
+
inputParameters: recordedParameters(task.input_parameters),
|
|
2674
|
+
metricRecords: recordedParameters(task.output_parameters),
|
|
2696
2675
|
figures: figures.length ? figures : undefined,
|
|
2697
2676
|
notes: [
|
|
2698
2677
|
typeof task.message === "string" ? `Task message: ${task.message}` : undefined,
|
|
@@ -2708,7 +2687,11 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2708
2687
|
},
|
|
2709
2688
|
};
|
|
2710
2689
|
|
|
2711
|
-
const result = toTextToolResult(
|
|
2690
|
+
const result = toTextToolResult(
|
|
2691
|
+
qcalEvidenceText(evidence, { inputs: params.includeInputs === true, metrics: params.includeMetrics === true }),
|
|
2692
|
+
{ evidence },
|
|
2693
|
+
{ tool: "qdash_build_qcal_evidence", taskId: params.taskId, evidence },
|
|
2694
|
+
);
|
|
2712
2695
|
return { ...result, content: [...result.content, ...images] };
|
|
2713
2696
|
},
|
|
2714
2697
|
});
|
|
@@ -2753,7 +2736,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2753
2736
|
pi.registerTool({
|
|
2754
2737
|
name: "qdash_validate_calibration",
|
|
2755
2738
|
label: "QDash Validate Calibration",
|
|
2756
|
-
description: "Validate a calibration task result with explicit gates for status
|
|
2739
|
+
description: "Validate a calibration task result with explicit gates for status and figures. Read-only; does not commit or apply candidates.",
|
|
2757
2740
|
promptSnippet: "Validate a QDash calibration result before continuing or committing parameters",
|
|
2758
2741
|
promptGuidelines: [
|
|
2759
2742
|
"Use qdash_validate_calibration after an operational task completes or fails.",
|
|
@@ -2807,7 +2790,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2807
2790
|
pi.registerTool({
|
|
2808
2791
|
name: "qdash_target_report",
|
|
2809
2792
|
label: "QDash Target Report",
|
|
2810
|
-
description: "Build a read-only operational report for one qubit or coupling, correlating recent results, failures,
|
|
2793
|
+
description: "Build a read-only operational report for one qubit or coupling, correlating recent results, failures, Forum context, and a safe next-action recommendation.",
|
|
2811
2794
|
promptSnippet: "Inspect one QDash qubit or coupling as an operational incident report",
|
|
2812
2795
|
promptGuidelines: [
|
|
2813
2796
|
"Use qdash_target_report before calibration changes when a specific qubit or coupling is under investigation.",
|
|
@@ -2836,7 +2819,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2836
2819
|
name: "qdash_dashboard",
|
|
2837
2820
|
label: "QDash Dashboard",
|
|
2838
2821
|
description: "Build a compact read-only QDash dashboard for the current profile/chip context.",
|
|
2839
|
-
promptSnippet: "Show QDash harness dashboard: chips, open
|
|
2822
|
+
promptSnippet: "Show QDash harness dashboard: chips, open Forum threads, recent executions, failed task results, and provenance stats",
|
|
2840
2823
|
promptGuidelines: [
|
|
2841
2824
|
"Use qdash_dashboard when the user asks for QDash status, overview, triage, or a dashboard.",
|
|
2842
2825
|
"Summarize dashboard data instead of dumping large raw payloads.",
|
|
@@ -2860,9 +2843,9 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2860
2843
|
pi.registerTool({
|
|
2861
2844
|
name: "qdash_dashboard_insights",
|
|
2862
2845
|
label: "QDash Dashboard Insights",
|
|
2863
|
-
description: "Correlate dashboard metrics, open
|
|
2846
|
+
description: "Correlate dashboard metrics, open Forum posts, and failed tasks into target-level insights with links.",
|
|
2864
2847
|
promptSnippet: "Generate QDash target insights from dashboard metrics, forum links, notes, and failed tasks",
|
|
2865
|
-
promptGuidelines: ["Use qdash_dashboard_insights when the user asks for insights across dashboard metrics, target summaries,
|
|
2848
|
+
promptGuidelines: ["Use qdash_dashboard_insights when the user asks for insights across dashboard metrics, target summaries, Forum posts, or anomalous targets."],
|
|
2866
2849
|
parameters: Type.Object({
|
|
2867
2850
|
...connectionParams,
|
|
2868
2851
|
chipId: Type.Optional(Type.String()),
|
|
@@ -2907,8 +2890,8 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2907
2890
|
pi.registerTool({
|
|
2908
2891
|
name: "qdash_investigate",
|
|
2909
2892
|
label: "QDash Investigate",
|
|
2910
|
-
description: "Run a read-only natural-language-friendly QDash investigation combining recent calibration results, target history, failures,
|
|
2911
|
-
promptSnippet: "Investigate a QDash target or recent calibration by correlating results, figures,
|
|
2893
|
+
description: "Run a read-only natural-language-friendly QDash investigation combining recent calibration results, target history, failures, Forum context, and safe recommendations.",
|
|
2894
|
+
promptSnippet: "Investigate a QDash target or recent calibration by correlating results, figures, and Forum context",
|
|
2912
2895
|
promptGuidelines: [
|
|
2913
2896
|
"Use qdash_investigate when the user asks to investigate, compare, or understand a QDash target or recent calibration in natural language.",
|
|
2914
2897
|
"This tool is read-only; do not execute tasks or change parameters based on its recommendation without explicit confirmation.",
|
|
@@ -2985,19 +2968,19 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
2985
2968
|
pi.registerTool({
|
|
2986
2969
|
name: "qdash_triage_overview",
|
|
2987
2970
|
label: "QDash Triage Overview",
|
|
2988
|
-
description: "Create a read-only triage overview from open
|
|
2989
|
-
promptSnippet: "Summarize QDash open
|
|
2971
|
+
description: "Create a read-only triage overview from open Forum threads and failed task results for the current QDash context.",
|
|
2972
|
+
promptSnippet: "Summarize QDash open Forum threads and failed task results for triage",
|
|
2990
2973
|
promptGuidelines: ["Use qdash_triage_overview when the user asks what to investigate next in QDash."],
|
|
2991
2974
|
parameters: Type.Object({ ...connectionParams, ...chipScopedParams, limit: Type.Optional(Type.Number()), color: Type.Optional(Type.Boolean({ description: "Emit ANSI colors in text output for terminal display." })) }),
|
|
2992
2975
|
async execute(_toolCallId, params: { profile?: string; configPath?: string; useEnv?: boolean; chipId?: string; limit?: number; color?: boolean }) {
|
|
2993
2976
|
const dashboard = await buildDashboard(params);
|
|
2994
2977
|
const triage = {
|
|
2995
2978
|
context: dashboard.context,
|
|
2996
|
-
|
|
2979
|
+
openThreads: dashboard.openThreads,
|
|
2997
2980
|
failedTaskResults: dashboard.failedTaskResults,
|
|
2998
2981
|
suggestedFocus: [
|
|
2999
2982
|
dashboard.failedTaskResults.count > 0 ? "Review failed task results first." : undefined,
|
|
3000
|
-
dashboard.
|
|
2983
|
+
dashboard.openThreads.count > 0 ? "Review open Forum threads and correlate with recent executions." : undefined,
|
|
3001
2984
|
].filter(Boolean),
|
|
3002
2985
|
};
|
|
3003
2986
|
const accent = (text: string) => params.color ? ansi("1;36", text) : text;
|
|
@@ -3005,7 +2988,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
3005
2988
|
const success = (text: string) => params.color ? ansi("32", text) : text;
|
|
3006
2989
|
const error = (text: string) => params.color ? ansi("31", text) : text;
|
|
3007
2990
|
const lines = boxed("QDash Triage", [
|
|
3008
|
-
`open
|
|
2991
|
+
`open threads ${triage.openThreads.count > 0 ? ansi("33", String(triage.openThreads.count)) : success(String(triage.openThreads.count))}`,
|
|
3009
2992
|
`failed tasks ${triage.failedTaskResults.count > 0 ? error(`${triage.failedTaskResults.shown}/${triage.failedTaskResults.count}`) : success(`${triage.failedTaskResults.shown}/${triage.failedTaskResults.count}`)}`,
|
|
3010
2993
|
"",
|
|
3011
2994
|
accent("Suggested focus"),
|
|
@@ -3017,11 +3000,11 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
3017
3000
|
return toTextToolResult(lines.join("\n"), triage, { tool: "qdash_triage_overview" });
|
|
3018
3001
|
},
|
|
3019
3002
|
renderResult(result, _options, theme) {
|
|
3020
|
-
const data = (result.details as { data?: {
|
|
3003
|
+
const data = (result.details as { data?: { openThreads?: { count?: number }; failedTaskResults?: { count?: number }; suggestedFocus?: string[] } } | undefined)?.data;
|
|
3021
3004
|
if (!data) return new Text(theme.fg("dim", "QDash triage unavailable"), 0, 0);
|
|
3022
3005
|
const lines = [
|
|
3023
3006
|
theme.fg("accent", theme.bold("QDash Triage")),
|
|
3024
|
-
`${theme.fg("muted", "open
|
|
3007
|
+
`${theme.fg("muted", "open threads")} ${theme.fg((data.openThreads?.count ?? 0) > 0 ? "warning" : "success", String(data.openThreads?.count ?? 0))}`,
|
|
3025
3008
|
`${theme.fg("muted", "failed tasks")} ${theme.fg((data.failedTaskResults?.count ?? 0) > 0 ? "error" : "success", String(data.failedTaskResults?.count ?? 0))}`,
|
|
3026
3009
|
"",
|
|
3027
3010
|
theme.fg("borderAccent", "Suggested focus"),
|
|
@@ -3293,7 +3276,7 @@ export default function qdashExtension(pi: ExtensionAPI) {
|
|
|
3293
3276
|
const dashboard = await buildDashboard({ limit: Number.isFinite(limit) ? limit : undefined });
|
|
3294
3277
|
refreshContextUi(ctx);
|
|
3295
3278
|
ctx.ui.setWidget("qdash", (_tui, theme) => dashboardComponent(dashboard, theme));
|
|
3296
|
-
ctx.ui.notify(`QDash dashboard updated: ${dashboard.
|
|
3279
|
+
ctx.ui.notify(`QDash dashboard updated: ${dashboard.openThreads.count} open Forum threads, ${dashboard.failedTaskResults.count} failed tasks`, "info");
|
|
3297
3280
|
},
|
|
3298
3281
|
});
|
|
3299
3282
|
|
|
@@ -250,10 +250,12 @@ export function registerCalibrationPipelineTools(pi: Pick<ExtensionAPI, "registe
|
|
|
250
250
|
pi.registerTool({
|
|
251
251
|
name: "qdash_run_pipeline",
|
|
252
252
|
label: "QDash Run Pipeline",
|
|
253
|
-
description: "Run a calibration pipeline spec as one QDash execution. Confirmation-gated write operation. Validate with qdash_plan_pipeline first; the spec runs exactly as validated.",
|
|
253
|
+
description: "Run a calibration pipeline spec as one QDash execution. Completed tasks write their calibrated parameters to QDash directly, without an agent candidate gate. Confirmation-gated write operation. Validate with qdash_plan_pipeline first; the spec runs exactly as validated.",
|
|
254
254
|
promptSnippet: "Run a validated QDash calibration pipeline spec",
|
|
255
255
|
promptGuidelines: [
|
|
256
256
|
"Use qdash_run_pipeline only for a spec that qdash_plan_pipeline reported valid.",
|
|
257
|
+
"Tell the user before running that completed tasks update calibrated parameters in QDash directly.",
|
|
258
|
+
"Do not use qdash_run_pipeline to run a task an agent session action failed to run; that skips the session's candidate gate. Report the failure instead.",
|
|
257
259
|
"After it starts, follow the execution with qdash_wait_execution and summarize failed targets.",
|
|
258
260
|
],
|
|
259
261
|
parameters: Type.Object({
|
|
@@ -265,7 +267,7 @@ export function registerCalibrationPipelineTools(pi: Pick<ExtensionAPI, "registe
|
|
|
265
267
|
}),
|
|
266
268
|
async execute(_toolCallId, params: ConnectionParams & { chipId: string; spec: Record<string, unknown>; backendName?: string; confirmWrite?: boolean }, _signal, _onUpdate, ctx) {
|
|
267
269
|
if (!params.confirmWrite) {
|
|
268
|
-
if (!ctx.hasUI || !(await ctx.ui.confirm("Run QDash calibration pipeline?", `This starts a calibration execution on chip ${params.chipId}.`))) {
|
|
270
|
+
if (!ctx.hasUI || !(await ctx.ui.confirm("Run QDash calibration pipeline?", `This starts a calibration execution on chip ${params.chipId}. Completed tasks update calibrated parameters in QDash directly.`))) {
|
|
269
271
|
throw new Error("qdash_run_pipeline requires explicit confirmation");
|
|
270
272
|
}
|
|
271
273
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oqtopus-team/pi-qdash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11-beta.20",
|
|
4
4
|
"description": "Pi extension for querying QDash via @oqtopus-team/qdash-client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"access": "public"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@oqtopus-team/qdash-client": "1.9.21-beta.
|
|
30
|
+
"@oqtopus-team/qdash-client": "1.9.21-beta.203"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"@earendil-works/pi-coding-agent": "*",
|
package/skills/qdash/SKILL.md
CHANGED
|
@@ -22,7 +22,6 @@ Prefer the dedicated tool that matches the question. Use `qdash_query` only as a
|
|
|
22
22
|
- `qdash_inspect_timeseries_csv`, `qdash_compare_timeseries` — validate and compare multiple QDash or CSV timeseries
|
|
23
23
|
- `qdash_list_task_results`, `qdash_get_task_result` — find and inspect calibration results
|
|
24
24
|
- `qdash_get_task_knowledge` — interpret a task's purpose, expected result, failure modes, and check questions
|
|
25
|
-
- `qdash_list_issues` — inspect tracked issues
|
|
26
25
|
- `qdash_list_flows`, `qdash_get_flow` — list or inspect calibration flows
|
|
27
26
|
- `qdash_get_pipeline_catalog`, `qdash_plan_pipeline` — compose and check a calibration pipeline spec before running it
|
|
28
27
|
- `qdash_list_executions`, `qdash_get_execution`, `qdash_wait_execution`, `qdash_compare_executions` — inspect, await, or compare executions
|
|
@@ -57,7 +56,7 @@ Workflow:
|
|
|
57
56
|
1. `qdash_get_pipeline_catalog` — read the step types (with what each needs from an earlier step and its default task list), the one-qubit modes, and the task names by type. Use only names from this catalog.
|
|
58
57
|
2. Turn the request into steps. Start from the catalog defaults and the flow templates (`qdash_query` with `flow_templates`, then `flow_template` for one template's task list): `OneQubitCheck` and `OneQubitFineTune` carry the standard one-qubit sequences, `TwoQubitCalibration` the two-qubit chain. Use `CustomOneQubit` / `CustomTwoQubit` only where the user wants a different task list, and keep the task order the templates use.
|
|
59
58
|
3. `qdash_plan_pipeline` — validate. A result with problems is not an error: each problem carries a path into the spec (`steps[2].tasks[0]`, `targets.qids[1]`); fix it and plan again until it is valid.
|
|
60
|
-
4. Show the user the resolved steps and task counts from the plan, then `qdash_run_pipeline` with the same `chipId` and `spec`. The execution is confirmation-gated.
|
|
59
|
+
4. Show the user the resolved steps and task counts from the plan, then `qdash_run_pipeline` with the same `chipId` and `spec`. The execution is confirmation-gated. Say that completed tasks write their calibrated parameters to QDash directly; there is no candidate gate, so inside an agent calibration session use the session's actions instead.
|
|
61
60
|
5. `qdash_wait_execution` with the returned `execution_id`. It returns when the whole run has finished, not just its first step. Then summarize which targets passed and which failed, fetch the figures of failed tasks with `qdash_get_task_figures` so the user sees them, and read `qdash_get_task_knowledge` for any task whose result needs interpretation.
|
|
62
61
|
|
|
63
62
|
Rules:
|
|
@@ -160,7 +159,7 @@ When the user wants to preserve an investigated observation in QDash forum/notes
|
|
|
160
159
|
- Never print tokens, passwords, Cloudflare Access secrets, or full profile contents.
|
|
161
160
|
- Treat write/operational endpoints (agent session/action creation, candidate commits, forum create/update, flow execute, git push/pull, re-execute, admin/auth APIs) as sensitive and ask the user before any write workflow.
|
|
162
161
|
- Write-oriented QDash tools are approval-gated. In interactive pi, let the extension prompt for confirmation. In non-interactive runs, set `confirmWrite: true` only after explicit user confirmation.
|
|
163
|
-
- Use `qdash_investigate` for natural-language requests to investigate, compare, or understand a target or recent calibration; it is read-only and combines recent results with target, Forum,
|
|
162
|
+
- Use `qdash_investigate` for natural-language requests to investigate, compare, or understand a target or recent calibration; it is read-only and combines recent results with target, Forum, and recommendation context.
|
|
164
163
|
- Use `qdash_compare_calibration` when the user asks what changed between recent calibration experiments; treat differences as evidence, not automatic approval to commit/apply parameters.
|
|
165
164
|
- Use `qdash_recent_calibration_figure` when the user asks to see recent experiment images without a task ID.
|
|
166
165
|
- Use `qdash_analyze_figure_json` for read-only numeric summaries of Plotly JSON calibration figures; keep domain-specific interpretation in the matching skill.
|
|
@@ -47,7 +47,7 @@ This skill defines how pi should behave as a conservative, confirmation-gated ca
|
|
|
47
47
|
- any in-cooldown wiring changes relevant to the target
|
|
48
48
|
- failed task(s)
|
|
49
49
|
- qid/coupling_id
|
|
50
|
-
- recent executions and open
|
|
50
|
+
- recent executions and open Forum context
|
|
51
51
|
2. Read evidence:
|
|
52
52
|
- `qdash_list_task_results`
|
|
53
53
|
- `qdash_get_task_result`
|
|
@@ -144,3 +144,7 @@ Stop and request human review when:
|
|
|
144
144
|
- the same target fails after `CheckChevron` and `Configure`
|
|
145
145
|
- figures suggest TLS, frequency collision, leakage, or multi-level behavior
|
|
146
146
|
- the next action would require broad parameter sweeps or hardware-risky changes
|
|
147
|
+
- QDash refuses to run an agent action's task before it measures anything (a platform or
|
|
148
|
+
configuration error, not a calibration result). Report the error with the task result and
|
|
149
|
+
execution links; do not rerun the task through another path such as `qdash_run_pipeline`,
|
|
150
|
+
which writes parameters without the session's candidate gate
|