@qloo/qloo-harness 0.1.18
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/LICENSE +21 -0
- package/README.md +394 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/dist/app.js +48 -0
- package/dist/bin.js +14 -0
- package/dist/build.js +117 -0
- package/dist/cli.js +95 -0
- package/dist/doctor.js +1019 -0
- package/dist/exec.js +782 -0
- package/dist/guided-journey.js +223 -0
- package/dist/index.d.ts +1321 -0
- package/dist/index.js +156 -0
- package/dist/integration-plan.js +1097 -0
- package/dist/mcp.js +909 -0
- package/dist/observability.js +115 -0
- package/dist/paths.js +77 -0
- package/dist/plan.js +163 -0
- package/dist/profiles.js +63 -0
- package/dist/project-context.js +364 -0
- package/dist/qloo-presentation.js +358 -0
- package/dist/qloo-tools.js +2195 -0
- package/dist/resolution-provider.js +1380 -0
- package/dist/router.js +6061 -0
- package/dist/runtime/explore-policy.js +666 -0
- package/dist/runtime/pi-adapter.js +259 -0
- package/dist/runtime/pi-command-policy.js +74 -0
- package/dist/runtime/qloo-header.js +173 -0
- package/dist/runtime/resources.js +37 -0
- package/dist/setup.js +1138 -0
- package/dist/update-manager.js +906 -0
- package/dist/workflow-executor.js +976 -0
- package/package.json +72 -0
- package/resources/BUILD.md +24 -0
- package/resources/EXPLORE.md +34 -0
- package/resources/INTEGRATE.md +28 -0
- package/resources/PLAN.md +23 -0
- package/resources/SYSTEM.md +49 -0
|
@@ -0,0 +1,1097 @@
|
|
|
1
|
+
import { createRequire as __qlooCreateRequire } from "node:module";
|
|
2
|
+
const require = __qlooCreateRequire(import.meta.url);
|
|
3
|
+
|
|
4
|
+
// apps/qloo-harness/dist/integration-plan.js
|
|
5
|
+
import { createHash as createHash2, randomUUID } from "node:crypto";
|
|
6
|
+
import { lstat, mkdir, readFile, realpath, rename, unlink, writeFile } from "node:fs/promises";
|
|
7
|
+
import { isAbsolute, join, posix, relative, resolve } from "node:path";
|
|
8
|
+
|
|
9
|
+
// packages/qloo-tool-schema/dist/upstream.lock.json
|
|
10
|
+
var upstream_lock_default = {
|
|
11
|
+
lockVersion: 1,
|
|
12
|
+
source: {
|
|
13
|
+
documentationUrl: "https://docs.qloo.com",
|
|
14
|
+
registryUrl: "https://dash.readme.com/api/v1/api-registry/4svkj1svmrti5tfx",
|
|
15
|
+
registryId: "4svkj1svmrti5tfx",
|
|
16
|
+
retrievedAt: "2026-08-20",
|
|
17
|
+
apiTitle: "Qloo API",
|
|
18
|
+
apiVersion: "2.0",
|
|
19
|
+
openapiVersion: "3.1.0",
|
|
20
|
+
sha256: "da8d4b830f2a646815d25afcaa653f81f1ad939707e2e2560d2d0b9f103076f1"
|
|
21
|
+
},
|
|
22
|
+
expectations: {
|
|
23
|
+
productionServer: "https://api.qloo.com",
|
|
24
|
+
authentication: {
|
|
25
|
+
scheme: "ApiKey",
|
|
26
|
+
type: "apiKey",
|
|
27
|
+
location: "header",
|
|
28
|
+
header: "X-Api-Key"
|
|
29
|
+
},
|
|
30
|
+
getPaths: [
|
|
31
|
+
"/",
|
|
32
|
+
"/entities",
|
|
33
|
+
"/search",
|
|
34
|
+
"/v2/analysis/compare",
|
|
35
|
+
"/v2/audiences",
|
|
36
|
+
"/v2/audiences/types",
|
|
37
|
+
"/v2/insights",
|
|
38
|
+
"/v2/tags",
|
|
39
|
+
"/v2/tags/types",
|
|
40
|
+
"/v2/trending"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
documentedExceptions: [
|
|
44
|
+
{
|
|
45
|
+
value: "urn:demographics",
|
|
46
|
+
scope: "/v2/insights filter.type",
|
|
47
|
+
status: "documented-but-not-enumerated",
|
|
48
|
+
documentationUrl: "https://docs.qloo.com/reference/demographics-use-case",
|
|
49
|
+
treatment: "Preserve as an explicit documentation exception; do not synthesize an endpoint payload schema from it."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
value: "urn:tag",
|
|
53
|
+
scope: "/v2/insights filter.type",
|
|
54
|
+
status: "documented-but-not-enumerated",
|
|
55
|
+
documentationUrl: "https://docs.qloo.com/reference/taste-analysis",
|
|
56
|
+
treatment: "The pinned specification mentions this value in diversify.by documentation but omits it from the filter.type enum; preserve the discrepancy for review."
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// packages/qloo-tool-schema/dist/src/catalog.js
|
|
62
|
+
var QLOO_TOOL_CATALOG_VERSION = "1.0.0";
|
|
63
|
+
var QLOO_TOOL_RESULT_SCHEMA_VERSION = "1.0-preview.1";
|
|
64
|
+
var QLOO_WORKFLOW_OPERATION_IDS = [
|
|
65
|
+
"recommend",
|
|
66
|
+
"rank",
|
|
67
|
+
"describe",
|
|
68
|
+
"where_popular",
|
|
69
|
+
"compare_audiences",
|
|
70
|
+
"entity_tags",
|
|
71
|
+
"audience_demographics",
|
|
72
|
+
"trends",
|
|
73
|
+
"find_tags"
|
|
74
|
+
];
|
|
75
|
+
var QLOO_CAPABILITIES_TOOL_NAME = "qloo_capabilities";
|
|
76
|
+
var QLOO_INSIGHTS_ENTITY_TYPES = [
|
|
77
|
+
{ id: "artist", urn: "urn:entity:artist" },
|
|
78
|
+
{ id: "book", urn: "urn:entity:book" },
|
|
79
|
+
{ id: "brand", urn: "urn:entity:brand" },
|
|
80
|
+
{ id: "movie", urn: "urn:entity:movie" },
|
|
81
|
+
{ id: "person", urn: "urn:entity:person" },
|
|
82
|
+
{ id: "place", urn: "urn:entity:place" },
|
|
83
|
+
{ id: "podcast", urn: "urn:entity:podcast" },
|
|
84
|
+
{ id: "tv_show", urn: "urn:entity:tv_show" },
|
|
85
|
+
{ id: "videogame", urn: "urn:entity:videogame" }
|
|
86
|
+
];
|
|
87
|
+
var QLOO_SEARCH_ENTITY_TYPES = [
|
|
88
|
+
...QLOO_INSIGHTS_ENTITY_TYPES,
|
|
89
|
+
{ id: "locality", urn: "urn:entity:locality" },
|
|
90
|
+
{ id: "actor", urn: "urn:entity:actor" },
|
|
91
|
+
{ id: "album", urn: "urn:entity:album" },
|
|
92
|
+
{ id: "author", urn: "urn:entity:author" },
|
|
93
|
+
{ id: "director", urn: "urn:entity:director" }
|
|
94
|
+
];
|
|
95
|
+
function buildEntityTypeAliases(entityTypes) {
|
|
96
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
97
|
+
const urnById = new Map(entityTypes.map(({ id, urn }) => [id, urn]));
|
|
98
|
+
for (const entityType of entityTypes) {
|
|
99
|
+
aliases.set(entityType.id, entityType.urn);
|
|
100
|
+
aliases.set(entityType.urn, entityType.urn);
|
|
101
|
+
aliases.set(`${entityType.id}s`, entityType.urn);
|
|
102
|
+
}
|
|
103
|
+
const addAlias = (alias, id) => {
|
|
104
|
+
const urn = urnById.get(id);
|
|
105
|
+
if (urn)
|
|
106
|
+
aliases.set(alias, urn);
|
|
107
|
+
};
|
|
108
|
+
addAlias("people", "person");
|
|
109
|
+
addAlias("localities", "locality");
|
|
110
|
+
addAlias("tv", "tv_show");
|
|
111
|
+
addAlias("show", "tv_show");
|
|
112
|
+
addAlias("shows", "tv_show");
|
|
113
|
+
addAlias("tv show", "tv_show");
|
|
114
|
+
addAlias("tv shows", "tv_show");
|
|
115
|
+
addAlias("game", "videogame");
|
|
116
|
+
addAlias("games", "videogame");
|
|
117
|
+
addAlias("video game", "videogame");
|
|
118
|
+
addAlias("video games", "videogame");
|
|
119
|
+
return aliases;
|
|
120
|
+
}
|
|
121
|
+
var insightsEntityTypeByAlias = buildEntityTypeAliases(QLOO_INSIGHTS_ENTITY_TYPES);
|
|
122
|
+
var searchEntityTypeByAlias = buildEntityTypeAliases(QLOO_SEARCH_ENTITY_TYPES);
|
|
123
|
+
var operationSummaries = {
|
|
124
|
+
recommend: "Find entities for a taste profile, audience, or collection of signals.",
|
|
125
|
+
rank: "Rank a supplied set of options for signals, location, or audience.",
|
|
126
|
+
describe: "Resolve and describe one Qloo entity.",
|
|
127
|
+
where_popular: "Find geographic areas where an entity has comparatively strong affinity.",
|
|
128
|
+
compare_audiences: "Contrast two signal groups and identify shared or differentiating affinities.",
|
|
129
|
+
entity_tags: "Retrieve concepts and tags that characterize entities.",
|
|
130
|
+
audience_demographics: "Retrieve aggregate audience segments associated with an entity.",
|
|
131
|
+
trends: "Retrieve interest or trend information over a time range.",
|
|
132
|
+
find_tags: "Search the Qloo tag ontology by natural-language concept."
|
|
133
|
+
};
|
|
134
|
+
var QLOO_WORKFLOW_OPERATIONS = Object.freeze(QLOO_WORKFLOW_OPERATION_IDS.map((id) => Object.freeze({
|
|
135
|
+
id,
|
|
136
|
+
toolName: `qloo_${id}`,
|
|
137
|
+
summary: operationSummaries[id],
|
|
138
|
+
since: QLOO_TOOL_CATALOG_VERSION
|
|
139
|
+
})));
|
|
140
|
+
var upstreamLock = upstream_lock_default;
|
|
141
|
+
var upstreamGetPaths = Object.freeze([
|
|
142
|
+
...upstreamLock.expectations.getPaths
|
|
143
|
+
]);
|
|
144
|
+
var documentedExceptions = Object.freeze(upstreamLock.documentedExceptions.map((exception) => Object.freeze({ ...exception })));
|
|
145
|
+
var QLOO_CAPABILITIES_TOOL = Object.freeze({
|
|
146
|
+
name: QLOO_CAPABILITIES_TOOL_NAME,
|
|
147
|
+
description: "Return the versioned Qloo workflow catalog and pinned upstream API contract metadata without making a network request.",
|
|
148
|
+
inputSchema: Object.freeze({
|
|
149
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
150
|
+
type: "object",
|
|
151
|
+
additionalProperties: false,
|
|
152
|
+
properties: Object.freeze({
|
|
153
|
+
operation_ids: Object.freeze({
|
|
154
|
+
type: "array",
|
|
155
|
+
description: "Optional canonical operation IDs to include. Omit for the complete catalog.",
|
|
156
|
+
uniqueItems: true,
|
|
157
|
+
items: Object.freeze({
|
|
158
|
+
type: "string",
|
|
159
|
+
enum: QLOO_WORKFLOW_OPERATION_IDS
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
})
|
|
164
|
+
});
|
|
165
|
+
var operationIds = new Set(QLOO_WORKFLOW_OPERATION_IDS);
|
|
166
|
+
function getQlooCapabilities(input = {}) {
|
|
167
|
+
const selected = input.operation_ids ? new Set(input.operation_ids) : void 0;
|
|
168
|
+
return {
|
|
169
|
+
contract_version: QLOO_TOOL_CATALOG_VERSION,
|
|
170
|
+
introspection_tool: QLOO_CAPABILITIES_TOOL_NAME,
|
|
171
|
+
operations: selected ? QLOO_WORKFLOW_OPERATIONS.filter(({ id }) => selected.has(id)) : QLOO_WORKFLOW_OPERATIONS,
|
|
172
|
+
upstream: {
|
|
173
|
+
documentation_url: upstreamLock.source.documentationUrl,
|
|
174
|
+
api_version: upstreamLock.source.apiVersion,
|
|
175
|
+
openapi_version: upstreamLock.source.openapiVersion,
|
|
176
|
+
production_server: upstreamLock.expectations.productionServer,
|
|
177
|
+
lock_sha256: upstreamLock.source.sha256,
|
|
178
|
+
get_paths: upstreamGetPaths,
|
|
179
|
+
documented_exceptions: documentedExceptions
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// packages/qloo-tool-schema/dist/src/workflow-contracts.js
|
|
185
|
+
import { createHash } from "node:crypto";
|
|
186
|
+
import { Type } from "typebox";
|
|
187
|
+
import { Value } from "typebox/value";
|
|
188
|
+
var QLOO_TOOL_DEFAULT_LIMIT = 10;
|
|
189
|
+
var QLOO_TOOL_MAX_RESULTS = 20;
|
|
190
|
+
var QLOO_TAG_BOOLEAN_OPERATORS = ["union", "intersection"];
|
|
191
|
+
var insightsEntityTypeIds = QLOO_INSIGHTS_ENTITY_TYPES.map(({ id }) => id);
|
|
192
|
+
var searchEntityTypeIds = QLOO_SEARCH_ENTITY_TYPES.map(({ id }) => id);
|
|
193
|
+
var QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA = Type.String({
|
|
194
|
+
enum: insightsEntityTypeIds,
|
|
195
|
+
description: `Qloo Insights entity type: ${insightsEntityTypeIds.join(", ")}.`
|
|
196
|
+
});
|
|
197
|
+
var QLOO_SEARCH_ENTITY_TYPE_SCHEMA = Type.String({
|
|
198
|
+
enum: searchEntityTypeIds,
|
|
199
|
+
description: `Qloo search entity type: ${searchEntityTypeIds.join(", ")}.`
|
|
200
|
+
});
|
|
201
|
+
var QLOO_TAG_BOOLEAN_OPERATOR_SCHEMA = Type.String({
|
|
202
|
+
enum: QLOO_TAG_BOOLEAN_OPERATORS,
|
|
203
|
+
description: "Combine tag values with union (any) or intersection (all)."
|
|
204
|
+
});
|
|
205
|
+
var QLOO_LIMIT_SCHEMA = Type.Optional(Type.Integer({
|
|
206
|
+
minimum: 1,
|
|
207
|
+
maximum: QLOO_TOOL_MAX_RESULTS,
|
|
208
|
+
default: QLOO_TOOL_DEFAULT_LIMIT,
|
|
209
|
+
description: `Maximum results to return (1-${QLOO_TOOL_MAX_RESULTS}).`
|
|
210
|
+
}));
|
|
211
|
+
var QLOO_ENTITY_INPUTS_SCHEMA = Type.Array(Type.String({ minLength: 1 }), {
|
|
212
|
+
minItems: 1,
|
|
213
|
+
maxItems: 10,
|
|
214
|
+
description: "Entity names or Qloo entity UUIDs. Keep one combined taste profile in one call."
|
|
215
|
+
});
|
|
216
|
+
var QLOO_TAG_INPUTS_SCHEMA = Type.Array(Type.String({ minLength: 1 }), {
|
|
217
|
+
minItems: 1,
|
|
218
|
+
maxItems: 10,
|
|
219
|
+
description: "Natural-language tag concepts or stable Qloo tag URNs."
|
|
220
|
+
});
|
|
221
|
+
var QLOO_DEMOGRAPHIC_SCHEMA = Type.String({
|
|
222
|
+
minLength: 1,
|
|
223
|
+
description: "Audience demographic in natural language, such as 'young women', 'men 25-34', or 'millennials'. It is converted to documented Qloo demographic signals.",
|
|
224
|
+
examples: ["young women", "men 25-34", "millennials"]
|
|
225
|
+
});
|
|
226
|
+
var QLOO_REQUEST_PREVIEW_SCHEMA = Type.Object({
|
|
227
|
+
method: Type.Literal("GET"),
|
|
228
|
+
path: Type.String({ minLength: 1 }),
|
|
229
|
+
query: Type.Unknown()
|
|
230
|
+
}, { additionalProperties: false });
|
|
231
|
+
var QLOO_RESULT_ENVELOPE_SCHEMA = Type.Object({
|
|
232
|
+
schema_version: Type.Literal(QLOO_TOOL_RESULT_SCHEMA_VERSION),
|
|
233
|
+
operation: Type.String(),
|
|
234
|
+
status: Type.Union([
|
|
235
|
+
Type.Literal("ok"),
|
|
236
|
+
Type.Literal("empty"),
|
|
237
|
+
Type.Literal("needs_input"),
|
|
238
|
+
Type.Literal("partial"),
|
|
239
|
+
Type.Literal("degraded"),
|
|
240
|
+
Type.Literal("error")
|
|
241
|
+
]),
|
|
242
|
+
summary: Type.Optional(Type.String()),
|
|
243
|
+
interpretation: Type.Optional(Type.Unknown()),
|
|
244
|
+
results: Type.Optional(Type.Unknown()),
|
|
245
|
+
result_count: Type.Optional(Type.Integer({ minimum: 0 })),
|
|
246
|
+
warnings: Type.Optional(Type.Array(Type.String())),
|
|
247
|
+
query_intent: Type.Optional(Type.Unknown()),
|
|
248
|
+
error: Type.Optional(Type.Object({
|
|
249
|
+
code: Type.String(),
|
|
250
|
+
layer: Type.String(),
|
|
251
|
+
retryable: Type.Boolean(),
|
|
252
|
+
recovery: Type.String()
|
|
253
|
+
}, { additionalProperties: false })),
|
|
254
|
+
execution: Type.Object({
|
|
255
|
+
transport: Type.String({ enum: ["direct", "cli", "mcp"] }),
|
|
256
|
+
transport_name: Type.String(),
|
|
257
|
+
correlation_id: Type.String(),
|
|
258
|
+
duration_ms: Type.Number({ minimum: 0 })
|
|
259
|
+
}, { additionalProperties: false }),
|
|
260
|
+
provenance: Type.Optional(Type.Object({
|
|
261
|
+
source: Type.String(),
|
|
262
|
+
endpoint: Type.String(),
|
|
263
|
+
documentation: Type.String(),
|
|
264
|
+
transport: Type.Optional(Type.String()),
|
|
265
|
+
requests: Type.Optional(Type.Array(QLOO_REQUEST_PREVIEW_SCHEMA, {
|
|
266
|
+
maxItems: 20
|
|
267
|
+
}))
|
|
268
|
+
}, { additionalProperties: false }))
|
|
269
|
+
}, { additionalProperties: true });
|
|
270
|
+
var QLOO_WORKFLOW_INPUT_SCHEMAS = {
|
|
271
|
+
recommend: Type.Object({
|
|
272
|
+
target_type: QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA,
|
|
273
|
+
signals: Type.Optional(QLOO_ENTITY_INPUTS_SCHEMA),
|
|
274
|
+
signal_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA),
|
|
275
|
+
signal_tags_operator: Type.Optional(QLOO_TAG_BOOLEAN_OPERATOR_SCHEMA),
|
|
276
|
+
signal_location: Type.Optional(Type.String({ minLength: 1 })),
|
|
277
|
+
demographic: Type.Optional(QLOO_DEMOGRAPHIC_SCHEMA),
|
|
278
|
+
filter_location: Type.Optional(Type.String({ minLength: 1 })),
|
|
279
|
+
include_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA),
|
|
280
|
+
include_tags_operator: Type.Optional(QLOO_TAG_BOOLEAN_OPERATOR_SCHEMA),
|
|
281
|
+
exclude_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA),
|
|
282
|
+
exclude_tags_operator: Type.Optional(QLOO_TAG_BOOLEAN_OPERATOR_SCHEMA),
|
|
283
|
+
explain: Type.Optional(Type.Boolean({ default: true })),
|
|
284
|
+
limit: QLOO_LIMIT_SCHEMA
|
|
285
|
+
}, { additionalProperties: false }),
|
|
286
|
+
rank: Type.Object({
|
|
287
|
+
options: QLOO_ENTITY_INPUTS_SCHEMA,
|
|
288
|
+
option_type: QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA,
|
|
289
|
+
signals: Type.Optional(QLOO_ENTITY_INPUTS_SCHEMA),
|
|
290
|
+
signal_location: Type.Optional(Type.String({ minLength: 1 })),
|
|
291
|
+
demographic: Type.Optional(QLOO_DEMOGRAPHIC_SCHEMA),
|
|
292
|
+
include_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA),
|
|
293
|
+
exclude_tags: Type.Optional(QLOO_TAG_INPUTS_SCHEMA)
|
|
294
|
+
}, { additionalProperties: false }),
|
|
295
|
+
describe: Type.Object({
|
|
296
|
+
entity: Type.String({ minLength: 1 }),
|
|
297
|
+
type: Type.Optional(QLOO_SEARCH_ENTITY_TYPE_SCHEMA)
|
|
298
|
+
}, { additionalProperties: false }),
|
|
299
|
+
where_popular: Type.Object({
|
|
300
|
+
entity: Type.String({ minLength: 1 }),
|
|
301
|
+
entity_type: Type.Optional(QLOO_SEARCH_ENTITY_TYPE_SCHEMA),
|
|
302
|
+
within: Type.String({
|
|
303
|
+
minLength: 1,
|
|
304
|
+
description: "Required geographic area that constrains the heatmap."
|
|
305
|
+
}),
|
|
306
|
+
limit: QLOO_LIMIT_SCHEMA
|
|
307
|
+
}, { additionalProperties: false }),
|
|
308
|
+
compare_audiences: Type.Object({
|
|
309
|
+
group_a: QLOO_ENTITY_INPUTS_SCHEMA,
|
|
310
|
+
group_b: QLOO_ENTITY_INPUTS_SCHEMA,
|
|
311
|
+
target_type: Type.Optional(QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA),
|
|
312
|
+
limit: QLOO_LIMIT_SCHEMA
|
|
313
|
+
}, { additionalProperties: false }),
|
|
314
|
+
entity_tags: Type.Object({
|
|
315
|
+
entities: QLOO_ENTITY_INPUTS_SCHEMA,
|
|
316
|
+
entity_type: Type.Optional(QLOO_SEARCH_ENTITY_TYPE_SCHEMA),
|
|
317
|
+
limit: QLOO_LIMIT_SCHEMA
|
|
318
|
+
}, { additionalProperties: false }),
|
|
319
|
+
audience_demographics: Type.Object({
|
|
320
|
+
entity: Type.String({ minLength: 1 }),
|
|
321
|
+
entity_type: Type.Optional(QLOO_SEARCH_ENTITY_TYPE_SCHEMA),
|
|
322
|
+
limit: QLOO_LIMIT_SCHEMA
|
|
323
|
+
}, { additionalProperties: false }),
|
|
324
|
+
trends: Type.Object({
|
|
325
|
+
entities: Type.Array(Type.String({ minLength: 1 }), { minItems: 1, maxItems: 5 }),
|
|
326
|
+
entity_type: QLOO_INSIGHTS_ENTITY_TYPE_SCHEMA,
|
|
327
|
+
start_date: Type.String({
|
|
328
|
+
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
|
|
329
|
+
description: "Inclusive start date in YYYY-MM-DD format."
|
|
330
|
+
}),
|
|
331
|
+
end_date: Type.String({
|
|
332
|
+
pattern: "^\\d{4}-\\d{2}-\\d{2}$",
|
|
333
|
+
description: "Inclusive end date in YYYY-MM-DD format."
|
|
334
|
+
}),
|
|
335
|
+
limit: QLOO_LIMIT_SCHEMA
|
|
336
|
+
}, { additionalProperties: false }),
|
|
337
|
+
find_tags: Type.Object({
|
|
338
|
+
query: Type.String({ minLength: 1 }),
|
|
339
|
+
semantic: Type.Optional(Type.Boolean({ default: true })),
|
|
340
|
+
limit: QLOO_LIMIT_SCHEMA
|
|
341
|
+
}, { additionalProperties: false })
|
|
342
|
+
};
|
|
343
|
+
var sharedWorkflowBehavior = Object.freeze({
|
|
344
|
+
readOnly: true,
|
|
345
|
+
idempotent: true,
|
|
346
|
+
mutating: false,
|
|
347
|
+
destructive: false,
|
|
348
|
+
openWorld: true,
|
|
349
|
+
concurrency: "safe_for_independent_inputs",
|
|
350
|
+
requiredCredential: "qloo_api",
|
|
351
|
+
defaultResultBudget: QLOO_TOOL_DEFAULT_LIMIT,
|
|
352
|
+
maximumResultBudget: QLOO_TOOL_MAX_RESULTS,
|
|
353
|
+
safeRetryClasses: Object.freeze(["dns", "connect", "timeout", "rate_limit", "upstream_5xx"]),
|
|
354
|
+
supportedTransports: Object.freeze(["direct", "mcp"])
|
|
355
|
+
});
|
|
356
|
+
var mediumLatencyOperations = /* @__PURE__ */ new Set([
|
|
357
|
+
"recommend",
|
|
358
|
+
"rank",
|
|
359
|
+
"compare_audiences",
|
|
360
|
+
"entity_tags",
|
|
361
|
+
"audience_demographics",
|
|
362
|
+
"trends"
|
|
363
|
+
]);
|
|
364
|
+
var QLOO_WORKFLOW_BEHAVIORS = Object.freeze(Object.fromEntries(Object.keys(QLOO_WORKFLOW_INPUT_SCHEMAS).map((operation2) => [
|
|
365
|
+
operation2,
|
|
366
|
+
Object.freeze({
|
|
367
|
+
...sharedWorkflowBehavior,
|
|
368
|
+
expectedLatency: mediumLatencyOperations.has(operation2) ? "medium" : "low"
|
|
369
|
+
})
|
|
370
|
+
])));
|
|
371
|
+
var QLOO_WORKFLOW_METADATA = {
|
|
372
|
+
recommend: {
|
|
373
|
+
toolName: "qloo_recommend",
|
|
374
|
+
label: "Qloo recommendations",
|
|
375
|
+
description: "Find Qloo entities related to one combined entity, tag, location, and demographic taste profile. Signals influence affinity; filters constrain output.",
|
|
376
|
+
promptSnippet: "Recommend entities from a combined Qloo taste profile",
|
|
377
|
+
promptGuidelines: [
|
|
378
|
+
"Pass every related taste and audience signal together in one qloo_recommend call.",
|
|
379
|
+
"Use signal_location for audience location and filter_location to constrain result geography.",
|
|
380
|
+
"Include demographic and location in the same call when both describe the audience."
|
|
381
|
+
],
|
|
382
|
+
documentation: ["https://docs.qloo.com/reference/insights-api-deep-dive"]
|
|
383
|
+
},
|
|
384
|
+
rank: {
|
|
385
|
+
toolName: "qloo_rank",
|
|
386
|
+
label: "Qloo rank",
|
|
387
|
+
description: "Rank one supplied option set against one shared entity, location, demographic, and tag profile. Scores from separate calls are not comparable.",
|
|
388
|
+
promptSnippet: "Rank a caller-supplied shortlist with Qloo",
|
|
389
|
+
promptGuidelines: ["Pass every option in one qloo_rank call so scores remain comparable."],
|
|
390
|
+
documentation: ["https://docs.qloo.com/reference/insights-api-deep-dive"]
|
|
391
|
+
},
|
|
392
|
+
describe: {
|
|
393
|
+
toolName: "qloo_describe",
|
|
394
|
+
label: "Qloo entity",
|
|
395
|
+
description: "Resolve one named entity or Qloo UUID and return only metadata present in Qloo.",
|
|
396
|
+
promptSnippet: "Resolve and describe one Qloo entity",
|
|
397
|
+
documentation: ["https://docs.qloo.com/reference/get-search", "https://docs.qloo.com/reference/get-entities"]
|
|
398
|
+
},
|
|
399
|
+
where_popular: {
|
|
400
|
+
toolName: "qloo_where_popular",
|
|
401
|
+
label: "Qloo geographic affinity",
|
|
402
|
+
description: "Find geographic areas where one entity has strong query-relative affinity. This is distinct from filtering recommendations to a place.",
|
|
403
|
+
promptSnippet: "Find where an entity is comparatively popular",
|
|
404
|
+
documentation: ["https://docs.qloo.com/reference/heatmaps-use-case"]
|
|
405
|
+
},
|
|
406
|
+
compare_audiences: {
|
|
407
|
+
toolName: "qloo_compare_audiences",
|
|
408
|
+
label: "Qloo audience comparison",
|
|
409
|
+
description: "Compare two complete groups of entity signals in one Qloo analysis request.",
|
|
410
|
+
promptSnippet: "Compare two Qloo taste-signal groups",
|
|
411
|
+
documentation: ["https://docs.qloo.com/reference/analysis-compare"]
|
|
412
|
+
},
|
|
413
|
+
entity_tags: {
|
|
414
|
+
toolName: "qloo_entity_tags",
|
|
415
|
+
label: "Qloo entity tags",
|
|
416
|
+
description: "Retrieve Qloo concepts that jointly characterize one or more entities using the documented urn:tag Insights workflow.",
|
|
417
|
+
promptSnippet: "Find concepts that characterize Qloo entities",
|
|
418
|
+
documentation: ["https://docs.qloo.com/reference/taste-analysis"]
|
|
419
|
+
},
|
|
420
|
+
audience_demographics: {
|
|
421
|
+
toolName: "qloo_audience_demographics",
|
|
422
|
+
label: "Qloo audience demographics",
|
|
423
|
+
description: "Retrieve aggregate demographic distributions associated with one entity through the documented urn:demographics Insights workflow.",
|
|
424
|
+
promptSnippet: "Inspect aggregate demographics associated with a Qloo entity",
|
|
425
|
+
documentation: ["https://docs.qloo.com/reference/demographics-use-case"]
|
|
426
|
+
},
|
|
427
|
+
trends: {
|
|
428
|
+
toolName: "qloo_trends",
|
|
429
|
+
label: "Qloo trends",
|
|
430
|
+
description: "Retrieve Qloo time-series popularity metrics for one to five resolved entities over an ISO date range.",
|
|
431
|
+
promptSnippet: "Track Qloo entity popularity metrics over time",
|
|
432
|
+
documentation: ["https://docs.qloo.com/reference/get-trending"]
|
|
433
|
+
},
|
|
434
|
+
find_tags: {
|
|
435
|
+
toolName: "qloo_find_tags",
|
|
436
|
+
label: "Qloo tag search",
|
|
437
|
+
description: "Search Qloo's tag ontology by natural-language concept and return stable tag URNs.",
|
|
438
|
+
promptSnippet: "Search the Qloo tag ontology",
|
|
439
|
+
documentation: ["https://docs.qloo.com/reference/get-tags-1"]
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
var QLOO_WORKFLOW_CONTRACTS = Object.freeze(Object.fromEntries(Object.entries(QLOO_WORKFLOW_METADATA).map(([id, metadata]) => [
|
|
443
|
+
id,
|
|
444
|
+
Object.freeze({
|
|
445
|
+
id,
|
|
446
|
+
...metadata,
|
|
447
|
+
behavior: QLOO_WORKFLOW_BEHAVIORS[id],
|
|
448
|
+
inputSchema: QLOO_WORKFLOW_INPUT_SCHEMAS[id],
|
|
449
|
+
resultSchema: QLOO_RESULT_ENVELOPE_SCHEMA
|
|
450
|
+
})
|
|
451
|
+
])));
|
|
452
|
+
var QLOO_ADAPTER_CONTRACT_MANIFEST_SCHEMA_VERSION = "1.1";
|
|
453
|
+
function getQlooExecutableCapabilities(input = {}) {
|
|
454
|
+
const capabilities = getQlooCapabilities(input);
|
|
455
|
+
return {
|
|
456
|
+
...capabilities,
|
|
457
|
+
result_schema_version: QLOO_TOOL_RESULT_SCHEMA_VERSION,
|
|
458
|
+
operations: capabilities.operations.map((operation2) => {
|
|
459
|
+
const contract = QLOO_WORKFLOW_CONTRACTS[operation2.id];
|
|
460
|
+
return {
|
|
461
|
+
...operation2,
|
|
462
|
+
input_schema: contract.inputSchema,
|
|
463
|
+
result_schema: contract.resultSchema,
|
|
464
|
+
documentation: contract.documentation,
|
|
465
|
+
behavior: contract.behavior
|
|
466
|
+
};
|
|
467
|
+
})
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
function getQlooAdapterContractManifest() {
|
|
471
|
+
const capabilities = getQlooExecutableCapabilities();
|
|
472
|
+
const contract = {
|
|
473
|
+
manifest_schema_version: QLOO_ADAPTER_CONTRACT_MANIFEST_SCHEMA_VERSION,
|
|
474
|
+
contract_version: capabilities.contract_version,
|
|
475
|
+
capabilities_tool: QLOO_CAPABILITIES_TOOL_NAME,
|
|
476
|
+
result_schema_version: capabilities.result_schema_version,
|
|
477
|
+
result_schema: QLOO_RESULT_ENVELOPE_SCHEMA,
|
|
478
|
+
operations: capabilities.operations.map((operation2) => {
|
|
479
|
+
const contract2 = QLOO_WORKFLOW_CONTRACTS[operation2.id];
|
|
480
|
+
return {
|
|
481
|
+
id: operation2.id,
|
|
482
|
+
shared_tool_name: operation2.toolName,
|
|
483
|
+
summary: operation2.summary,
|
|
484
|
+
description: contract2.description,
|
|
485
|
+
input_schema: contract2.inputSchema,
|
|
486
|
+
behavior: contract2.behavior,
|
|
487
|
+
documentation: contract2.documentation
|
|
488
|
+
};
|
|
489
|
+
}),
|
|
490
|
+
upstream: capabilities.upstream
|
|
491
|
+
};
|
|
492
|
+
return {
|
|
493
|
+
...contract,
|
|
494
|
+
contract_checksum: `sha256:${createHash("sha256").update(JSON.stringify(contract)).digest("hex")}`
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// packages/qloo-tool-schema/dist/src/resolution-contracts.js
|
|
499
|
+
import { Type as Type2 } from "typebox";
|
|
500
|
+
var QLOO_RESOLUTION_CONTRACT_VERSION = "1.0-preview.1";
|
|
501
|
+
var QLOO_RESOLUTION_PROVIDER_DESCRIPTOR_SCHEMA = Type2.Object({
|
|
502
|
+
contract_version: Type2.Literal(QLOO_RESOLUTION_CONTRACT_VERSION),
|
|
503
|
+
provider_id: Type2.String({ minLength: 1 }),
|
|
504
|
+
name: Type2.String({ minLength: 1 }),
|
|
505
|
+
entity_strategy: Type2.String({ minLength: 1 }),
|
|
506
|
+
tag_strategy: Type2.String({ minLength: 1 }),
|
|
507
|
+
remote: Type2.Boolean(),
|
|
508
|
+
uses_model: Type2.Boolean(),
|
|
509
|
+
fallback: Type2.Literal("none")
|
|
510
|
+
}, { additionalProperties: false });
|
|
511
|
+
var QLOO_RESOLUTION_CANDIDATE_SCHEMA = Type2.Object({
|
|
512
|
+
id: Type2.String({ minLength: 1 }),
|
|
513
|
+
name: Type2.String({ minLength: 1 }),
|
|
514
|
+
type: Type2.Optional(Type2.String({ minLength: 1 })),
|
|
515
|
+
rank: Type2.Optional(Type2.Integer({ minimum: 1 })),
|
|
516
|
+
score: Type2.Optional(Type2.Number()),
|
|
517
|
+
popularity: Type2.Optional(Type2.Number()),
|
|
518
|
+
parent_types: Type2.Optional(Type2.Array(Type2.String({ minLength: 1 }), {
|
|
519
|
+
maxItems: 20,
|
|
520
|
+
uniqueItems: true
|
|
521
|
+
})),
|
|
522
|
+
description: Type2.Optional(Type2.String({ minLength: 1, maxLength: 500 })),
|
|
523
|
+
release_year: Type2.Optional(Type2.Integer()),
|
|
524
|
+
address: Type2.Optional(Type2.Unknown())
|
|
525
|
+
}, { additionalProperties: false });
|
|
526
|
+
var QLOO_RESOLUTION_PROVENANCE_SCHEMA = Type2.Object({
|
|
527
|
+
contract_version: Type2.Literal(QLOO_RESOLUTION_CONTRACT_VERSION),
|
|
528
|
+
provider_id: Type2.String({ minLength: 1 }),
|
|
529
|
+
provider_name: Type2.String({ minLength: 1 }),
|
|
530
|
+
endpoint: Type2.String({ minLength: 1 }),
|
|
531
|
+
strategy: Type2.String({ minLength: 1 }),
|
|
532
|
+
remote: Type2.Boolean(),
|
|
533
|
+
uses_model: Type2.Boolean(),
|
|
534
|
+
provider_version: Type2.Optional(Type2.String({ minLength: 1 })),
|
|
535
|
+
cache_status: Type2.Optional(Type2.String({ minLength: 1 }))
|
|
536
|
+
}, { additionalProperties: false });
|
|
537
|
+
var QLOO_RESOLUTION_OUTCOME_COMMON_SCHEMA = {
|
|
538
|
+
input: Type2.String({ minLength: 1 }),
|
|
539
|
+
provenance: QLOO_RESOLUTION_PROVENANCE_SCHEMA,
|
|
540
|
+
warnings: Type2.Optional(Type2.Array(Type2.String()))
|
|
541
|
+
};
|
|
542
|
+
var QLOO_RESOLUTION_OUTCOME_SCHEMA = Type2.Union([
|
|
543
|
+
Type2.Object({
|
|
544
|
+
...QLOO_RESOLUTION_OUTCOME_COMMON_SCHEMA,
|
|
545
|
+
status: Type2.Literal("resolved"),
|
|
546
|
+
match: Type2.Union([
|
|
547
|
+
Type2.Literal("identifier"),
|
|
548
|
+
Type2.Literal("exact"),
|
|
549
|
+
Type2.Literal("semantic")
|
|
550
|
+
]),
|
|
551
|
+
selected: QLOO_RESOLUTION_CANDIDATE_SCHEMA,
|
|
552
|
+
alternatives: Type2.Optional(Type2.Array(QLOO_RESOLUTION_CANDIDATE_SCHEMA))
|
|
553
|
+
}, { additionalProperties: false }),
|
|
554
|
+
Type2.Object({
|
|
555
|
+
...QLOO_RESOLUTION_OUTCOME_COMMON_SCHEMA,
|
|
556
|
+
status: Type2.Literal("ambiguous"),
|
|
557
|
+
candidates: Type2.Array(QLOO_RESOLUTION_CANDIDATE_SCHEMA)
|
|
558
|
+
}, { additionalProperties: false }),
|
|
559
|
+
Type2.Object({
|
|
560
|
+
...QLOO_RESOLUTION_OUTCOME_COMMON_SCHEMA,
|
|
561
|
+
status: Type2.Literal("not_found"),
|
|
562
|
+
candidates: Type2.Optional(Type2.Array(QLOO_RESOLUTION_CANDIDATE_SCHEMA))
|
|
563
|
+
}, { additionalProperties: false })
|
|
564
|
+
]);
|
|
565
|
+
|
|
566
|
+
// apps/qloo-harness/dist/integration-plan.js
|
|
567
|
+
import { Type as Type3 } from "typebox";
|
|
568
|
+
import { Value as Value2 } from "typebox/value";
|
|
569
|
+
import { inspectProjectContext, QLOO_PROJECT_CONTEXT_SCHEMA_VERSION } from "./project-context.js";
|
|
570
|
+
var QLOO_INTEGRATION_PLAN_SCHEMA_VERSION = "1.0";
|
|
571
|
+
var QLOO_INTEGRATION_PLAN_MAX_BYTES = 64 * 1024;
|
|
572
|
+
var QLOO_INTEGRATION_PLAN_MAX_GOAL_BYTES = 8 * 1024;
|
|
573
|
+
var QLOO_INTEGRATION_PLAN_MAX_EVIDENCE_BYTES = 1024 * 1024;
|
|
574
|
+
var QLOO_INTEGRATION_PLAN_ID_PATTERN = /^qplan_[a-f0-9]{24}$/u;
|
|
575
|
+
var shortText = Type3.String({ minLength: 1, maxLength: 500 });
|
|
576
|
+
var longText = Type3.String({ minLength: 1, maxLength: 2e3 });
|
|
577
|
+
var relativePath = Type3.String({ minLength: 1, maxLength: 500 });
|
|
578
|
+
var operation = Type3.Union([
|
|
579
|
+
Type3.Literal("recommend"),
|
|
580
|
+
Type3.Literal("rank"),
|
|
581
|
+
Type3.Literal("describe"),
|
|
582
|
+
Type3.Literal("where_popular"),
|
|
583
|
+
Type3.Literal("compare_audiences"),
|
|
584
|
+
Type3.Literal("entity_tags"),
|
|
585
|
+
Type3.Literal("audience_demographics"),
|
|
586
|
+
Type3.Literal("trends"),
|
|
587
|
+
Type3.Literal("find_tags")
|
|
588
|
+
]);
|
|
589
|
+
var fieldMapping = Type3.Object({
|
|
590
|
+
local_field: shortText,
|
|
591
|
+
qloo_role: Type3.Union([
|
|
592
|
+
Type3.Literal("signal"),
|
|
593
|
+
Type3.Literal("filter"),
|
|
594
|
+
Type3.Literal("output")
|
|
595
|
+
]),
|
|
596
|
+
qloo_field: shortText,
|
|
597
|
+
normalized_result_field: Type3.Union([shortText, Type3.Null()]),
|
|
598
|
+
owning_boundary: shortText,
|
|
599
|
+
transformation: longText
|
|
600
|
+
}, { additionalProperties: false });
|
|
601
|
+
var boundaries = Type3.Object({
|
|
602
|
+
http_client: longText,
|
|
603
|
+
configuration: longText,
|
|
604
|
+
data_model: longText,
|
|
605
|
+
error_boundary: longText
|
|
606
|
+
}, { additionalProperties: false });
|
|
607
|
+
var errorHandling = Type3.Object({
|
|
608
|
+
condition: shortText,
|
|
609
|
+
behavior: longText
|
|
610
|
+
}, { additionalProperties: false });
|
|
611
|
+
var change = Type3.Object({
|
|
612
|
+
path: relativePath,
|
|
613
|
+
action: Type3.Union([Type3.Literal("create"), Type3.Literal("modify")]),
|
|
614
|
+
purpose: longText,
|
|
615
|
+
verification: longText
|
|
616
|
+
}, { additionalProperties: false });
|
|
617
|
+
var verification = Type3.Object({
|
|
618
|
+
command: Type3.String({ minLength: 1, maxLength: 1e3 }),
|
|
619
|
+
purpose: longText
|
|
620
|
+
}, { additionalProperties: false });
|
|
621
|
+
var draftEvidence = Type3.Object({
|
|
622
|
+
path: relativePath,
|
|
623
|
+
relevance: longText
|
|
624
|
+
}, { additionalProperties: false });
|
|
625
|
+
var hashedEvidence = Type3.Object({
|
|
626
|
+
path: relativePath,
|
|
627
|
+
relevance: longText,
|
|
628
|
+
sha256: Type3.String({ pattern: "^[a-f0-9]{64}$" })
|
|
629
|
+
}, { additionalProperties: false });
|
|
630
|
+
var QLOO_INTEGRATION_PLAN_DRAFT_SCHEMA = Type3.Object({
|
|
631
|
+
status: Type3.Union([Type3.Literal("ready"), Type3.Literal("needs_input")]),
|
|
632
|
+
summary: longText,
|
|
633
|
+
operation,
|
|
634
|
+
transport: Type3.Union([Type3.Literal("direct"), Type3.Literal("mcp")]),
|
|
635
|
+
field_mapping: Type3.Array(fieldMapping, { minItems: 1, maxItems: 32 }),
|
|
636
|
+
boundaries,
|
|
637
|
+
error_handling: Type3.Array(errorHandling, { minItems: 1, maxItems: 16 }),
|
|
638
|
+
privacy_constraints: Type3.Array(longText, { minItems: 1, maxItems: 16 }),
|
|
639
|
+
changes: Type3.Array(change, { minItems: 1, maxItems: 32 }),
|
|
640
|
+
verification: Type3.Array(verification, { minItems: 1, maxItems: 16 }),
|
|
641
|
+
rollout: Type3.Array(longText, { minItems: 1, maxItems: 16 }),
|
|
642
|
+
rollback: Type3.Array(longText, { minItems: 1, maxItems: 16 }),
|
|
643
|
+
assumptions: Type3.Array(longText, { maxItems: 24 }),
|
|
644
|
+
unresolved_decisions: Type3.Array(longText, { maxItems: 16 }),
|
|
645
|
+
evidence: Type3.Array(draftEvidence, { minItems: 1, maxItems: 32 })
|
|
646
|
+
}, { additionalProperties: false });
|
|
647
|
+
var projectIdentity = Type3.Object({
|
|
648
|
+
name: shortText,
|
|
649
|
+
fingerprint: Type3.String({ pattern: "^[a-f0-9]{64}$" }),
|
|
650
|
+
context_schema_version: Type3.Literal(QLOO_PROJECT_CONTEXT_SCHEMA_VERSION),
|
|
651
|
+
languages: Type3.Array(shortText, { maxItems: 16 }),
|
|
652
|
+
package_managers: Type3.Array(shortText, { maxItems: 16 }),
|
|
653
|
+
frameworks: Type3.Array(shortText, { maxItems: 32 }),
|
|
654
|
+
source_roots: Type3.Array(relativePath, { maxItems: 32 }),
|
|
655
|
+
http_clients: Type3.Array(shortText, { maxItems: 16 }),
|
|
656
|
+
data_modeling: Type3.Array(shortText, { maxItems: 16 }),
|
|
657
|
+
configuration: Type3.Array(shortText, { maxItems: 16 }),
|
|
658
|
+
test_frameworks: Type3.Array(shortText, { maxItems: 16 }),
|
|
659
|
+
verification_scripts: Type3.Array(shortText, { maxItems: 32 })
|
|
660
|
+
}, { additionalProperties: false });
|
|
661
|
+
var qlooContract = Type3.Object({
|
|
662
|
+
operation,
|
|
663
|
+
tool_name: Type3.String({ pattern: "^qloo_[a-z_]+$" }),
|
|
664
|
+
transport: Type3.Union([Type3.Literal("direct"), Type3.Literal("mcp")]),
|
|
665
|
+
manifest_schema_version: shortText,
|
|
666
|
+
contract_version: shortText,
|
|
667
|
+
contract_checksum: Type3.String({ pattern: "^sha256:[a-f0-9]{64}$" }),
|
|
668
|
+
result_schema_version: shortText
|
|
669
|
+
}, { additionalProperties: false });
|
|
670
|
+
var QLOO_INTEGRATION_PLAN_SCHEMA = Type3.Object({
|
|
671
|
+
schema_version: Type3.Literal(QLOO_INTEGRATION_PLAN_SCHEMA_VERSION),
|
|
672
|
+
plan_id: Type3.String({ pattern: "^qplan_[a-f0-9]{24}$" }),
|
|
673
|
+
generated_at: Type3.String({ minLength: 20, maxLength: 40 }),
|
|
674
|
+
goal: Type3.String({ minLength: 1, maxLength: QLOO_INTEGRATION_PLAN_MAX_GOAL_BYTES }),
|
|
675
|
+
status: Type3.Union([Type3.Literal("ready"), Type3.Literal("needs_input")]),
|
|
676
|
+
summary: longText,
|
|
677
|
+
project: projectIdentity,
|
|
678
|
+
qloo: qlooContract,
|
|
679
|
+
field_mapping: Type3.Array(fieldMapping, { minItems: 1, maxItems: 32 }),
|
|
680
|
+
boundaries,
|
|
681
|
+
error_handling: Type3.Array(errorHandling, { minItems: 1, maxItems: 16 }),
|
|
682
|
+
privacy_constraints: Type3.Array(longText, { minItems: 1, maxItems: 16 }),
|
|
683
|
+
changes: Type3.Array(change, { minItems: 1, maxItems: 32 }),
|
|
684
|
+
verification: Type3.Array(verification, { minItems: 1, maxItems: 16 }),
|
|
685
|
+
rollout: Type3.Array(longText, { minItems: 1, maxItems: 16 }),
|
|
686
|
+
rollback: Type3.Array(longText, { minItems: 1, maxItems: 16 }),
|
|
687
|
+
assumptions: Type3.Array(longText, { maxItems: 24 }),
|
|
688
|
+
unresolved_decisions: Type3.Array(longText, { maxItems: 16 }),
|
|
689
|
+
evidence: Type3.Array(hashedEvidence, { minItems: 1, maxItems: 32 })
|
|
690
|
+
}, { additionalProperties: false });
|
|
691
|
+
var QlooIntegrationPlanError = class extends Error {
|
|
692
|
+
code;
|
|
693
|
+
constructor(code, message) {
|
|
694
|
+
super(message);
|
|
695
|
+
this.code = code;
|
|
696
|
+
this.name = "QlooIntegrationPlanError";
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
function validationMessage(schema, value) {
|
|
700
|
+
return [...Value2.Errors(schema, value)].slice(0, 8).map((issue) => `${issue.instancePath || "/"}: ${issue.message}`).join("; ");
|
|
701
|
+
}
|
|
702
|
+
function parseDraft(value) {
|
|
703
|
+
if (!Value2.Check(QLOO_INTEGRATION_PLAN_DRAFT_SCHEMA, value)) {
|
|
704
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `submitted plan does not match schema: ${validationMessage(QLOO_INTEGRATION_PLAN_DRAFT_SCHEMA, value)}`);
|
|
705
|
+
}
|
|
706
|
+
return structuredClone(value);
|
|
707
|
+
}
|
|
708
|
+
function rejectUnsafeText(value, location = "/") {
|
|
709
|
+
if (typeof value === "string") {
|
|
710
|
+
if (/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/u.test(value)) {
|
|
711
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `${location} contains an unsafe control character`);
|
|
712
|
+
}
|
|
713
|
+
const assignment = value.match(/(?:api[_-]?key|password|secret|access[_-]?token|refresh[_-]?token)\s*[:=]\s*["']?([A-Za-z0-9+/_.=-]{12,})/iu);
|
|
714
|
+
const assignedValue = assignment?.[1];
|
|
715
|
+
const looksLikePlaceholder = assignedValue === void 0 || /^(?:configured|environment|placeholder|redacted|unknown)$/iu.test(assignedValue) || /^[A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)$/u.test(assignedValue);
|
|
716
|
+
if (/-----BEGIN [A-Z ]*PRIVATE KEY-----/iu.test(value) || /authorization\s*[:=]\s*bearer\s+[A-Za-z0-9+/_.=-]{8,}/iu.test(value) || !looksLikePlaceholder) {
|
|
717
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `${location} appears to contain a credential value`);
|
|
718
|
+
}
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (Array.isArray(value)) {
|
|
722
|
+
value.forEach((item, index) => rejectUnsafeText(item, `${location}${index}/`));
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (value !== null && typeof value === "object") {
|
|
726
|
+
for (const [key, item] of Object.entries(value)) {
|
|
727
|
+
rejectUnsafeText(item, `${location}${key}/`);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
function canonicalRelativePath(value) {
|
|
732
|
+
const candidate = value.trim();
|
|
733
|
+
const normalized = posix.normalize(candidate.replace(/^\.\//u, ""));
|
|
734
|
+
if (candidate.length === 0 || candidate.includes("\\") || /[\u0000-\u001f\u007f]/u.test(candidate) || isAbsolute(candidate) || /^[A-Za-z]:/u.test(candidate) || normalized === "." || normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/")) {
|
|
735
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `plan path must stay project-relative: ${JSON.stringify(value)}`);
|
|
736
|
+
}
|
|
737
|
+
const segments = normalized.split("/");
|
|
738
|
+
const fileName = segments.at(-1)?.toLocaleLowerCase("en-US") ?? "";
|
|
739
|
+
const safeEnvironmentTemplate = /^\.env\.(?:example|sample|template)$/u.test(fileName);
|
|
740
|
+
if (segments.some((segment) => [".git", ".qloo", ".ssh", "node_modules"].includes(segment.toLocaleLowerCase("en-US"))) || /^\.env(?:\..+)?$/u.test(fileName) && !safeEnvironmentTemplate || [".npmrc", ".pypirc", "id_rsa", "id_ed25519"].includes(fileName)) {
|
|
741
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `plan path targets protected or credential-bearing state: ${JSON.stringify(value)}`);
|
|
742
|
+
}
|
|
743
|
+
return normalized;
|
|
744
|
+
}
|
|
745
|
+
function canonicalizeDraft(draft) {
|
|
746
|
+
const changes = draft.changes.map((item) => ({
|
|
747
|
+
...item,
|
|
748
|
+
path: canonicalRelativePath(item.path)
|
|
749
|
+
}));
|
|
750
|
+
const evidence = draft.evidence.map((item) => ({
|
|
751
|
+
...item,
|
|
752
|
+
path: canonicalRelativePath(item.path)
|
|
753
|
+
}));
|
|
754
|
+
for (const [label, paths] of [
|
|
755
|
+
["change", changes.map(({ path }) => path)],
|
|
756
|
+
["evidence", evidence.map(({ path }) => path)]
|
|
757
|
+
]) {
|
|
758
|
+
if (new Set(paths).size !== paths.length) {
|
|
759
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `plan contains duplicate ${label} paths`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (draft.status === "ready" && draft.unresolved_decisions.length > 0) {
|
|
763
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "a ready plan cannot contain unresolved decisions; use status needs_input");
|
|
764
|
+
}
|
|
765
|
+
if (draft.status === "needs_input" && draft.unresolved_decisions.length === 0) {
|
|
766
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "a needs_input plan must identify at least one unresolved decision");
|
|
767
|
+
}
|
|
768
|
+
const evidencePaths = new Set(evidence.map(({ path }) => path));
|
|
769
|
+
for (const item of changes) {
|
|
770
|
+
if (item.action === "modify" && !evidencePaths.has(item.path)) {
|
|
771
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `every modified file must also be cited as evidence: ${item.path}`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
for (const item of draft.verification) {
|
|
775
|
+
if (/\r|\n|[;&|`$<>]/u.test(item.command)) {
|
|
776
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `verification command must be one non-composed command: ${JSON.stringify(item.command)}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return { ...draft, changes, evidence };
|
|
780
|
+
}
|
|
781
|
+
function schemaProperties(schema) {
|
|
782
|
+
if (schema === null || typeof schema !== "object" || Array.isArray(schema))
|
|
783
|
+
return /* @__PURE__ */ new Set();
|
|
784
|
+
const properties = Reflect.get(schema, "properties");
|
|
785
|
+
return properties !== null && typeof properties === "object" && !Array.isArray(properties) ? new Set(Object.keys(properties)) : /* @__PURE__ */ new Set();
|
|
786
|
+
}
|
|
787
|
+
function qlooFieldRoot(value) {
|
|
788
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\[\])?(?:\.[A-Za-z_][A-Za-z0-9_]*(?:\[\])?)*$/u.test(value)) {
|
|
789
|
+
return void 0;
|
|
790
|
+
}
|
|
791
|
+
return value.match(/^[A-Za-z_][A-Za-z0-9_]*/u)?.[0];
|
|
792
|
+
}
|
|
793
|
+
function validateFieldMapping(mapping, inputSchema, resultSchema) {
|
|
794
|
+
const inputFields = schemaProperties(inputSchema);
|
|
795
|
+
const resultFields = schemaProperties(resultSchema);
|
|
796
|
+
let inputCount = 0;
|
|
797
|
+
let outputCount = 0;
|
|
798
|
+
mapping.forEach((item, index) => {
|
|
799
|
+
const root = qlooFieldRoot(item.qloo_field);
|
|
800
|
+
if (item.qloo_role === "output") {
|
|
801
|
+
outputCount += 1;
|
|
802
|
+
if (!root || !resultFields.has(root)) {
|
|
803
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `/field_mapping/${index}/qloo_field is not in the normalized Qloo result schema: ${item.qloo_field}`);
|
|
804
|
+
}
|
|
805
|
+
if (item.normalized_result_field === null) {
|
|
806
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `/field_mapping/${index}/normalized_result_field is required for an output mapping`);
|
|
807
|
+
}
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
inputCount += 1;
|
|
811
|
+
if (!root || !inputFields.has(root)) {
|
|
812
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `/field_mapping/${index}/qloo_field is not in the selected Qloo input schema: ${item.qloo_field}`);
|
|
813
|
+
}
|
|
814
|
+
if (item.normalized_result_field !== null) {
|
|
815
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `/field_mapping/${index}/normalized_result_field must be null for a signal or filter mapping`);
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
if (inputCount === 0 || outputCount === 0) {
|
|
819
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "field_mapping must contain at least one Qloo input and one normalized output");
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function projectIdentityFromContext(context) {
|
|
823
|
+
return {
|
|
824
|
+
name: context.project_name,
|
|
825
|
+
fingerprint: context.fingerprint,
|
|
826
|
+
context_schema_version: context.schema_version,
|
|
827
|
+
languages: [...context.languages],
|
|
828
|
+
package_managers: [...context.package_managers],
|
|
829
|
+
frameworks: [...context.frameworks],
|
|
830
|
+
source_roots: [...context.source_roots],
|
|
831
|
+
http_clients: [...context.http_clients],
|
|
832
|
+
data_modeling: [...context.data_modeling],
|
|
833
|
+
configuration: [...context.configuration],
|
|
834
|
+
test_frameworks: [...context.test_frameworks],
|
|
835
|
+
verification_scripts: [...context.verification_scripts]
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
function isInside(root, target) {
|
|
839
|
+
const pathFromRoot = relative(root, target);
|
|
840
|
+
return pathFromRoot === "" || !pathFromRoot.startsWith("..") && !isAbsolute(pathFromRoot);
|
|
841
|
+
}
|
|
842
|
+
async function hashEvidenceFile(cwd, path) {
|
|
843
|
+
const projectRoot = await realpath(resolve(cwd));
|
|
844
|
+
const target = resolve(projectRoot, path);
|
|
845
|
+
let metadata;
|
|
846
|
+
try {
|
|
847
|
+
metadata = await lstat(target);
|
|
848
|
+
} catch {
|
|
849
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `evidence file does not exist: ${path}`);
|
|
850
|
+
}
|
|
851
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
852
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `evidence must be a regular file: ${path}`);
|
|
853
|
+
}
|
|
854
|
+
const realTarget = await realpath(target);
|
|
855
|
+
if (!isInside(projectRoot, realTarget)) {
|
|
856
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `evidence escapes the project: ${path}`);
|
|
857
|
+
}
|
|
858
|
+
if (metadata.size > QLOO_INTEGRATION_PLAN_MAX_EVIDENCE_BYTES) {
|
|
859
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `evidence file exceeds the ${QLOO_INTEGRATION_PLAN_MAX_EVIDENCE_BYTES}-byte limit: ${path}`);
|
|
860
|
+
}
|
|
861
|
+
return createHash2("sha256").update(await readFile(realTarget)).digest("hex");
|
|
862
|
+
}
|
|
863
|
+
function planId(unsigned) {
|
|
864
|
+
return `qplan_${createHash2("sha256").update(JSON.stringify(unsigned)).digest("hex").slice(0, 24)}`;
|
|
865
|
+
}
|
|
866
|
+
function validateGoal(goal) {
|
|
867
|
+
const normalized = goal.trim();
|
|
868
|
+
if (normalized.length === 0) {
|
|
869
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "the integration goal must not be empty");
|
|
870
|
+
}
|
|
871
|
+
if (Buffer.byteLength(normalized) > QLOO_INTEGRATION_PLAN_MAX_GOAL_BYTES) {
|
|
872
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `the integration goal exceeds the ${QLOO_INTEGRATION_PLAN_MAX_GOAL_BYTES}-byte limit`);
|
|
873
|
+
}
|
|
874
|
+
rejectUnsafeText(normalized, "/goal/");
|
|
875
|
+
return normalized;
|
|
876
|
+
}
|
|
877
|
+
async function createIntegrationPlanArtifact(options) {
|
|
878
|
+
const goal = validateGoal(options.goal);
|
|
879
|
+
const draft = canonicalizeDraft(parseDraft(options.draft));
|
|
880
|
+
rejectUnsafeText(draft);
|
|
881
|
+
const { context } = await inspectProjectContext({
|
|
882
|
+
cwd: options.cwd,
|
|
883
|
+
paths: options.paths,
|
|
884
|
+
refresh: true,
|
|
885
|
+
...options.now === void 0 ? {} : { now: options.now }
|
|
886
|
+
});
|
|
887
|
+
const manifest = getQlooAdapterContractManifest();
|
|
888
|
+
const operationContract = manifest.operations.find(({ id }) => id === draft.operation);
|
|
889
|
+
if (!operationContract) {
|
|
890
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `unsupported Qloo operation: ${draft.operation}`);
|
|
891
|
+
}
|
|
892
|
+
validateFieldMapping(draft.field_mapping, operationContract.input_schema, manifest.result_schema);
|
|
893
|
+
const evidence = await Promise.all(draft.evidence.map(async (item) => ({
|
|
894
|
+
...item,
|
|
895
|
+
sha256: await hashEvidenceFile(options.cwd, item.path)
|
|
896
|
+
})));
|
|
897
|
+
const { operation: selectedOperation, transport, evidence: _draftEvidence, ...implementation } = draft;
|
|
898
|
+
const unsigned = {
|
|
899
|
+
schema_version: QLOO_INTEGRATION_PLAN_SCHEMA_VERSION,
|
|
900
|
+
generated_at: (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
901
|
+
goal,
|
|
902
|
+
...implementation,
|
|
903
|
+
project: projectIdentityFromContext(context),
|
|
904
|
+
qloo: {
|
|
905
|
+
operation: selectedOperation,
|
|
906
|
+
tool_name: operationContract.shared_tool_name,
|
|
907
|
+
transport,
|
|
908
|
+
manifest_schema_version: manifest.manifest_schema_version,
|
|
909
|
+
contract_version: manifest.contract_version,
|
|
910
|
+
contract_checksum: manifest.contract_checksum,
|
|
911
|
+
result_schema_version: manifest.result_schema_version
|
|
912
|
+
},
|
|
913
|
+
evidence
|
|
914
|
+
};
|
|
915
|
+
const artifact = { ...unsigned, plan_id: planId(unsigned) };
|
|
916
|
+
if (!Value2.Check(QLOO_INTEGRATION_PLAN_SCHEMA, artifact)) {
|
|
917
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `finalized plan does not match schema: ${validationMessage(QLOO_INTEGRATION_PLAN_SCHEMA, artifact)}`);
|
|
918
|
+
}
|
|
919
|
+
if (Buffer.byteLength(JSON.stringify(artifact)) > QLOO_INTEGRATION_PLAN_MAX_BYTES) {
|
|
920
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `finalized plan exceeds the ${QLOO_INTEGRATION_PLAN_MAX_BYTES}-byte limit`);
|
|
921
|
+
}
|
|
922
|
+
return artifact;
|
|
923
|
+
}
|
|
924
|
+
async function saveIntegrationPlanArtifact(plan, paths) {
|
|
925
|
+
const validated = parseArtifact(plan);
|
|
926
|
+
await mkdir(paths.plansDir, { recursive: true, mode: 448 });
|
|
927
|
+
const target = join(paths.plansDir, `${validated.plan_id}.json`);
|
|
928
|
+
const temporary = join(paths.plansDir, `.${validated.plan_id}.${randomUUID()}.tmp`);
|
|
929
|
+
try {
|
|
930
|
+
await writeFile(temporary, `${JSON.stringify(validated, null, 2)}
|
|
931
|
+
`, {
|
|
932
|
+
encoding: "utf8",
|
|
933
|
+
flag: "wx",
|
|
934
|
+
mode: 384
|
|
935
|
+
});
|
|
936
|
+
await rename(temporary, target);
|
|
937
|
+
} catch (error) {
|
|
938
|
+
await unlink(temporary).catch(() => void 0);
|
|
939
|
+
throw error;
|
|
940
|
+
}
|
|
941
|
+
return target;
|
|
942
|
+
}
|
|
943
|
+
function parseArtifact(value) {
|
|
944
|
+
if (!Value2.Check(QLOO_INTEGRATION_PLAN_SCHEMA, value)) {
|
|
945
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `stored plan does not match schema: ${validationMessage(QLOO_INTEGRATION_PLAN_SCHEMA, value)}`);
|
|
946
|
+
}
|
|
947
|
+
const artifact = structuredClone(value);
|
|
948
|
+
if (validateGoal(artifact.goal) !== artifact.goal) {
|
|
949
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "stored integration goal is not canonical");
|
|
950
|
+
}
|
|
951
|
+
const { plan_id: storedId, ...unsigned } = artifact;
|
|
952
|
+
if (storedId !== planId(unsigned)) {
|
|
953
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "stored plan integrity check failed");
|
|
954
|
+
}
|
|
955
|
+
if (!Number.isFinite(Date.parse(artifact.generated_at))) {
|
|
956
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "stored plan has an invalid generation time");
|
|
957
|
+
}
|
|
958
|
+
rejectUnsafeText(artifact);
|
|
959
|
+
canonicalizeDraft({
|
|
960
|
+
status: artifact.status,
|
|
961
|
+
summary: artifact.summary,
|
|
962
|
+
operation: artifact.qloo.operation,
|
|
963
|
+
transport: artifact.qloo.transport,
|
|
964
|
+
field_mapping: artifact.field_mapping,
|
|
965
|
+
boundaries: artifact.boundaries,
|
|
966
|
+
error_handling: artifact.error_handling,
|
|
967
|
+
privacy_constraints: artifact.privacy_constraints,
|
|
968
|
+
changes: artifact.changes,
|
|
969
|
+
verification: artifact.verification,
|
|
970
|
+
rollout: artifact.rollout,
|
|
971
|
+
rollback: artifact.rollback,
|
|
972
|
+
assumptions: artifact.assumptions,
|
|
973
|
+
unresolved_decisions: artifact.unresolved_decisions,
|
|
974
|
+
evidence: artifact.evidence.map(({ path, relevance }) => ({ path, relevance }))
|
|
975
|
+
});
|
|
976
|
+
for (const item of [...artifact.changes, ...artifact.evidence]) {
|
|
977
|
+
if (canonicalRelativePath(item.path) !== item.path) {
|
|
978
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", `stored plan path is not canonical: ${item.path}`);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
return artifact;
|
|
982
|
+
}
|
|
983
|
+
async function loadIntegrationPlanArtifact(options) {
|
|
984
|
+
if (!QLOO_INTEGRATION_PLAN_ID_PATTERN.test(options.planId)) {
|
|
985
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "--plan must be a Qloo plan ID");
|
|
986
|
+
}
|
|
987
|
+
const path = join(options.paths.plansDir, `${options.planId}.json`);
|
|
988
|
+
let metadata;
|
|
989
|
+
try {
|
|
990
|
+
metadata = await lstat(path);
|
|
991
|
+
} catch {
|
|
992
|
+
throw new QlooIntegrationPlanError("PLAN_NOT_FOUND", `integration plan not found: ${options.planId}`);
|
|
993
|
+
}
|
|
994
|
+
if (!metadata.isFile() || metadata.isSymbolicLink()) {
|
|
995
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "stored integration plan is not a regular file");
|
|
996
|
+
}
|
|
997
|
+
if (metadata.size > QLOO_INTEGRATION_PLAN_MAX_BYTES) {
|
|
998
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "stored integration plan exceeds its size limit");
|
|
999
|
+
}
|
|
1000
|
+
let parsed;
|
|
1001
|
+
try {
|
|
1002
|
+
parsed = JSON.parse(await readFile(path, "utf8"));
|
|
1003
|
+
} catch {
|
|
1004
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "stored integration plan is not valid JSON");
|
|
1005
|
+
}
|
|
1006
|
+
const artifact = parseArtifact(parsed);
|
|
1007
|
+
if (artifact.plan_id !== options.planId) {
|
|
1008
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "stored plan ID does not match its filename");
|
|
1009
|
+
}
|
|
1010
|
+
if ((options.requireReady ?? true) && artifact.status !== "ready") {
|
|
1011
|
+
throw new QlooIntegrationPlanError("PLAN_NOT_READY", `integration plan ${artifact.plan_id} still needs input: ${artifact.unresolved_decisions.join("; ")}`);
|
|
1012
|
+
}
|
|
1013
|
+
const manifest = getQlooAdapterContractManifest();
|
|
1014
|
+
const operationContract = manifest.operations.find(({ id }) => id === artifact.qloo.operation);
|
|
1015
|
+
if (artifact.qloo.contract_checksum !== manifest.contract_checksum || artifact.qloo.contract_version !== manifest.contract_version || artifact.qloo.manifest_schema_version !== manifest.manifest_schema_version || artifact.qloo.result_schema_version !== manifest.result_schema_version || !QLOO_WORKFLOW_OPERATION_IDS.includes(artifact.qloo.operation) || operationContract?.shared_tool_name !== artifact.qloo.tool_name) {
|
|
1016
|
+
throw new QlooIntegrationPlanError("PLAN_STALE", "the Qloo workflow contract changed after this plan was generated; create a new plan");
|
|
1017
|
+
}
|
|
1018
|
+
validateFieldMapping(artifact.field_mapping, operationContract.input_schema, manifest.result_schema);
|
|
1019
|
+
const { context } = await inspectProjectContext({
|
|
1020
|
+
cwd: options.cwd,
|
|
1021
|
+
paths: options.paths,
|
|
1022
|
+
refresh: true
|
|
1023
|
+
});
|
|
1024
|
+
if (artifact.project.name !== context.project_name || artifact.project.fingerprint !== context.fingerprint) {
|
|
1025
|
+
throw new QlooIntegrationPlanError("PLAN_STALE", "the project manifest or structure changed after this plan was generated; create a new plan");
|
|
1026
|
+
}
|
|
1027
|
+
for (const item of artifact.evidence) {
|
|
1028
|
+
let currentHash;
|
|
1029
|
+
try {
|
|
1030
|
+
currentHash = await hashEvidenceFile(options.cwd, item.path);
|
|
1031
|
+
} catch {
|
|
1032
|
+
throw new QlooIntegrationPlanError("PLAN_STALE", `plan evidence is no longer available: ${item.path}`);
|
|
1033
|
+
}
|
|
1034
|
+
if (currentHash !== item.sha256) {
|
|
1035
|
+
throw new QlooIntegrationPlanError("PLAN_STALE", `plan evidence changed after generation: ${item.path}`);
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return artifact;
|
|
1039
|
+
}
|
|
1040
|
+
function createIntegrationPlanSubmissionTool(options) {
|
|
1041
|
+
let artifact;
|
|
1042
|
+
const tool = {
|
|
1043
|
+
name: "qloo_submit_integration_plan",
|
|
1044
|
+
label: "Submit Qloo integration plan",
|
|
1045
|
+
description: "Finalize one typed, evidence-backed Qloo integration plan. Call exactly once after inspecting project context, relevant source files, and the selected Qloo workflow contract.",
|
|
1046
|
+
promptSnippet: "Submit the final typed Qloo integration plan",
|
|
1047
|
+
promptGuidelines: [
|
|
1048
|
+
"Call only after all read-only planning work is complete.",
|
|
1049
|
+
"Use project-relative regular files as evidence and include only implementation files that the build should create or modify.",
|
|
1050
|
+
"Set status to needs_input when a material decision remains; a ready plan cannot contain unresolved decisions."
|
|
1051
|
+
],
|
|
1052
|
+
parameters: QLOO_INTEGRATION_PLAN_DRAFT_SCHEMA,
|
|
1053
|
+
async execute(_toolCallId, params) {
|
|
1054
|
+
if (artifact) {
|
|
1055
|
+
throw new QlooIntegrationPlanError("PLAN_INVALID", "an integration plan was already submitted");
|
|
1056
|
+
}
|
|
1057
|
+
artifact = await createIntegrationPlanArtifact({
|
|
1058
|
+
...options,
|
|
1059
|
+
draft: params
|
|
1060
|
+
});
|
|
1061
|
+
return {
|
|
1062
|
+
content: [{
|
|
1063
|
+
type: "text",
|
|
1064
|
+
text: JSON.stringify({
|
|
1065
|
+
accepted: true,
|
|
1066
|
+
plan_id: artifact.plan_id,
|
|
1067
|
+
status: artifact.status
|
|
1068
|
+
})
|
|
1069
|
+
}],
|
|
1070
|
+
details: {
|
|
1071
|
+
schema_version: artifact.schema_version,
|
|
1072
|
+
plan_id: artifact.plan_id,
|
|
1073
|
+
status: artifact.status
|
|
1074
|
+
},
|
|
1075
|
+
terminate: true
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
};
|
|
1079
|
+
return {
|
|
1080
|
+
tool,
|
|
1081
|
+
getArtifact: () => artifact
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
export {
|
|
1085
|
+
QLOO_INTEGRATION_PLAN_DRAFT_SCHEMA,
|
|
1086
|
+
QLOO_INTEGRATION_PLAN_ID_PATTERN,
|
|
1087
|
+
QLOO_INTEGRATION_PLAN_MAX_BYTES,
|
|
1088
|
+
QLOO_INTEGRATION_PLAN_MAX_EVIDENCE_BYTES,
|
|
1089
|
+
QLOO_INTEGRATION_PLAN_MAX_GOAL_BYTES,
|
|
1090
|
+
QLOO_INTEGRATION_PLAN_SCHEMA,
|
|
1091
|
+
QLOO_INTEGRATION_PLAN_SCHEMA_VERSION,
|
|
1092
|
+
QlooIntegrationPlanError,
|
|
1093
|
+
createIntegrationPlanArtifact,
|
|
1094
|
+
createIntegrationPlanSubmissionTool,
|
|
1095
|
+
loadIntegrationPlanArtifact,
|
|
1096
|
+
saveIntegrationPlanArtifact
|
|
1097
|
+
};
|