metergraph-cli 0.2.3 → 0.2.5
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 +78 -14
- package/assets/skill/SKILL.md +124 -31
- package/assets/skill/manifest.json +3 -3
- package/assets/skills/manifest.json +37 -31
- package/assets/skills/metergraph-analyze/SKILL.md +161 -58
- package/assets/skills/metergraph-candidates/SKILL.md +50 -8
- package/assets/skills/metergraph-evals/SKILL.md +83 -16
- package/assets/skills/metergraph-investigate/SKILL.md +90 -34
- package/assets/skills/metergraph-iterate/SKILL.md +82 -12
- package/assets/skills/metergraph-local-bundle/SKILL.md +146 -128
- package/assets/skills/metergraph-model-swap/SKILL.md +142 -20
- package/assets/skills/metergraph-onboarding/SKILL.md +93 -49
- package/assets/skills/metergraph-price-check/SKILL.md +219 -0
- package/assets/skills/metergraph-report/SKILL.md +60 -15
- package/assets/skills/metergraph-workloads/SKILL.md +97 -13
- package/native/README.md +85 -0
- package/native/anchored-fs.c +243 -0
- package/native/bin/anchored-fs-darwin-arm64 +0 -0
- package/native/bin/anchored-fs-darwin-x64 +0 -0
- package/native/bin/anchored-fs-linux-arm64 +0 -0
- package/native/bin/anchored-fs-linux-x64 +0 -0
- package/native/build.mjs +55 -0
- package/native/manifest.json +9 -0
- package/native/targets.mjs +24 -0
- package/package.json +10 -3
- package/src/action-help.js +130 -0
- package/src/agent-key.js +21 -14
- package/src/anchored-fs.js +163 -0
- package/src/args.js +125 -17
- package/src/auth-binding.js +12 -7
- package/src/auth-callback.js +10 -2
- package/src/auth-login.js +111 -28
- package/src/auth-oauth.js +89 -45
- package/src/auth-session.js +19 -12
- package/src/auth-store.js +28 -7
- package/src/cli.js +52 -8
- package/src/constants.js +37 -9
- package/src/experiments-output.js +7 -6
- package/src/experiments.js +26 -17
- package/src/output.js +274 -79
- package/src/read-contract.js +42 -8
- package/src/read-output.js +66 -13
- package/src/read.js +90 -15
- package/src/repository-identity.js +2 -2
- package/src/setup-env-git.js +1 -1
- package/src/setup-env.js +16 -9
- package/src/setup-interactive.js +19 -4
- package/src/setup-refusal.js +2 -2
- package/src/setup-state.js +9 -4
- package/src/setup.js +15 -3
- package/src/skill-bundle.js +2 -2
- package/src/skill-trusted.js +56 -0
- package/src/skill.js +28 -9
- package/src/style.js +33 -7
- package/src/tools-output.js +73 -0
- package/src/tools.js +249 -0
- package/src/trace-contract.js +1 -1
- package/src/transport.js +21 -0
- package/src/verify-output.js +2 -2
- package/src/verify.js +5 -2
package/src/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
import { runRead } from "./read.js";
|
|
20
20
|
import { runExperimentStart } from "./experiments.js";
|
|
21
21
|
import { experimentsMessage, experimentsText } from "./experiments-output.js";
|
|
22
|
+
import { toolsMessage, toolsText } from "./tools-output.js";
|
|
22
23
|
import { readMessage, readText } from "./read-output.js";
|
|
23
24
|
import { listSkillPack, runSkill, runSkillPack } from "./skill.js";
|
|
24
25
|
import { runSetup } from "./setup.js";
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
canAsk,
|
|
30
31
|
commandLine,
|
|
31
32
|
fillSetupArgv,
|
|
33
|
+
fillLoginArgv,
|
|
32
34
|
offerAgentAccess,
|
|
33
35
|
terminalPrompter,
|
|
34
36
|
} from "./setup-interactive.js";
|
|
@@ -63,7 +65,12 @@ async function run(argv, { stdout, stderr, stdin }) {
|
|
|
63
65
|
let parsed = parseArgs(argv);
|
|
64
66
|
const style = createStyle(stdout);
|
|
65
67
|
const say = (...lines) => stdout.write(lines.map((line) => `${line}\n`).join(""));
|
|
66
|
-
const asking = canAsk(argv, { stdin, stdout });
|
|
68
|
+
const asking = canAsk(argv, { stdin, stdout, stderr });
|
|
69
|
+
|
|
70
|
+
if (asking && parsed.command === "login") {
|
|
71
|
+
argv = fillLoginArgv(argv);
|
|
72
|
+
parsed = parseArgs(argv);
|
|
73
|
+
}
|
|
67
74
|
|
|
68
75
|
// A person at a terminal is asked for what setup is missing; the answers
|
|
69
76
|
// become flags and go through the same parsing as a script's.
|
|
@@ -95,13 +102,20 @@ async function run(argv, { stdout, stderr, stdin }) {
|
|
|
95
102
|
message: parsed.message,
|
|
96
103
|
});
|
|
97
104
|
if (parsed.json) stdout.write(toJsonLine(result));
|
|
98
|
-
else
|
|
105
|
+
else {
|
|
106
|
+
const errorStyle = createStyle(stderr);
|
|
107
|
+
const [first, ...rest] = paragraph(errorStyle, `Error: ${parsed.message}`, "");
|
|
108
|
+
const help = parsed.command && parsed.command !== "help"
|
|
109
|
+
? `metergraph ${parsed.command} --help` : "metergraph --help";
|
|
110
|
+
stderr.write(render([first.replace(/^Error:/, errorStyle.red("Error:")), ...rest,
|
|
111
|
+
"", ...steps(errorStyle, [["Help", help]])]));
|
|
112
|
+
}
|
|
99
113
|
return result;
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
if (parsed.command === "help") {
|
|
103
|
-
const result = envelope({ command: "help", outcome: "ok", data: helpData(parsed.topic) });
|
|
104
|
-
stdout.write(parsed.json ? toJsonLine(result) : helpText(parsed.topic, style));
|
|
117
|
+
const result = envelope({ command: "help", outcome: "ok", data: helpData(parsed.topic, parsed.action) });
|
|
118
|
+
stdout.write(parsed.json ? toJsonLine(result) : helpText(parsed.topic, style, parsed.action, parsed.advanced));
|
|
105
119
|
return result;
|
|
106
120
|
}
|
|
107
121
|
|
|
@@ -181,7 +195,7 @@ async function run(argv, { stdout, stderr, stdin }) {
|
|
|
181
195
|
}
|
|
182
196
|
|
|
183
197
|
if (READ_COMMANDS.includes(parsed.command)) {
|
|
184
|
-
const { outcome, reason, data } = await runRead(parsed);
|
|
198
|
+
const { outcome, reason, data, validateOutput } = await runRead(parsed);
|
|
185
199
|
const message = readMessage(outcome, reason);
|
|
186
200
|
const result = envelope({
|
|
187
201
|
command: parsed.command,
|
|
@@ -190,14 +204,16 @@ async function run(argv, { stdout, stderr, stdin }) {
|
|
|
190
204
|
data,
|
|
191
205
|
message: outcome === "ok" ? null : message,
|
|
192
206
|
});
|
|
193
|
-
|
|
207
|
+
const text = parsed.json ? toJsonLine(result) : readText(result, message, style);
|
|
208
|
+
if (validateOutput && !validateOutput(text)) return refuseCredentialOutput(parsed, stdout, style, validateOutput);
|
|
209
|
+
stdout.write(text);
|
|
194
210
|
return result;
|
|
195
211
|
}
|
|
196
212
|
|
|
197
213
|
if (parsed.command === "experiments") {
|
|
198
214
|
// start is the one write: its own key session (experiments.js). The other
|
|
199
215
|
// actions are Metadata reads through runRead's shared session.
|
|
200
|
-
const { outcome, reason, data } = parsed.action === "start" ? await runExperimentStart(parsed) : await runRead(parsed);
|
|
216
|
+
const { outcome, reason, data, validateOutput } = parsed.action === "start" ? await runExperimentStart(parsed) : await runRead(parsed);
|
|
201
217
|
const message = experimentsMessage(outcome, reason);
|
|
202
218
|
const result = envelope({
|
|
203
219
|
command: `experiments ${parsed.action}`,
|
|
@@ -206,7 +222,26 @@ async function run(argv, { stdout, stderr, stdin }) {
|
|
|
206
222
|
data,
|
|
207
223
|
message: outcome === "ok" ? null : message,
|
|
208
224
|
});
|
|
209
|
-
|
|
225
|
+
const text = parsed.json ? toJsonLine(result) : experimentsText(result, message, style, parsed.action);
|
|
226
|
+
if (validateOutput && !validateOutput(text)) return refuseCredentialOutput(parsed, stdout, style, validateOutput);
|
|
227
|
+
stdout.write(text);
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (parsed.command === "tools") {
|
|
232
|
+
// Any Agent Access tool, through runRead's verified session (tools.js).
|
|
233
|
+
const { outcome, reason, data, validateOutput } = await runRead(parsed);
|
|
234
|
+
const message = toolsMessage(outcome, reason);
|
|
235
|
+
const result = envelope({
|
|
236
|
+
command: `tools ${parsed.action}`,
|
|
237
|
+
outcome,
|
|
238
|
+
reason,
|
|
239
|
+
data,
|
|
240
|
+
message: outcome === "ok" ? null : message,
|
|
241
|
+
});
|
|
242
|
+
const text = parsed.json ? toJsonLine(result) : toolsText(result, message, style, parsed.action);
|
|
243
|
+
if (validateOutput && !validateOutput(text)) return refuseCredentialOutput(parsed, stdout, style, validateOutput);
|
|
244
|
+
stdout.write(text);
|
|
210
245
|
return result;
|
|
211
246
|
}
|
|
212
247
|
|
|
@@ -390,3 +425,12 @@ function repositoryValue(repository) {
|
|
|
390
425
|
default: return "not recorded (no owner/name git remote; pass --repository OWNER/NAME)";
|
|
391
426
|
}
|
|
392
427
|
}
|
|
428
|
+
|
|
429
|
+
function refuseCredentialOutput(parsed, stdout, style, validateOutput) {
|
|
430
|
+
const reason = "credential_in_metadata_response";
|
|
431
|
+
const message = readMessage("verification_failed", reason);
|
|
432
|
+
const result = envelope({ command: parsed.command, outcome: "verification_failed", reason, message });
|
|
433
|
+
const text = parsed.json ? toJsonLine(result) : `${message}\n`;
|
|
434
|
+
if (validateOutput(text)) stdout.write(text);
|
|
435
|
+
return result;
|
|
436
|
+
}
|
package/src/constants.js
CHANGED
|
@@ -64,9 +64,38 @@ export const LOGIN_MIN_TIMEOUT_MS = 1000;
|
|
|
64
64
|
export const LOGIN_MAX_TIMEOUT_MS = 900000;
|
|
65
65
|
export const AUTH_HTTP_TIMEOUT_MS = 15000;
|
|
66
66
|
|
|
67
|
-
// The
|
|
67
|
+
// The scope sign in requests. It never asks for agent:read (Debug) or
|
|
68
68
|
// agent:replay, and never falls back to them.
|
|
69
69
|
export const METADATA_SCOPE = "agent:metadata";
|
|
70
|
+
// The Debug role and the workflow permissions an owner adds to it, by the
|
|
71
|
+
// names the Keys page uses. Writing and testing evaluations and starting
|
|
72
|
+
// analyses need Debug with all three. Replay is never used by this CLI.
|
|
73
|
+
export const DEBUG_SCOPE = "agent:read";
|
|
74
|
+
export const REPLAY_SCOPE = "agent:replay";
|
|
75
|
+
export const WORKFLOW_PERMISSIONS = Object.freeze([
|
|
76
|
+
Object.freeze({ field: "allow_evaluation_writes", scope: "agent:eval-write", label: "Allow evaluation writes" }),
|
|
77
|
+
Object.freeze({ field: "allow_evaluation_previews", scope: "agent:eval-preview", label: "Allow evaluation previews" }),
|
|
78
|
+
Object.freeze({ field: "allow_analysis_starts", scope: "agent:analysis-run", label: "Allow analysis starts" }),
|
|
79
|
+
]);
|
|
80
|
+
export const WORKFLOW_SCOPES = Object.freeze(WORKFLOW_PERMISSIONS.map(({ scope }) => scope).sort());
|
|
81
|
+
// The permissions are offered where the deployment reports managed
|
|
82
|
+
// evaluations; customer-owned AWS (byoc-core) and the open-source server do not.
|
|
83
|
+
export const WORKFLOW_DEPLOYMENT_CAPABILITY = "evaluations";
|
|
84
|
+
|
|
85
|
+
// The scope login asks for and a saved sign in records: Metadata, or Debug
|
|
86
|
+
// followed by the chosen workflow permissions in WORKFLOW_SCOPES order.
|
|
87
|
+
export function loginScope(role, permissions = []) {
|
|
88
|
+
if (role !== "debug") return METADATA_SCOPE;
|
|
89
|
+
return [DEBUG_SCOPE, ...WORKFLOW_SCOPES.filter((scope) => permissions.includes(scope))].join(" ");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Whether a saved sign in's scope is one login asks for, in that exact form.
|
|
93
|
+
export function isLoginScope(scope) {
|
|
94
|
+
if (scope === METADATA_SCOPE) return true;
|
|
95
|
+
if (typeof scope !== "string") return false;
|
|
96
|
+
const [first, ...rest] = scope.split(" ");
|
|
97
|
+
return first === DEBUG_SCOPE && scope === loginScope("debug", rest);
|
|
98
|
+
}
|
|
70
99
|
|
|
71
100
|
// The service's agent access contract version, as sent in schema_version by
|
|
72
101
|
// GET /v1/agent/workspace and GET /v1/agent/capabilities. It is the
|
|
@@ -134,20 +163,19 @@ export const USAGE_COMPARISONS = Object.freeze(["previous", "previous-same-time"
|
|
|
134
163
|
// Experiments: replay a workload's recent production calls on 1 to 3
|
|
135
164
|
// treatments and compare them with production. The list, estimate and
|
|
136
165
|
// summary are Metadata reads at these fixed paths (one experiment is
|
|
137
|
-
// EXPERIMENT_PATHS.list plus "/" and its UUID). Starting one
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
// request and nothing else. The limits match the service's own.
|
|
166
|
+
// EXPERIMENT_PATHS.list plus "/" and its UUID). Starting one needs a
|
|
167
|
+
// coding-agent key with Debug access and the EXPERIMENT_START_SCOPE grant.
|
|
168
|
+
// The limits match the service's own.
|
|
141
169
|
export const EXPERIMENT_PATHS = Object.freeze({
|
|
142
170
|
list: "/v1/agent/experiments",
|
|
143
171
|
estimate: "/v1/agent/experiments/estimate",
|
|
144
172
|
start: "/v1/agent/experiments",
|
|
145
173
|
});
|
|
146
174
|
export const EXPERIMENT_START_SCOPE = "agent:analysis-run";
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
export const EXPERIMENT_START_KEY_SCOPES = Object.freeze([
|
|
175
|
+
// The scopes a start key must carry: Debug and the start grant. It may also
|
|
176
|
+
// carry the other workflow permissions; a key with
|
|
177
|
+
// Replay, Metadata only or any other scope is refused.
|
|
178
|
+
export const EXPERIMENT_START_KEY_SCOPES = Object.freeze([DEBUG_SCOPE, EXPERIMENT_START_SCOPE]);
|
|
151
179
|
export const EXPERIMENT_LAST_N = Object.freeze({ min: 10, max: 40, default: 20 });
|
|
152
180
|
export const EXPERIMENT_MAX_TREATMENTS = 3;
|
|
153
181
|
export const EXPERIMENT_MAX_TRACKED_FIELDS = 10;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { STATISTICS } from "./experiments-contract.js";
|
|
2
2
|
import { readMessage } from "./read-output.js";
|
|
3
|
-
import { PLAIN, fields, headline, paragraph, render, steps, toneFor } from "./style.js";
|
|
3
|
+
import { PLAIN, fields, headline, paragraph, render, steps, toneFor, workspaceShown } from "./style.js";
|
|
4
4
|
|
|
5
5
|
// Fixed text for experiments results. Nothing here contains a value from a
|
|
6
6
|
// service response other than validated metadata; the service's own refusal
|
|
@@ -11,11 +11,12 @@ const MESSAGES = {
|
|
|
11
11
|
"This deployment does not offer experiments to this credential, for example because the feature is off. " +
|
|
12
12
|
"Nothing was read or started.",
|
|
13
13
|
experiment_start_key_required:
|
|
14
|
-
"Starting an experiment needs a coding-agent key with Debug
|
|
15
|
-
"--agent-token-file or METERGRAPH_AGENT_TOKEN. A saved sign in is Metadata only.
|
|
14
|
+
"Starting an experiment needs a coding-agent key with the Debug role and Allow analysis starts, in " +
|
|
15
|
+
"--agent-token-file or METERGRAPH_AGENT_TOKEN. A saved sign in is Metadata only. " +
|
|
16
|
+
"Nothing was sent.",
|
|
16
17
|
experiment_start_not_granted:
|
|
17
|
-
"This key cannot start experiments: it needs
|
|
18
|
-
"workspace owner, and
|
|
18
|
+
"This key cannot start experiments: it needs Debug access and the Allow analysis starts grant from a " +
|
|
19
|
+
"workspace owner, and no Replay access. Nothing was started.",
|
|
19
20
|
experiment_start_unavailable:
|
|
20
21
|
"The service does not offer experiment starts to this key, for example because the feature is off or the " +
|
|
21
22
|
"granting owner is no longer an owner. Nothing was started.",
|
|
@@ -165,7 +166,7 @@ export function experimentsText(result, message, style = PLAIN, action) {
|
|
|
165
166
|
style.dim(action === "start" ? "Replays captured prompts within the server's budget" : "Metadata only")}`,
|
|
166
167
|
"",
|
|
167
168
|
];
|
|
168
|
-
const rows = [["Origin", data.origin], ["Workspace", data.workspace
|
|
169
|
+
const rows = [["Origin", data.origin], ["Workspace", workspaceShown(data.workspace)]];
|
|
169
170
|
if (result.ok) {
|
|
170
171
|
const [more, body] = resultLines(action, data.result);
|
|
171
172
|
lines.push(...fields(style, [...rows, ...more]));
|
package/src/experiments.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { agentKeySource, keySession, keyTarget, readAgentKey } from "./agent-key.js";
|
|
2
2
|
import { readBinding, resolveProject } from "./auth-binding.js";
|
|
3
|
-
import { Stop, openStore,
|
|
3
|
+
import { Stop, openStore, readProjectCredential, resolveConfigDir } from "./auth-store.js";
|
|
4
4
|
import { DEFAULT_ORIGIN, EXPERIMENT_PATHS, EXPERIMENT_START_SCOPE, METADATA_SCOPE, READ_MAX_BYTES } from "./constants.js";
|
|
5
5
|
import { AGENT_URL_ENV, classifyEnvironment, handoffPages } from "./environment.js";
|
|
6
6
|
import {
|
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
} from "./experiments-contract.js";
|
|
13
13
|
import { parseJsonObject } from "./http.js";
|
|
14
14
|
import { parseOrigin } from "./origin.js";
|
|
15
|
-
import {
|
|
15
|
+
import { workspaceLabel, workspacePinned } from "./read-contract.js";
|
|
16
|
+
import { holdsKnown, outputHoldsKnown } from "./read.js";
|
|
16
17
|
import { deadline, failureOf, send, trapSignals } from "./transport.js";
|
|
17
18
|
|
|
18
19
|
// Experiments: replay a workload's recent production calls on 1 to 3
|
|
@@ -20,15 +21,15 @@ import { deadline, failureOf, send, trapSignals } from "./transport.js";
|
|
|
20
21
|
//
|
|
21
22
|
// list, estimate and get are Metadata reads. runRead (read.js) establishes
|
|
22
23
|
// their session exactly as for every other read command, a saved grant or a
|
|
23
|
-
//
|
|
24
|
+
// coding-agent key, and calls experimentRead with it. They send only these
|
|
24
25
|
// requests and print only validated fields; no input, output or tracked
|
|
25
26
|
// field value is ever requested.
|
|
26
27
|
//
|
|
27
|
-
// start
|
|
28
|
-
//
|
|
29
|
-
// --agent-token-file or METERGRAPH_AGENT_TOKEN, verified
|
|
30
|
-
// (auth-oauth.js checkStartWorkspace and
|
|
31
|
-
//
|
|
28
|
+
// start changes a workspace. It needs a coding-agent key with Debug access
|
|
29
|
+
// and the agent:analysis-run grant (Allow analysis starts), from
|
|
30
|
+
// --agent-token-file or METERGRAPH_AGENT_TOKEN, verified before it is used
|
|
31
|
+
// for this one POST (auth-oauth.js checkStartWorkspace and
|
|
32
|
+
// checkStartCapabilities). A saved sign in is Metadata only and never starts
|
|
32
33
|
// one. Each start creates a new experiment, so a start that succeeded is not
|
|
33
34
|
// retried.
|
|
34
35
|
|
|
@@ -167,8 +168,8 @@ function pollOrOpen(experiment, { started = false } = {}) {
|
|
|
167
168
|
return {
|
|
168
169
|
kind: "poll_experiment",
|
|
169
170
|
message: started
|
|
170
|
-
? `The experiment is ${experiment.status}. Poll it in about ${POLL_AFTER_SECONDS} seconds with
|
|
171
|
-
"
|
|
171
|
+
? `The experiment is ${experiment.status}. Poll it in about ${POLL_AFTER_SECONDS} seconds with ` +
|
|
172
|
+
"this project's sign in or a coding-agent key."
|
|
172
173
|
: `The experiment is ${experiment.status}. Run the same command again in about ${POLL_AFTER_SECONDS} seconds.`,
|
|
173
174
|
command: `metergraph experiments get --id ${experiment.id} --json`,
|
|
174
175
|
poll_after_seconds: POLL_AFTER_SECONDS,
|
|
@@ -217,6 +218,7 @@ export async function runExperimentStart(options) {
|
|
|
217
218
|
origin: null,
|
|
218
219
|
workspaceId: null,
|
|
219
220
|
intendedWorkspace: null,
|
|
221
|
+
workspaceLabel: { slug: null, name: null },
|
|
220
222
|
profile: null,
|
|
221
223
|
authenticated: false,
|
|
222
224
|
scopes: [],
|
|
@@ -234,9 +236,11 @@ export async function runExperimentStart(options) {
|
|
|
234
236
|
const data = startData(ctx, notices, outcome, report);
|
|
235
237
|
if (holdsKnown(data, known)) {
|
|
236
238
|
return { outcome: "verification_failed", reason: "credential_in_metadata_response",
|
|
237
|
-
data: startData({ ...ctx, reason: "credential_in_metadata_response"
|
|
239
|
+
data: startData({ ...ctx, reason: "credential_in_metadata_response", workspaceLabel: { slug: null, name: null } },
|
|
240
|
+
new Set(), "verification_failed", null),
|
|
241
|
+
validateOutput: (text) => !outputHoldsKnown(text, known) };
|
|
238
242
|
}
|
|
239
|
-
return { outcome, reason, data };
|
|
243
|
+
return { outcome, reason, data, validateOutput: (text) => !outputHoldsKnown(text, known) };
|
|
240
244
|
};
|
|
241
245
|
const settle = (result) => {
|
|
242
246
|
const interrupted =
|
|
@@ -257,7 +261,7 @@ export async function runExperimentStart(options) {
|
|
|
257
261
|
if (binding !== null) {
|
|
258
262
|
try {
|
|
259
263
|
const store = openStore(resolveConfigDir(options.configDir), { create: false });
|
|
260
|
-
const record = store === null ? null :
|
|
264
|
+
const record = store === null ? null : readProjectCredential(store, binding.credential_slot, root);
|
|
261
265
|
originConfirmed = record !== null && record.origin === binding.origin;
|
|
262
266
|
} catch (error) {
|
|
263
267
|
if (!(error instanceof Stop)) throw error;
|
|
@@ -279,6 +283,10 @@ export async function runExperimentStart(options) {
|
|
|
279
283
|
return end(settled.outcome, reason);
|
|
280
284
|
}
|
|
281
285
|
ctx.profile = session.session.profile;
|
|
286
|
+
if (target.workspaceText !== null && !workspacePinned(session.documents.workspace?.workspace, target.workspaceText)) {
|
|
287
|
+
return end("verification_failed", "workspace_context_mismatch");
|
|
288
|
+
}
|
|
289
|
+
ctx.workspaceLabel = workspaceLabel(session.documents.workspace?.workspace);
|
|
282
290
|
if (ctx.workspaceId === null) {
|
|
283
291
|
ctx.workspaceId = session.session.workspaceId;
|
|
284
292
|
notices.add("workspace_not_pinned");
|
|
@@ -335,7 +343,7 @@ function unconfirmed(response, failure) {
|
|
|
335
343
|
function startData(ctx, notices, outcome, report) {
|
|
336
344
|
return {
|
|
337
345
|
origin: ctx.origin,
|
|
338
|
-
workspace: ctx.workspaceId === null ? null : { id: ctx.workspaceId },
|
|
346
|
+
workspace: ctx.workspaceId === null ? null : { id: ctx.workspaceId, ...ctx.workspaceLabel },
|
|
339
347
|
deployment_profile: ctx.profile,
|
|
340
348
|
environment: ctx.environment.kind,
|
|
341
349
|
credential: "agent_key",
|
|
@@ -356,7 +364,7 @@ function startNextAction(outcome, ctx, report) {
|
|
|
356
364
|
kind: "check_before_retry",
|
|
357
365
|
message:
|
|
358
366
|
"The start request reached the service, but its answer was not confirmed: an experiment may exist and " +
|
|
359
|
-
"may be running. List the newest experiments with
|
|
367
|
+
"may be running. List the newest experiments with this project's sign in or a coding-agent key before " +
|
|
360
368
|
"starting again; each start creates a new experiment.",
|
|
361
369
|
command: "metergraph experiments list --json",
|
|
362
370
|
};
|
|
@@ -376,8 +384,9 @@ function startNextAction(outcome, ctx, report) {
|
|
|
376
384
|
kind: "use_experiment_start_key",
|
|
377
385
|
url: keys,
|
|
378
386
|
message:
|
|
379
|
-
"Starting an experiment needs a coding-agent key with Debug
|
|
380
|
-
"a workspace owner can grant on the Keys page
|
|
387
|
+
"Starting an experiment needs a coding-agent key with the Debug role and Allow analysis starts, which " +
|
|
388
|
+
"only a workspace owner can grant on the Keys page (or, on the customer-local bundle, with " +
|
|
389
|
+
"bin/agent-key). Pass it with --agent-token-file or METERGRAPH_AGENT_TOKEN; " +
|
|
381
390
|
"do not ask for it in chat. A start replays the sampled calls' captured prompts on each treatment's " +
|
|
382
391
|
"provider within the server's budget. Or start the experiment in the dashboard.",
|
|
383
392
|
required_scope: EXPERIMENT_START_SCOPE,
|