@wrenai/genbi 0.0.5 → 0.0.6
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/dist-server/harness/route/codex-ask.d.ts +1 -1
- package/dist-server/harness/route/codex-ask.js +5 -46
- package/dist-server/harness/route/dispatched.d.ts +20 -0
- package/dist-server/harness/route/dispatched.js +122 -24
- package/dist-server/harness/route/host-query.d.ts +93 -0
- package/dist-server/harness/route/{codex-host-query.js → host-query.js} +120 -54
- package/dist-server/harness/route/route.js +3 -0
- package/dist-server/harness/route/types.d.ts +18 -1
- package/dist-server/harness/route/{codex-wren-proxy.js → wren-host-proxy.js} +6 -5
- package/dist-server/harness/setup/runner.d.ts +15 -3
- package/dist-server/harness/setup/runner.js +234 -72
- package/dist-server/harness/tools/setup-native.d.ts +7 -0
- package/dist-server/harness/tools/setup-native.js +7 -4
- package/dist-server/server/app.js +13 -4
- package/dist-server/server/bin.js +8 -0
- package/dist-server/server/fold.d.ts +9 -8
- package/dist-server/server/fold.js +17 -12
- package/dist-server/server/host-evidence-grounding.d.ts +21 -0
- package/dist-server/server/{codex-local-grounding.js → host-evidence-grounding.js} +15 -14
- package/dist-server/server/native-artifacts.d.ts +93 -4
- package/dist-server/server/native-artifacts.js +153 -4
- package/dist-server/server/turn.js +4 -1
- package/package.json +2 -2
- package/dist-server/harness/route/codex-host-query.d.ts +0 -52
- package/dist-server/server/codex-local-grounding.d.ts +0 -18
- /package/dist-server/harness/route/{codex-wren-proxy.d.ts → wren-host-proxy.d.ts} +0 -0
|
@@ -124,6 +124,22 @@ function resolveDispatchedUserProject(runOptions, agentId) {
|
|
|
124
124
|
}
|
|
125
125
|
return canonicalProject;
|
|
126
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* The project directory a project-bound step (`connect_resume`/`context`) runs
|
|
129
|
+
* against, as the default exec cwd of the in-process `setup_execution` tool.
|
|
130
|
+
* `undefined` for the initial `connect` step, which scaffolds the project from
|
|
131
|
+
* the workspace root, and for a missing/unsafe project name. The tool applies
|
|
132
|
+
* it only while the directory actually exists.
|
|
133
|
+
*/
|
|
134
|
+
function boundSetupProjectDir(runOptions, agentId) {
|
|
135
|
+
const stepKey = runOptions.stepKey ?? (agentId === BUILD_CONTEXT_AGENT_ID ? "context" : "connect");
|
|
136
|
+
if (stepKey === "connect")
|
|
137
|
+
return undefined;
|
|
138
|
+
const projectName = runOptions.projectName;
|
|
139
|
+
if (projectName === undefined || !SAFE_PROJECT_NAME.test(projectName))
|
|
140
|
+
return undefined;
|
|
141
|
+
return path.resolve(runOptions.workspaceRoot, projectName);
|
|
142
|
+
}
|
|
127
143
|
/**
|
|
128
144
|
* Dispatches a setup-wizard turn over dispatched, bypassing `route()` entirely
|
|
129
145
|
* (unlike the Ask turn path) so it can perform its own, setup-specific
|
|
@@ -562,8 +578,14 @@ export class InProcessSetupRunner {
|
|
|
562
578
|
// project-root write scope on the dispatched side.
|
|
563
579
|
const policy = { readOnly: false, artifactWriteScope: "." };
|
|
564
580
|
const env = createLocalExecutionEnv({ rootDir: runOptions.workspaceRoot });
|
|
581
|
+
const boundProjectDir = boundSetupProjectDir(runOptions, agentId);
|
|
565
582
|
const nativeTools = createNativeToolRegistry();
|
|
566
|
-
nativeTools.register(SETUP_EXECUTION_TOOL_NAME, () => createSetupExecutionTool({
|
|
583
|
+
nativeTools.register(SETUP_EXECUTION_TOOL_NAME, () => createSetupExecutionTool({
|
|
584
|
+
env,
|
|
585
|
+
policy,
|
|
586
|
+
workspaceRoot: runOptions.workspaceRoot,
|
|
587
|
+
...(boundProjectDir !== undefined ? { defaultExecCwd: boundProjectDir } : {}),
|
|
588
|
+
}));
|
|
567
589
|
// Kept in lockstep with `effectiveMaxTurns()` above via the shared
|
|
568
590
|
// `resolveEffectiveMaxTurns` helper, exactly like `DispatchedSetupRunner.run()`
|
|
569
591
|
// does for its own `--max-turns` flag.
|
|
@@ -1052,6 +1074,64 @@ const SCHEMA_INTROSPECTION_COMMANDS = [
|
|
|
1052
1074
|
/\b(?:psql|mysql|sqlite3|duckdb|snowsql|clickhouse-client|trino(?:-cli)?)\b[\s\S]*(?:\s(?:-c|--command|--execute|--query)\s|\b(?:information_schema|show\s+(?:tables|columns)|describe\s+(?:table|schema)|\.tables)\b)/i,
|
|
1053
1075
|
/\bbq\s+query\b/i,
|
|
1054
1076
|
];
|
|
1077
|
+
const CD_PREFIX = /^\s*cd\s+("[^"$`\\]*"|'[^']*'|[^\s;&|<>()$`\\'"~]+)\s*&&\s*([\s\S]*)$/;
|
|
1078
|
+
const STARTS_WITH_CD = /^\s*cd(?:\s|$)/;
|
|
1079
|
+
/**
|
|
1080
|
+
* A recorded relative `cwd` resolves against the tool's own root: the workspace
|
|
1081
|
+
* for the in-process tool, the project for Codex local (whose tool root is the
|
|
1082
|
+
* project directory for project steps).
|
|
1083
|
+
*/
|
|
1084
|
+
function recordedCwdBase(entry, cwd, binding) {
|
|
1085
|
+
return path.resolve(entry.label === CODEX_SETUP_EXECUTION_TOOL_NAME ? binding.projectDir : binding.root, cwd);
|
|
1086
|
+
}
|
|
1087
|
+
function samePath(a, b) {
|
|
1088
|
+
const resolvedA = path.resolve(a);
|
|
1089
|
+
const resolvedB = path.resolve(b);
|
|
1090
|
+
if (resolvedA === resolvedB)
|
|
1091
|
+
return true;
|
|
1092
|
+
try {
|
|
1093
|
+
return realpathSync(resolvedA) === realpathSync(resolvedB);
|
|
1094
|
+
}
|
|
1095
|
+
catch {
|
|
1096
|
+
return false;
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* The ONE recogniser rule shared by schema discovery and the context
|
|
1101
|
+
* lifecycle: a recorded command counts only as the bare command or as exactly
|
|
1102
|
+
* one `cd <dir> && <cmd>` whose `<dir>` is the bound project. Returns the
|
|
1103
|
+
* command to match against the per-stage patterns (the `cd` prefix stripped),
|
|
1104
|
+
* or `undefined` when the shape is not recognised (another directory, a second
|
|
1105
|
+
* `cd`, a dynamic or `~` directory, or no binding to verify against).
|
|
1106
|
+
*
|
|
1107
|
+
* `<dir>` resolves against the exec's explicit `cwd` when the entry recorded
|
|
1108
|
+
* one. Otherwise the effective cwd was not recorded, so the target is accepted
|
|
1109
|
+
* if it names the project from either candidate default (workspace root, the
|
|
1110
|
+
* pre-binding default; or the project itself, the post-binding default). The
|
|
1111
|
+
* entry already exited 0, so the `cd` really resolved to that directory.
|
|
1112
|
+
*/
|
|
1113
|
+
function projectLocalCommand(entry, command, binding) {
|
|
1114
|
+
const explicitCwd = entry.input?.cwd;
|
|
1115
|
+
const recordedCwd = typeof explicitCwd === "string" && explicitCwd.length > 0 ? explicitCwd : undefined;
|
|
1116
|
+
if (!STARTS_WITH_CD.test(command)) {
|
|
1117
|
+
if (binding === undefined || recordedCwd === undefined)
|
|
1118
|
+
return command;
|
|
1119
|
+
// A recorded cwd must itself be the project for a bare command to count.
|
|
1120
|
+
return samePath(recordedCwdBase(entry, recordedCwd, binding), binding.projectDir) ? command : undefined;
|
|
1121
|
+
}
|
|
1122
|
+
if (binding === undefined)
|
|
1123
|
+
return undefined;
|
|
1124
|
+
const match = CD_PREFIX.exec(command);
|
|
1125
|
+
if (!match)
|
|
1126
|
+
return undefined;
|
|
1127
|
+
const raw = match[1];
|
|
1128
|
+
const dir = raw.startsWith('"') || raw.startsWith("'") ? raw.slice(1, -1) : raw;
|
|
1129
|
+
const inner = match[2];
|
|
1130
|
+
if (dir.length === 0 || STARTS_WITH_CD.test(inner))
|
|
1131
|
+
return undefined;
|
|
1132
|
+
const bases = recordedCwd !== undefined ? [recordedCwdBase(entry, recordedCwd, binding)] : [binding.root, binding.projectDir];
|
|
1133
|
+
return bases.some((base) => samePath(path.resolve(base, dir), binding.projectDir)) ? inner : undefined;
|
|
1134
|
+
}
|
|
1055
1135
|
/**
|
|
1056
1136
|
* The single setup-workflow contract for recognisable schema discovery. A
|
|
1057
1137
|
* matching command is evidence only after it completed successfully (see
|
|
@@ -1060,13 +1140,18 @@ const SCHEMA_INTROSPECTION_COMMANDS = [
|
|
|
1060
1140
|
* this for both terminal acceptance and the no-introspection diagnostic,
|
|
1061
1141
|
* rather than reimplementing the allowlist elsewhere.
|
|
1062
1142
|
*/
|
|
1063
|
-
export function classifyRecordedSchemaDiscovery(worklog) {
|
|
1143
|
+
export function classifyRecordedSchemaDiscovery(worklog, binding) {
|
|
1064
1144
|
let failed;
|
|
1065
1145
|
for (const entry of worklog) {
|
|
1066
1146
|
const command = isSetupExecutionEntry(entry) ? execCommandOf(entry) : undefined;
|
|
1067
|
-
if (command === undefined
|
|
1068
|
-
|
|
1069
|
-
|
|
1147
|
+
if (command === undefined)
|
|
1148
|
+
continue;
|
|
1149
|
+
// Without a binding (legacy callers) the matching below is unchanged; with
|
|
1150
|
+
// one, a leading `cd` must satisfy the shared project-local rule.
|
|
1151
|
+
const recognised = binding === undefined ? command : projectLocalCommand(entry, command, binding);
|
|
1152
|
+
if (recognised === undefined ||
|
|
1153
|
+
!SCHEMA_DISCOVERY_EVIDENCE.test(recognised) ||
|
|
1154
|
+
!SCHEMA_INTROSPECTION_COMMANDS.some((pattern) => pattern.test(recognised))) {
|
|
1070
1155
|
continue;
|
|
1071
1156
|
}
|
|
1072
1157
|
const succeeded = execSucceeded(entry);
|
|
@@ -1087,7 +1172,7 @@ const DIRECT_CONTEXT_LIFECYCLE_COMMAND = new RegExp(String.raw `^\s*(?:(?:${SHEL
|
|
|
1087
1172
|
* context build`, each recorded as a successful setup execution. An artifact
|
|
1088
1173
|
* alone or merely naming a command in the final message is not evidence.
|
|
1089
1174
|
*/
|
|
1090
|
-
function contextLifecycleStage(command) {
|
|
1175
|
+
function contextLifecycleStage(entry, command, binding) {
|
|
1091
1176
|
// This evidence must describe the actual lifecycle invocation, rather than
|
|
1092
1177
|
// merely mention it. `setup_execution` already supplies the project cwd as
|
|
1093
1178
|
// a separate structured field; the only shell prefixes we accept here are
|
|
@@ -1096,7 +1181,12 @@ function contextLifecycleStage(command) {
|
|
|
1096
1181
|
// command cwd to the canonical project, so this is the same project-local
|
|
1097
1182
|
// lifecycle operation emitted by some Wren guidance. Arbitrary paths, other
|
|
1098
1183
|
// flags, shell sequencing, quoted text, and wrappers remain unrecognized.
|
|
1099
|
-
|
|
1184
|
+
// The one additional shape is a single `cd <project> &&` prefix, decided by
|
|
1185
|
+
// the shared `projectLocalCommand` rule (also used for schema discovery).
|
|
1186
|
+
const inner = projectLocalCommand(entry, command, binding);
|
|
1187
|
+
if (inner === undefined)
|
|
1188
|
+
return undefined;
|
|
1189
|
+
const match = DIRECT_CONTEXT_LIFECYCLE_COMMAND.exec(inner);
|
|
1100
1190
|
return match?.[1]?.toLowerCase();
|
|
1101
1191
|
}
|
|
1102
1192
|
function stageAfter(prefix) {
|
|
@@ -1111,40 +1201,55 @@ function prefixFor(stage) {
|
|
|
1111
1201
|
return "discovery";
|
|
1112
1202
|
return stage === "build" ? "validate" : "none";
|
|
1113
1203
|
}
|
|
1114
|
-
|
|
1204
|
+
const CONTEXT_LIFECYCLE_MENTION = /\bwren\s+context\s+(validate|build)\b/i;
|
|
1205
|
+
function scanRecordedContextLifecycle(worklog, prior, binding) {
|
|
1115
1206
|
let stage = stageAfter(prior);
|
|
1116
1207
|
let completed = prior === "build";
|
|
1117
1208
|
let outOfOrderObserved;
|
|
1209
|
+
// Successful commands that mention a lifecycle command but were not
|
|
1210
|
+
// recognised as one, so a failure can quote them instead of claiming the
|
|
1211
|
+
// command never ran.
|
|
1212
|
+
const unrecognisedMentions = [];
|
|
1118
1213
|
for (const entry of worklog) {
|
|
1119
1214
|
if (!isSetupExecutionEntry(entry) || execSucceeded(entry) !== true)
|
|
1120
1215
|
continue;
|
|
1121
1216
|
const command = execCommandOf(entry);
|
|
1122
1217
|
if (command === undefined)
|
|
1123
1218
|
continue;
|
|
1219
|
+
const noteUnrecognised = (candidate, text) => {
|
|
1220
|
+
if (contextLifecycleStage(candidate, text, binding) !== undefined)
|
|
1221
|
+
return;
|
|
1222
|
+
const mention = CONTEXT_LIFECYCLE_MENTION.exec(text);
|
|
1223
|
+
if (mention)
|
|
1224
|
+
unrecognisedMentions.push({ command: text, stage: mention[1].toLowerCase() });
|
|
1225
|
+
};
|
|
1124
1226
|
if (stage === "discovery") {
|
|
1125
|
-
if (classifyRecordedSchemaDiscovery([entry]).kind === "successful") {
|
|
1227
|
+
if (classifyRecordedSchemaDiscovery([entry], binding).kind === "successful") {
|
|
1126
1228
|
stage = "validate";
|
|
1127
1229
|
continue;
|
|
1128
1230
|
}
|
|
1129
|
-
if (contextLifecycleStage(command) !== undefined) {
|
|
1130
|
-
outOfOrderObserved ??= `successful
|
|
1231
|
+
if (contextLifecycleStage(entry, command, binding) !== undefined) {
|
|
1232
|
+
outOfOrderObserved ??= `successful ${quoteCommand(command)} ran before successful schema discovery`;
|
|
1131
1233
|
}
|
|
1234
|
+
noteUnrecognised(entry, command);
|
|
1132
1235
|
continue;
|
|
1133
1236
|
}
|
|
1134
|
-
const nativeStage = contextLifecycleStage(command);
|
|
1237
|
+
const nativeStage = contextLifecycleStage(entry, command, binding);
|
|
1238
|
+
if (nativeStage === undefined)
|
|
1239
|
+
noteUnrecognised(entry, command);
|
|
1135
1240
|
if (stage === "validate") {
|
|
1136
1241
|
if (nativeStage === "validate") {
|
|
1137
1242
|
stage = "build";
|
|
1138
1243
|
}
|
|
1139
1244
|
else if (nativeStage === "build") {
|
|
1140
|
-
outOfOrderObserved ??= `successful
|
|
1245
|
+
outOfOrderObserved ??= `successful ${quoteCommand(command)} ran before successful \`wren context validate\``;
|
|
1141
1246
|
}
|
|
1142
1247
|
continue;
|
|
1143
1248
|
}
|
|
1144
1249
|
if (nativeStage === "build")
|
|
1145
1250
|
completed = true;
|
|
1146
1251
|
}
|
|
1147
|
-
return { stage, completed, outOfOrderObserved };
|
|
1252
|
+
return { stage, completed, outOfOrderObserved, unrecognisedMentions };
|
|
1148
1253
|
}
|
|
1149
1254
|
/**
|
|
1150
1255
|
* Returns the greatest ordered prefix proved by the supplied successful tool
|
|
@@ -1152,26 +1257,122 @@ function scanRecordedContextLifecycle(worklog, prior) {
|
|
|
1152
1257
|
* call in the same attempt is out of order, so a corrective attempt can ask
|
|
1153
1258
|
* only for the unproven suffix.
|
|
1154
1259
|
*/
|
|
1155
|
-
export function recordedContextLifecyclePrefix(worklog, prior = "none") {
|
|
1156
|
-
const scan = scanRecordedContextLifecycle(worklog, prior);
|
|
1260
|
+
export function recordedContextLifecyclePrefix(worklog, prior = "none", binding) {
|
|
1261
|
+
const scan = scanRecordedContextLifecycle(worklog, prior, binding);
|
|
1157
1262
|
return scan.completed ? "build" : prefixFor(scan.stage);
|
|
1158
1263
|
}
|
|
1159
|
-
export function classifyRecordedContextLifecycle(worklog, prior = "none") {
|
|
1160
|
-
const { stage, completed, outOfOrderObserved } = scanRecordedContextLifecycle(worklog, prior);
|
|
1264
|
+
export function classifyRecordedContextLifecycle(worklog, prior = "none", binding) {
|
|
1265
|
+
const { stage, completed, outOfOrderObserved, unrecognisedMentions } = scanRecordedContextLifecycle(worklog, prior, binding);
|
|
1266
|
+
const wanted = stage === "validate" ? "validate" : "build";
|
|
1267
|
+
const unrecognised = (unrecognisedMentions.find((mention) => mention.stage === wanted) ?? unrecognisedMentions[0])?.command;
|
|
1161
1268
|
if (completed)
|
|
1162
1269
|
return { kind: "successful" };
|
|
1270
|
+
const withUnrecognised = unrecognised !== undefined ? { unrecognised } : {};
|
|
1163
1271
|
if (outOfOrderObserved !== undefined)
|
|
1164
|
-
return { kind: "out_of_order", observed: outOfOrderObserved };
|
|
1272
|
+
return { kind: "out_of_order", observed: outOfOrderObserved, ...withUnrecognised };
|
|
1165
1273
|
if (stage === "discovery")
|
|
1166
1274
|
return { kind: "missing_discovery" };
|
|
1167
|
-
if (stage === "validate")
|
|
1168
|
-
return { kind: "validate_missing", observed: "no successful `wren context validate` followed successful schema discovery" };
|
|
1169
|
-
|
|
1275
|
+
if (stage === "validate") {
|
|
1276
|
+
return { kind: "validate_missing", observed: "no recognised successful `wren context validate` followed successful schema discovery", ...withUnrecognised };
|
|
1277
|
+
}
|
|
1278
|
+
return { kind: "build_missing", observed: "no recognised successful `wren context build` followed successful `wren context validate`", ...withUnrecognised };
|
|
1170
1279
|
}
|
|
1171
1280
|
/** A narrow terminal-message check: only override a context error that explicitly claims a zero-table/model outcome. */
|
|
1172
1281
|
function claimsNoTablesOrModels(message) {
|
|
1173
1282
|
return /\b(?:no|zero)\s+(?:tables?|models?)\b|\b0\s+(?:tables?|models?)\b/i.test(message);
|
|
1174
1283
|
}
|
|
1284
|
+
const ENV_ASSIGNMENT_VALUE = /(^|[\s;&|(])([A-Za-z_][A-Za-z0-9_]*)=("[^"]*"|'[^']*'|[^\s;&|]*)/g;
|
|
1285
|
+
/** Masks the value of every `NAME=value` assignment (leading, after `env`, or after `&&`/`;`). */
|
|
1286
|
+
function redactEnvAssignments(command) {
|
|
1287
|
+
return command.replace(ENV_ASSIGNMENT_VALUE, "$1$2=***");
|
|
1288
|
+
}
|
|
1289
|
+
/** Quotes a command for an error message: env values redacted first, then bounded so a long script cannot flood it. */
|
|
1290
|
+
function quoteCommand(command) {
|
|
1291
|
+
const flat = redactEnvAssignments(command).replace(/\s+/g, " ").trim();
|
|
1292
|
+
return `\`${flat.length > 300 ? `${flat.slice(0, 300)}...` : flat}\``;
|
|
1293
|
+
}
|
|
1294
|
+
const UNRECOGNISED_LIFECYCLE_HINT = "It exited successfully but is not a recognised lifecycle invocation: only the bare command (optionally with environment assignments or `--path .`) or a single `cd <project directory> &&` prefix naming this step's project directory is accepted. Run it again as the bare command and set the `setup_execution` `cwd` field to the project directory instead of shell chaining.";
|
|
1295
|
+
function projectBindingOf(context) {
|
|
1296
|
+
return { root: context.root, projectDir: path.join(context.root, context.name) };
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* The host-held evidence gate for a successful `context` step: the recorded
|
|
1300
|
+
* discovery -> validate -> build lifecycle, then the built `target/mdl.json`.
|
|
1301
|
+
* Returns the failure, or `undefined` when the evidence supports success. It
|
|
1302
|
+
* never looks at the agent's own status line.
|
|
1303
|
+
*/
|
|
1304
|
+
function verifyContextEvidence(context) {
|
|
1305
|
+
if (context.worklog !== undefined) {
|
|
1306
|
+
const lifecycle = classifyRecordedContextLifecycle(context.worklog, context.priorContextLifecycle, projectBindingOf(context));
|
|
1307
|
+
if (lifecycle.kind === "missing_discovery") {
|
|
1308
|
+
return hostContractFailure("context_schema_discovery_missing", "The agent never completed recognized schema discovery in its recorded worklog, so its model/build result cannot be accepted. This is an agent-workflow failure, not evidence that the connection or data source lacks tables.", "the recorded worklog has no successful recognized schema-discovery command", [
|
|
1309
|
+
"run a recognized schema-discovery command against the connected source and make sure it exits successfully",
|
|
1310
|
+
"build target/mdl.json only from that discovered schema, with at least one model",
|
|
1311
|
+
], "no_successful_schema_discovery");
|
|
1312
|
+
}
|
|
1313
|
+
const discovery = classifyRecordedSchemaDiscovery(context.worklog, projectBindingOf(context));
|
|
1314
|
+
if (discovery.kind === "failed") {
|
|
1315
|
+
const exitCodeSuffix = discovery.exitCode !== undefined ? ` with exit code ${discovery.exitCode}` : "";
|
|
1316
|
+
return hostContractFailure("context_schema_discovery_failed", `${quoteCommand(discovery.command)} failed${exitCodeSuffix} during schema discovery — a model/build result cannot be accepted until that command/tool failure is resolved; this is not evidence that the connection or data source lacks tables.`, discovery.exitCode !== undefined
|
|
1317
|
+
? `the recorded schema-discovery command exited with code ${discovery.exitCode}`
|
|
1318
|
+
: "the recorded schema-discovery command failed", [
|
|
1319
|
+
"resolve the schema-discovery command failure and run a recognized discovery command successfully",
|
|
1320
|
+
"build target/mdl.json only from that discovered schema, with at least one model",
|
|
1321
|
+
]);
|
|
1322
|
+
}
|
|
1323
|
+
if (lifecycle.kind === "validate_missing") {
|
|
1324
|
+
return hostContractFailure("context_validate_missing", lifecycle.unrecognised !== undefined
|
|
1325
|
+
? `The agent ran ${quoteCommand(lifecycle.unrecognised)} successfully, but it was not recognised as \`wren context validate\` after schema discovery. ${UNRECOGNISED_LIFECYCLE_HINT}`
|
|
1326
|
+
: "The agent did not successfully run `wren context validate` after recognized schema discovery, so its model/build result cannot be accepted.", lifecycle.unrecognised !== undefined
|
|
1327
|
+
? `${lifecycle.observed}; unrecognised successful command: ${quoteCommand(lifecycle.unrecognised)}`
|
|
1328
|
+
: lifecycle.observed, [
|
|
1329
|
+
"run a recognized schema-discovery command successfully",
|
|
1330
|
+
"run `wren context validate` successfully before `wren context build`",
|
|
1331
|
+
"build target/mdl.json only after validation, with at least one model",
|
|
1332
|
+
...(lifecycle.unrecognised !== undefined
|
|
1333
|
+
? ["pass the project directory through the `setup_execution` `cwd` field rather than `cd`/shell chaining"]
|
|
1334
|
+
: []),
|
|
1335
|
+
]);
|
|
1336
|
+
}
|
|
1337
|
+
if (lifecycle.kind === "build_missing") {
|
|
1338
|
+
return hostContractFailure("context_build_missing", lifecycle.unrecognised !== undefined
|
|
1339
|
+
? `The agent ran ${quoteCommand(lifecycle.unrecognised)} successfully, but it was not recognised as \`wren context build\` after \`wren context validate\`. ${UNRECOGNISED_LIFECYCLE_HINT}`
|
|
1340
|
+
: "The agent did not successfully run `wren context build` after `wren context validate`, so its claimed context success cannot be accepted.", lifecycle.unrecognised !== undefined
|
|
1341
|
+
? `${lifecycle.observed}; unrecognised successful command: ${quoteCommand(lifecycle.unrecognised)}`
|
|
1342
|
+
: lifecycle.observed, [
|
|
1343
|
+
"run a recognized schema-discovery command successfully",
|
|
1344
|
+
"run `wren context validate` successfully",
|
|
1345
|
+
"run `wren context build` successfully after validation and produce target/mdl.json with at least one model",
|
|
1346
|
+
...(lifecycle.unrecognised !== undefined
|
|
1347
|
+
? ["pass the project directory through the `setup_execution` `cwd` field rather than `cd`/shell chaining"]
|
|
1348
|
+
: []),
|
|
1349
|
+
]);
|
|
1350
|
+
}
|
|
1351
|
+
if (lifecycle.kind === "out_of_order") {
|
|
1352
|
+
return hostContractFailure("context_lifecycle_out_of_order", lifecycle.unrecognised !== undefined
|
|
1353
|
+
? `The native context lifecycle ran out of order; Setup requires successful schema discovery followed by \`wren context validate\` and then \`wren context build\`. The agent also ran ${quoteCommand(lifecycle.unrecognised)} successfully, but it was not recognised. ${UNRECOGNISED_LIFECYCLE_HINT}`
|
|
1354
|
+
: "The native context lifecycle ran out of order; Setup requires successful schema discovery followed by `wren context validate` and then `wren context build`.", lifecycle.unrecognised !== undefined
|
|
1355
|
+
? `${lifecycle.observed}; unrecognised successful command: ${quoteCommand(lifecycle.unrecognised)}`
|
|
1356
|
+
: lifecycle.observed, [
|
|
1357
|
+
"run a recognized schema-discovery command successfully",
|
|
1358
|
+
"run `wren context validate` successfully before `wren context build`",
|
|
1359
|
+
"produce target/mdl.json with at least one schema-derived model",
|
|
1360
|
+
...(lifecycle.unrecognised !== undefined
|
|
1361
|
+
? ["pass the project directory through the `setup_execution` `cwd` field rather than `cd`/shell chaining"]
|
|
1362
|
+
: []),
|
|
1363
|
+
]);
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
const mdlPath = path.join(context.root, context.name, "target", "mdl.json");
|
|
1367
|
+
if (!existsSync(mdlPath)) {
|
|
1368
|
+
return hostContractFailure("context_mdl_missing", `the setup agent reported "ok" but ${mdlPath} does not exist — treating this as a failed context build`, "target/mdl.json is missing after the claimed context build", ["target/mdl.json must exist", "target/mdl.json must contain at least one model"]);
|
|
1369
|
+
}
|
|
1370
|
+
const modelCount = countMdlModels(mdlPath);
|
|
1371
|
+
if (modelCount < 1) {
|
|
1372
|
+
return hostContractFailure("context_mdl_empty", `the setup agent reported "ok" but ${mdlPath} has ${modelCount} models — treating this as a failed context build`, `target/mdl.json has ${modelCount} models after the claimed context build`, ["target/mdl.json must contain at least one model"]);
|
|
1373
|
+
}
|
|
1374
|
+
return undefined;
|
|
1375
|
+
}
|
|
1175
1376
|
/**
|
|
1176
1377
|
* Parses the LAST `SETUP_STATUS: ok|needs_input|error` line out of a setup
|
|
1177
1378
|
* turn's finalText (the terminal contract lives in the composed
|
|
@@ -1214,6 +1415,12 @@ export function parseSetupTerminal(finalText, context) {
|
|
|
1214
1415
|
}
|
|
1215
1416
|
}
|
|
1216
1417
|
if (!matched) {
|
|
1418
|
+
// For the context step the status line is advisory: recorded execution
|
|
1419
|
+
// evidence plus the built project decide success. Anything short of that
|
|
1420
|
+
// keeps the missing-status failure (and its bounded correction turn).
|
|
1421
|
+
if (context.stepKey === "context" && context.worklog !== undefined && verifyContextEvidence(context) === undefined) {
|
|
1422
|
+
return { status: "ok", message: defaultMessageFor("ok") };
|
|
1423
|
+
}
|
|
1217
1424
|
return {
|
|
1218
1425
|
status: "error",
|
|
1219
1426
|
message: "the setup agent's final message did not contain a SETUP_STATUS line",
|
|
@@ -1222,54 +1429,9 @@ export function parseSetupTerminal(finalText, context) {
|
|
|
1222
1429
|
}
|
|
1223
1430
|
if (matched.status === "ok") {
|
|
1224
1431
|
if (context.stepKey === "context") {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
return hostContractFailure("context_schema_discovery_missing", "The agent never completed recognized schema discovery in its recorded worklog, so its model/build result cannot be accepted. This is an agent-workflow failure, not evidence that the connection or data source lacks tables.", "the recorded worklog has no successful recognized schema-discovery command", [
|
|
1229
|
-
"run a recognized schema-discovery command against the connected source and make sure it exits successfully",
|
|
1230
|
-
"build target/mdl.json only from that discovered schema, with at least one model",
|
|
1231
|
-
], "no_successful_schema_discovery");
|
|
1232
|
-
}
|
|
1233
|
-
const discovery = classifyRecordedSchemaDiscovery(context.worklog);
|
|
1234
|
-
if (discovery.kind === "failed") {
|
|
1235
|
-
const exitCodeSuffix = discovery.exitCode !== undefined ? ` with exit code ${discovery.exitCode}` : "";
|
|
1236
|
-
return hostContractFailure("context_schema_discovery_failed", `\`${discovery.command}\` failed${exitCodeSuffix} during schema discovery — a model/build result cannot be accepted until that command/tool failure is resolved; this is not evidence that the connection or data source lacks tables.`, discovery.exitCode !== undefined
|
|
1237
|
-
? `the recorded schema-discovery command exited with code ${discovery.exitCode}`
|
|
1238
|
-
: "the recorded schema-discovery command failed", [
|
|
1239
|
-
"resolve the schema-discovery command failure and run a recognized discovery command successfully",
|
|
1240
|
-
"build target/mdl.json only from that discovered schema, with at least one model",
|
|
1241
|
-
]);
|
|
1242
|
-
}
|
|
1243
|
-
if (lifecycle.kind === "validate_missing") {
|
|
1244
|
-
return hostContractFailure("context_validate_missing", "The agent did not successfully run `wren context validate` after recognized schema discovery, so its model/build result cannot be accepted.", lifecycle.observed, [
|
|
1245
|
-
"run a recognized schema-discovery command successfully",
|
|
1246
|
-
"run `wren context validate` successfully before `wren context build`",
|
|
1247
|
-
"build target/mdl.json only after validation, with at least one model",
|
|
1248
|
-
]);
|
|
1249
|
-
}
|
|
1250
|
-
if (lifecycle.kind === "build_missing") {
|
|
1251
|
-
return hostContractFailure("context_build_missing", "The agent did not successfully run `wren context build` after `wren context validate`, so its claimed context success cannot be accepted.", lifecycle.observed, [
|
|
1252
|
-
"run a recognized schema-discovery command successfully",
|
|
1253
|
-
"run `wren context validate` successfully",
|
|
1254
|
-
"run `wren context build` successfully after validation and produce target/mdl.json with at least one model",
|
|
1255
|
-
]);
|
|
1256
|
-
}
|
|
1257
|
-
if (lifecycle.kind === "out_of_order") {
|
|
1258
|
-
return hostContractFailure("context_lifecycle_out_of_order", "The native context lifecycle ran out of order; Setup requires successful schema discovery followed by `wren context validate` and then `wren context build`.", lifecycle.observed, [
|
|
1259
|
-
"run a recognized schema-discovery command successfully",
|
|
1260
|
-
"run `wren context validate` successfully before `wren context build`",
|
|
1261
|
-
"produce target/mdl.json with at least one schema-derived model",
|
|
1262
|
-
]);
|
|
1263
|
-
}
|
|
1264
|
-
}
|
|
1265
|
-
const mdlPath = path.join(context.root, context.name, "target", "mdl.json");
|
|
1266
|
-
if (!existsSync(mdlPath)) {
|
|
1267
|
-
return hostContractFailure("context_mdl_missing", `the setup agent reported "ok" but ${mdlPath} does not exist — treating this as a failed context build`, "target/mdl.json is missing after the claimed context build", ["target/mdl.json must exist", "target/mdl.json must contain at least one model"]);
|
|
1268
|
-
}
|
|
1269
|
-
const modelCount = countMdlModels(mdlPath);
|
|
1270
|
-
if (modelCount < 1) {
|
|
1271
|
-
return hostContractFailure("context_mdl_empty", `the setup agent reported "ok" but ${mdlPath} has ${modelCount} models — treating this as a failed context build`, `target/mdl.json has ${modelCount} models after the claimed context build`, ["target/mdl.json must contain at least one model"]);
|
|
1272
|
-
}
|
|
1432
|
+
const failure = verifyContextEvidence(context);
|
|
1433
|
+
if (failure !== undefined)
|
|
1434
|
+
return failure;
|
|
1273
1435
|
}
|
|
1274
1436
|
else {
|
|
1275
1437
|
const markerName = context.stepKey === "connect_resume" ? ".wren-validated" : "wren_project.yml";
|
|
@@ -1316,14 +1478,14 @@ export function parseSetupTerminal(finalText, context) {
|
|
|
1316
1478
|
// `setup_execution` call that exited non-zero, and reframe if one is found.
|
|
1317
1479
|
const failedExec = firstFailedExec(context.worklog);
|
|
1318
1480
|
if (failedExec !== undefined) {
|
|
1319
|
-
const command = failedExec.command !== undefined ?
|
|
1481
|
+
const command = failedExec.command !== undefined ? quoteCommand(failedExec.command) : "a command";
|
|
1320
1482
|
const exitCodeSuffix = failedExec.exitCode !== undefined ? ` with exit code ${failedExec.exitCode}` : "";
|
|
1321
1483
|
return {
|
|
1322
1484
|
status: "error",
|
|
1323
1485
|
message: `${command} failed${exitCodeSuffix} during this step — a command in this step's own history failed, so this step's error framing can't be trusted until that's ruled out; this is a command/tool failure, not by itself evidence that the connection or data source lacks tables. The agent's own report was: "${matched.message}"`,
|
|
1324
1486
|
};
|
|
1325
1487
|
}
|
|
1326
|
-
if (claimsNoTablesOrModels(matched.message) && classifyRecordedSchemaDiscovery(context.worklog).kind === "none") {
|
|
1488
|
+
if (claimsNoTablesOrModels(matched.message) && classifyRecordedSchemaDiscovery(context.worklog, projectBindingOf(context)).kind === "none") {
|
|
1327
1489
|
return {
|
|
1328
1490
|
status: "error",
|
|
1329
1491
|
failureKind: "no_successful_schema_discovery",
|
|
@@ -17,6 +17,13 @@ export interface SetupExecutionToolOptions {
|
|
|
17
17
|
* never broaden either its cwd or its write scope beyond it.
|
|
18
18
|
*/
|
|
19
19
|
readonly workspaceRoot: string;
|
|
20
|
+
/**
|
|
21
|
+
* The bound project directory of a project-bound setup step. When set and the
|
|
22
|
+
* directory exists, an `exec` without an explicit `cwd` runs there instead of
|
|
23
|
+
* at `workspaceRoot`; an explicit `cwd` still wins. Must lie within
|
|
24
|
+
* `workspaceRoot` (same containment check as an explicit `cwd`).
|
|
25
|
+
*/
|
|
26
|
+
readonly defaultExecCwd?: string;
|
|
20
27
|
}
|
|
21
28
|
/**
|
|
22
29
|
* A single flat top-level object, NOT a `z.discriminatedUnion` — a
|
|
@@ -109,7 +109,7 @@ export const setupExecutionInputSchema = z.object({
|
|
|
109
109
|
cwd: z
|
|
110
110
|
.string()
|
|
111
111
|
.optional()
|
|
112
|
-
.describe('Optional for both actions: use this directory instead of the workspace root as the command cwd or the base for a relative write path (e.g. the project directory). Absolute or relative to the workspace root, and must resolve within it. Defaults to the workspace root when omitted. For exec, use this instead of "cd <dir> && ..." chaining.'),
|
|
112
|
+
.describe('Optional for both actions: use this directory instead of the workspace root as the command cwd or the base for a relative write path (e.g. the project directory). Absolute or relative to the workspace root, and must resolve within it. Defaults to the workspace root when omitted (for exec, the bound project directory once the step has one). For exec, use this instead of "cd <dir> && ..." chaining.'),
|
|
113
113
|
path: z
|
|
114
114
|
.string()
|
|
115
115
|
.optional()
|
|
@@ -291,8 +291,11 @@ function resolveWritePath(workspaceRoot, requestedPath, requestedCwd) {
|
|
|
291
291
|
* exec cwd to any directory within `workspaceRoot` (e.g. the project
|
|
292
292
|
* directory), but never broaden it beyond that root.
|
|
293
293
|
*/
|
|
294
|
-
function resolveExecCwd(workspaceRoot, requestedCwd) {
|
|
294
|
+
function resolveExecCwd(workspaceRoot, requestedCwd, defaultCwd) {
|
|
295
295
|
if (requestedCwd === undefined) {
|
|
296
|
+
if (defaultCwd !== undefined && existsSync(defaultCwd)) {
|
|
297
|
+
return assertWithinScope(workspaceRoot, defaultCwd, (requested, scope) => new SetupExecCwdScopeError(requested, scope));
|
|
298
|
+
}
|
|
296
299
|
return workspaceRoot;
|
|
297
300
|
}
|
|
298
301
|
return assertWithinScope(workspaceRoot, requestedCwd, (requestedCwd, scope) => new SetupExecCwdScopeError(requestedCwd, scope));
|
|
@@ -319,7 +322,7 @@ export async function executeSetupExecution(input, options) {
|
|
|
319
322
|
throw new SetupExecutionInputError("exec", "command");
|
|
320
323
|
}
|
|
321
324
|
assertCommandAllowed(input.command);
|
|
322
|
-
const cwd = resolveExecCwd(workspaceRoot, input.cwd);
|
|
325
|
+
const cwd = resolveExecCwd(workspaceRoot, input.cwd, options.defaultExecCwd);
|
|
323
326
|
const result = await env.exec({ mode: "write", command: "/bin/sh", args: ["-c", input.command], cwd, timeoutMs: SETUP_EXEC_TIMEOUT_MS }, policy);
|
|
324
327
|
const { stdout, stderr } = redactSetupExecutionOutput(input.command, result);
|
|
325
328
|
return {
|
|
@@ -343,7 +346,7 @@ export function createSetupExecutionTool(options) {
|
|
|
343
346
|
return tool({
|
|
344
347
|
description: "Run a shell command or write a file within the setup workspace, subject to a destructive/redirection " +
|
|
345
348
|
'command denylist and a workspace-root scope. Two call shapes: {action: "exec", command, cwd?} runs a ' +
|
|
346
|
-
"shell command (command is required; cwd is optional, defaults to the workspace root, and must resolve " +
|
|
349
|
+
"shell command (command is required; cwd is optional, defaults to the bound project directory for a project step once it exists (otherwise the workspace root), and must resolve " +
|
|
347
350
|
'within it — path/content are unused); {action: "write", path, content, cwd?} writes a file (path and content ' +
|
|
348
351
|
"are both required; a relative path uses cwd when supplied, otherwise the workspace root; command is unused). A call missing the field(s) its action requires, or " +
|
|
349
352
|
"supplying a cwd/path that escapes the workspace root, is rejected before anything runs or is written.",
|
|
@@ -24,7 +24,7 @@ import { verifyProposal } from "./enrichment-verify.js";
|
|
|
24
24
|
import { canonicalizeProposal, hashEnrichmentOperation, NATIVE_ENRICHMENT_SUBMIT_INPUT_SCHEMA, NATIVE_ENRICHMENT_SUBMIT_MCP_TOOL_NAME, requiresApproval, resolveEnrichmentBinding, sameEnrichmentBinding, EnrichmentContractError } from "./enrichment.js";
|
|
25
25
|
import { INTERACTIVE_TARGETS, InteractiveLaunchError } from "./interactive-terminal.js";
|
|
26
26
|
import { nativeSessionLaunchErrorCode, nativeSessionLaunchFailure, nativeSessionLifecycle, NATIVE_PURPOSES, NATIVE_SETUP_RECOVERY_MCP_TOOL_NAME, NativeSetupRecoveryError } from "./native-sessions.js";
|
|
27
|
-
import { NATIVE_MCP_PERSIST_ANSWER_TOOL_NAME, NATIVE_MCP_TOOL_NAME, NATIVE_PERSIST_ANSWER_CONTRACT, NATIVE_SAVE_DASHBOARD_CONTRACT, NativeArtifactError } from "./native-artifacts.js";
|
|
27
|
+
import { NATIVE_MCP_PERSIST_ANSWER_TOOL_NAME, NATIVE_MCP_QUERY_TOOL_NAME, NATIVE_MCP_TOOL_NAME, NATIVE_PERSIST_ANSWER_CONTRACT, NATIVE_QUERY_INPUT_SCHEMA, NATIVE_SAVE_DASHBOARD_CONTRACT, NativeArtifactError } from "./native-artifacts.js";
|
|
28
28
|
import { sameNativeRuntimeBinding } from "./native-dispatch-registry.js";
|
|
29
29
|
import { ProfileRegistryError, toWarbleProfileDto } from "./profile-registry.js";
|
|
30
30
|
import { DEFAULT_CONVERSATION_PROFILE } from "./native-sessions.js";
|
|
@@ -1196,14 +1196,19 @@ export function createApp(deps) {
|
|
|
1196
1196
|
};
|
|
1197
1197
|
const artifactTool = {
|
|
1198
1198
|
name: NATIVE_MCP_TOOL_NAME,
|
|
1199
|
-
description: "Save a dashboard to Artifacts. Use a prior persist_answer reference, or select this session's latest retained answer when the opaque reference is no longer available, without recomputing. The host marks the artifact verified only when the referenced answer is one it grounded itself
|
|
1199
|
+
description: "Save a dashboard to Artifacts. Use a prior persist_answer reference, or select this session's latest retained answer when the opaque reference is no longer available, without recomputing. The host marks the artifact verified only when the referenced answer is one it grounded itself, or when a payload you write holds only table and definition blocks (plus an optional summary) and every definition cites a query this session ran through the query tool (by its query_id, else its SQL); its tables and definitions are then rebuilt from those query results and your summary is kept. A payload with any other block (narrative, chart, KPI) or an estimate flag is stored intact and unverified, as is any other payload. The existing payload form remains available for compatibility.",
|
|
1200
1200
|
inputSchema: NATIVE_SAVE_DASHBOARD_CONTRACT.inputSchema,
|
|
1201
1201
|
};
|
|
1202
1202
|
const persistAnswerTool = {
|
|
1203
1203
|
name: NATIVE_MCP_PERSIST_ANSWER_TOOL_NAME,
|
|
1204
|
-
description: "Persist this answer's exact typed table and optional definition result before conversational presentation. idempotency_key is retry authority only; the host returns canonical provenance. On success, later save_dashboard calls should use its opaque answer_ref, with the session-scoped latest selector available if that ref leaves the conversation; on failure, present the answer but report that reference saving is unavailable.",
|
|
1204
|
+
description: "Persist this answer's exact typed table and optional definition result before conversational presentation. idempotency_key is retry authority only; the host returns canonical provenance. On success, later save_dashboard calls should use its opaque answer_ref, with the session-scoped latest selector available if that ref leaves the conversation; on failure, present the answer but report that reference saving is unavailable. The host records the answer as verified only when its definition cites a query this session ran through the query tool (by its query_id, else its SQL); its table and definition are then rebuilt from that query's result. Any other answer is stored as written and unverified.",
|
|
1205
1205
|
inputSchema: NATIVE_PERSIST_ANSWER_CONTRACT.inputSchema,
|
|
1206
1206
|
};
|
|
1207
|
+
const queryTool = {
|
|
1208
|
+
name: NATIVE_MCP_QUERY_TOOL_NAME,
|
|
1209
|
+
description: "Run one read-only SQL query through the host and return its rows plus a query_id. Cite that query_id in the definition block of the answer you persist or save; only answers whose every definition cites a query this session ran here can be recorded as verified.",
|
|
1210
|
+
inputSchema: NATIVE_QUERY_INPUT_SCHEMA,
|
|
1211
|
+
};
|
|
1207
1212
|
const enrichmentSubmitTool = {
|
|
1208
1213
|
name: NATIVE_ENRICHMENT_SUBMIT_MCP_TOOL_NAME,
|
|
1209
1214
|
description: "Submit a drafted context enrichment proposal for host verification. The host re-checks the project binding, canonicalizes the proposal, and runs it through a shadow-copy verification ladder (grammar, sink, validate, build, dry-run) against a throwaway copy of the project -- the bound project itself is never written to. Only a verified proposal is recorded, into the same enrichment ledger a human reviews through accept/edit/skip. A refuted proposal is never recorded; the refuting step and reason are returned so the draft can be revised.",
|
|
@@ -1216,7 +1221,7 @@ export function createApp(deps) {
|
|
|
1216
1221
|
const nativeToolsForPurpose = (purpose) => {
|
|
1217
1222
|
switch (purpose) {
|
|
1218
1223
|
case "setup": return [setupTool];
|
|
1219
|
-
case "analysis": return [persistAnswerTool, artifactTool];
|
|
1224
|
+
case "analysis": return deps.nativeArtifacts?.queryAvailable() ? [queryTool, persistAnswerTool, artifactTool] : [persistAnswerTool, artifactTool];
|
|
1220
1225
|
case "context_enrichment": return [enrichmentSubmitTool];
|
|
1221
1226
|
default: {
|
|
1222
1227
|
const exhaustive = purpose;
|
|
@@ -1244,6 +1249,10 @@ export function createApp(deps) {
|
|
|
1244
1249
|
const saved = deps.nativeArtifacts.save(credential, request.params.arguments);
|
|
1245
1250
|
return c.json({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify(saved) }], structuredContent: saved } });
|
|
1246
1251
|
}
|
|
1252
|
+
if (nativeSession.purpose === "analysis" && request.params.name === NATIVE_MCP_QUERY_TOOL_NAME && deps.nativeArtifacts.queryAvailable()) {
|
|
1253
|
+
const queried = await deps.nativeArtifacts.query(credential, request.params.arguments);
|
|
1254
|
+
return c.json({ jsonrpc: "2.0", id, result: { isError: queried.isError, content: [{ type: "text", text: queried.text }], ...(queried.result !== undefined ? { structuredContent: queried.result } : {}) } });
|
|
1255
|
+
}
|
|
1247
1256
|
if (nativeSession.purpose === "analysis" && request.params.name === NATIVE_MCP_PERSIST_ANSWER_TOOL_NAME) {
|
|
1248
1257
|
const persisted = deps.nativeArtifacts.persistAnswer(credential, request.params.arguments);
|
|
1249
1258
|
return c.json({ jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify(persisted) }], structuredContent: persisted } });
|
|
@@ -122,6 +122,8 @@ import { resolveWarbleBinary } from "../harness/compile/resolve-binary.js";
|
|
|
122
122
|
import { toAuthChoiceFromRuntimeSettings } from "./auth-choice.js";
|
|
123
123
|
import { Store } from "./db.js";
|
|
124
124
|
import { resolveEnrichmentBinding, resolveProjectIdentity } from "./enrichment.js";
|
|
125
|
+
import { hashDirectory } from "../harness/compile/fingerprint.js";
|
|
126
|
+
import { openWrenComponentAccess } from "../harness/components/wren-access.js";
|
|
125
127
|
import { createDispatchedEnrichmentDraftRunner } from "./enrichment-runner.js";
|
|
126
128
|
import { dispatchInteractiveArtifacts, getInteractiveTerminalReadiness, InteractiveLaunchError, InteractiveTerminalManager, prepareInteractiveHandoff, unavailableInteractiveReadiness } from "./interactive-terminal.js";
|
|
127
129
|
import { createProbedPtyFactory, ensureDarwinNodePtySpawnHelper } from "./node-pty-host.js";
|
|
@@ -556,6 +558,12 @@ async function main() {
|
|
|
556
558
|
expectedMcpUrl: nativeMcpUrl,
|
|
557
559
|
mcpUrl: process.env["WREN_HARNESS_NATIVE_MCP_URL"] ?? nativeMcpUrl,
|
|
558
560
|
getBinding: currentInteractiveBinding,
|
|
561
|
+
// The session `query` tool runs through the same `wren` native sessions are given.
|
|
562
|
+
...(nativeWrenExecutable ? {
|
|
563
|
+
openQueryAccess: async (binding, signal) => openWrenComponentAccess({
|
|
564
|
+
executable: nativeWrenExecutable.executable, project: binding.path, fingerprint: await hashDirectory(binding.path), signal,
|
|
565
|
+
}),
|
|
566
|
+
} : {}),
|
|
559
567
|
});
|
|
560
568
|
const nativeTerminalHostAvailable = async () => {
|
|
561
569
|
try {
|
|
@@ -57,14 +57,15 @@ export declare function toAnswerOrRefusalEvent(id: string, result: RouteResult,
|
|
|
57
57
|
* The one place a subscription answer's `verified` is decided; a `verified`
|
|
58
58
|
* field in model-written text is never kept.
|
|
59
59
|
*
|
|
60
|
-
* - `codex-local`: the model's
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
60
|
+
* - `codex-local` and `agent-sdk`: the model's host query tool (codex:local's
|
|
61
|
+
* `run_sql`, the agent-sdk host MCP's `run_sql`) executes through this turn's
|
|
62
|
+
* host query service, so the answer is grounded against that turn's
|
|
63
|
+
* observations ({@link groundOnHostEvidence}): verified only when every
|
|
64
|
+
* query it cites is one the host ran this turn and that read a table, and
|
|
65
|
+
* then its tables and definitions are the observed ones. Otherwise it stays
|
|
66
|
+
* as written, badged "Unverified" — including any answer whose data came
|
|
67
|
+
* another way (agent-sdk's Bash `wren` stays available and yields no host
|
|
68
|
+
* evidence).
|
|
68
69
|
*
|
|
69
70
|
* Other backends pass through.
|
|
70
71
|
*/
|