@blotout/qinto-cli 0.1.9 → 0.1.11

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.
@@ -739,6 +739,10 @@ var MAX_APPS_Q_BYTES = 4 * 1024 * 1024;
739
739
  // ../../libs/app-manifest/src/cron.ts
740
740
  var MAX_CRON_LENGTH = 128;
741
741
 
742
+ // ../../libs/app-manifest/src/tool-names.ts
743
+ var MAX_APP_TOOL_NAME_LENGTH = 32;
744
+ var APP_TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
745
+
742
746
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
743
747
  var external_exports = {};
744
748
  __export(external_exports, {
@@ -15853,6 +15857,12 @@ var validateEnvelopeResponse = /* @__PURE__ */ __name((raw2, installCount) => {
15853
15857
  var CONTROL_ENVELOPE_VERSION = 1;
15854
15858
  var MAX_CONTROL_BYTES = 256 * 1024;
15855
15859
  var MAX_MIGRATION_CHECKPOINT_BYTES = 16 * 1024;
15860
+ var MAX_TOOL_ARGUMENTS_BYTES = 32 * 1024;
15861
+ var MAX_TOOL_RESULT_BYTES = 64 * 1024;
15862
+ var MAX_TOOL_DATA_DEPTH = 8;
15863
+ var MAX_TOOL_DATA_NODES = 5e3;
15864
+ var CONTROL_FORWARD_TIMEOUT_MS = 1e4;
15865
+ var CONTROL_TOOL_FORWARD_TIMEOUT_MS = 5e4;
15856
15866
  var installSchema2 = external_exports.object({
15857
15867
  id: external_exports.string().min(1).max(64),
15858
15868
  ord: external_exports.number().int().min(1),
@@ -15922,19 +15932,39 @@ var scheduleSchema = external_exports.object({
15922
15932
  id: external_exports.string().min(1).max(64),
15923
15933
  ord: external_exports.number().int().min(1),
15924
15934
  label: external_exports.string().max(100),
15925
- catalogQueryTicket: external_exports.string().min(1).max(2048)
15935
+ queryTicket: external_exports.string().min(1).max(2048)
15926
15936
  }).strict()
15927
15937
  ).min(1).max(MAX_INSTALLS_PER_APP)
15928
15938
  }).strict();
15939
+ var toolSchema = external_exports.object({
15940
+ // The manifest's tools[].name, never the `<slug>_` composed MCP name: the slug is already in
15941
+ // `app.slug`, and carrying it twice would give the app worker two sources for one fact.
15942
+ name: external_exports.string().min(1).max(MAX_APP_TOOL_NAME_LENGTH).regex(APP_TOOL_NAME_PATTERN),
15943
+ // Who asked, and what they may do on this site. Derived from the bearer by the portal, never
15944
+ // from the model's arguments.
15945
+ viewer: external_exports.object({ userId: external_exports.string().min(1).max(64), access: external_exports.enum(["read", "write"]) }).strict(),
15946
+ // Already validated against the tool's declared inputSchema, with the reserved routing names
15947
+ // stripped. Every declared input schema has an object root, so this is an object too — and a
15948
+ // call that names no arguments normalizes to `{}` rather than reaching the handler as undefined.
15949
+ arguments: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
15950
+ // Stable across a collapsed retry, so an app can deduplicate its external effects on it.
15951
+ callId: external_exports.string().min(1).max(64),
15952
+ // The short-lived install/version-scoped credential used only by the app runtime's query
15953
+ // broker. It never enters handler arguments or app-authored result data.
15954
+ queryTicket: external_exports.string().min(1).max(2048)
15955
+ }).strict().refine((tool) => {
15956
+ return new TextEncoder().encode(JSON.stringify(tool.arguments)).length <= MAX_TOOL_ARGUMENTS_BYTES;
15957
+ }, "Tool arguments exceed the size cap");
15929
15958
  var controlEnvelopeSchema = external_exports.object({
15930
15959
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
15931
- kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule"]),
15960
+ kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule", "tool"]),
15932
15961
  site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
15933
15962
  app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
15934
15963
  install: external_exports.object({ id: external_exports.string().min(1).max(64), ord: external_exports.number().int().min(1), label: external_exports.string().max(100) }).strict().optional(),
15935
15964
  lifecycle: lifecycleEventSchema.optional(),
15936
15965
  migration: migrationSchema.optional(),
15937
- schedule: scheduleSchema.optional()
15966
+ schedule: scheduleSchema.optional(),
15967
+ tool: toolSchema.optional()
15938
15968
  }).strict().superRefine((envelope, ctx) => {
15939
15969
  if (envelope.kind === "lifecycle" !== (envelope.lifecycle !== void 0)) {
15940
15970
  ctx.addIssue({ code: "custom", path: ["lifecycle"], message: "Lifecycle payload must match the control kind" });
@@ -15945,13 +15975,50 @@ var controlEnvelopeSchema = external_exports.object({
15945
15975
  if (envelope.kind === "schedule" !== (envelope.schedule !== void 0)) {
15946
15976
  ctx.addIssue({ code: "custom", path: ["schedule"], message: "Schedule payload must match the control kind" });
15947
15977
  }
15978
+ if (envelope.kind === "tool" !== (envelope.tool !== void 0)) {
15979
+ ctx.addIssue({ code: "custom", path: ["tool"], message: "Tool payload must match the control kind" });
15980
+ }
15948
15981
  if (envelope.kind === "lifecycle" && envelope.install === void 0) {
15949
15982
  ctx.addIssue({ code: "custom", path: ["install"], message: "Lifecycle control needs an install" });
15950
15983
  }
15984
+ if (envelope.kind === "tool" && envelope.install === void 0) {
15985
+ ctx.addIssue({ code: "custom", path: ["install"], message: "Tool control needs an install" });
15986
+ }
15951
15987
  });
15988
+ var toolDataWithinLimits = /* @__PURE__ */ __name((value) => {
15989
+ const stack = [{ value, depth: 0 }];
15990
+ let seen = 0;
15991
+ while (stack.length > 0) {
15992
+ const next = stack.pop();
15993
+ seen += 1;
15994
+ if (next.depth > MAX_TOOL_DATA_DEPTH || seen > MAX_TOOL_DATA_NODES) {
15995
+ return false;
15996
+ }
15997
+ if (next.value !== null && typeof next.value === "object") {
15998
+ for (const child of Object.values(next.value)) {
15999
+ stack.push({ value: child, depth: next.depth + 1 });
16000
+ }
16001
+ }
16002
+ }
16003
+ return true;
16004
+ }, "toolDataWithinLimits");
16005
+ var toolResultSchema = external_exports.object({
16006
+ content: external_exports.string().max(MAX_TOOL_RESULT_BYTES),
16007
+ // MCP defines `structuredContent` as an object, so a bare scalar or array is refused here rather
16008
+ // than surfacing as a protocol error at the client.
16009
+ data: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
16010
+ // True when the handler chose to fail with `ctx.fail`: `content` is then the app's own message
16011
+ // for the model. A thrown handler is `ok: false` with no `tool` at all.
16012
+ failed: external_exports.boolean()
16013
+ }).strict().refine((result) => {
16014
+ return toolDataWithinLimits(result.data);
16015
+ }, "Tool result nests too deeply or carries too many values").refine((result) => {
16016
+ return new TextEncoder().encode(JSON.stringify({ content: result.content, data: result.data ?? null })).length <= MAX_TOOL_RESULT_BYTES;
16017
+ }, "Tool result exceeds the size cap");
15952
16018
  var controlResponseSchema = external_exports.object({
15953
16019
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
15954
16020
  ok: external_exports.boolean(),
16021
+ tool: toolResultSchema.optional(),
15955
16022
  migration: external_exports.union([
15956
16023
  external_exports.object({ done: external_exports.literal(true) }).strict(),
15957
16024
  external_exports.object({ done: external_exports.literal(false), checkpoint: external_exports.unknown() }).strict().refine((result) => {
@@ -19052,17 +19119,25 @@ var readableContext = /* @__PURE__ */ __name((own, shared) => {
19052
19119
  }, "readableContext");
19053
19120
 
19054
19121
  // src/apps/observability.ts
19122
+ var APP_ERROR_DATA_POINT_RESERVE = 50;
19055
19123
  var observabilityApp = /* @__PURE__ */ __name((slug, app2) => {
19056
19124
  return { slug, name: app2.name, version: app2.version };
19057
19125
  }, "observabilityApp");
19058
19126
  var createDataPointBudget = /* @__PURE__ */ __name(() => {
19059
- let spent = 0;
19127
+ let events = 0;
19128
+ let total = 0;
19060
19129
  return {
19061
- take: /* @__PURE__ */ __name(() => {
19062
- if (spent >= MAX_APP_DATA_POINTS_PER_INVOCATION) {
19130
+ take: /* @__PURE__ */ __name((kind2) => {
19131
+ if (total >= MAX_APP_DATA_POINTS_PER_INVOCATION) {
19063
19132
  return false;
19064
19133
  }
19065
- spent += 1;
19134
+ if (kind2 === "event") {
19135
+ if (events >= MAX_APP_DATA_POINTS_PER_INVOCATION - APP_ERROR_DATA_POINT_RESERVE) {
19136
+ return false;
19137
+ }
19138
+ events += 1;
19139
+ }
19140
+ total += 1;
19066
19141
  return true;
19067
19142
  }, "take")
19068
19143
  };
@@ -19085,43 +19160,71 @@ var errorBlobs = /* @__PURE__ */ __name((app2, install, record2, rowId) => {
19085
19160
  var eventBlobs = /* @__PURE__ */ __name((app2, install, record2) => {
19086
19161
  return [app2.slug, app2.name, install.id, install.label, app2.version, record2.name, record2.status];
19087
19162
  }, "eventBlobs");
19088
- var writeAppObservability = /* @__PURE__ */ __name((env, response, target) => {
19089
- const record2 = parseAppObservability(response.headers.get(APP_OBSERVABILITY_HEADER));
19090
- if (record2 === null) {
19091
- return;
19092
- }
19093
- const byOrd = new Map(
19094
- target.installs.map((install) => {
19095
- return [install.ord, install];
19096
- })
19097
- );
19098
- for (const event of record2.events) {
19163
+ var writeEvents = /* @__PURE__ */ __name((env, target, byOrd, events) => {
19164
+ for (const [index, event] of events.entries()) {
19099
19165
  const install = byOrd.get(event.ord);
19100
19166
  if (install === void 0) {
19101
19167
  addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
19102
19168
  continue;
19103
19169
  }
19104
- if (!target.budget.take()) {
19105
- addSpanEvent("app observability dropped at the data-point cap", { level: "warn", attrs: { app: target.app.slug } });
19106
- return;
19170
+ if (!target.budget.take("event")) {
19171
+ addSpanEvent("app observability dropped events at the data-point cap", {
19172
+ level: "warn",
19173
+ attrs: { app: target.app.slug, skipped: events.length - index }
19174
+ });
19175
+ break;
19107
19176
  }
19108
19177
  env.APP_EVENTS.writeDataPoint({ indexes: [`${install.id}:${event.status}`], blobs: eventBlobs(target.app, install, event) });
19109
19178
  }
19110
- for (const saved of record2.errors) {
19179
+ }, "writeEvents");
19180
+ var writeErrors = /* @__PURE__ */ __name((env, target, byOrd, errors) => {
19181
+ for (const [index, saved] of errors.entries()) {
19111
19182
  const install = byOrd.get(saved.ord);
19112
19183
  if (install === void 0) {
19113
19184
  addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
19114
19185
  continue;
19115
19186
  }
19116
- if (!target.budget.take()) {
19117
- addSpanEvent("app observability dropped at the data-point cap", { level: "warn", attrs: { app: target.app.slug } });
19118
- return;
19187
+ if (!target.budget.take("error")) {
19188
+ addSpanEvent("app observability dropped saved errors at the data-point cap", {
19189
+ level: "warn",
19190
+ attrs: { app: target.app.slug, skipped: errors.length - index }
19191
+ });
19192
+ break;
19119
19193
  }
19120
19194
  env.APP_ERRORS.writeDataPoint({
19121
19195
  indexes: [`${install.id}:${saved.category}`],
19122
19196
  blobs: errorBlobs(target.app, install, saved, ulid3())
19123
19197
  });
19124
19198
  }
19199
+ }, "writeErrors");
19200
+ var guarded = /* @__PURE__ */ __name((target, write) => {
19201
+ try {
19202
+ write();
19203
+ return true;
19204
+ } catch (error51) {
19205
+ addSpanEvent("app observability write failed", { level: "warn", attrs: { app: target.app.slug, error: String(error51) } });
19206
+ return false;
19207
+ }
19208
+ }, "guarded");
19209
+ var writeAppObservability = /* @__PURE__ */ __name((env, response, target) => {
19210
+ const record2 = parseAppObservability(response.headers.get(APP_OBSERVABILITY_HEADER));
19211
+ if (record2 === null) {
19212
+ return;
19213
+ }
19214
+ const byOrd = new Map(
19215
+ target.installs.map((install) => {
19216
+ return [install.ord, install];
19217
+ })
19218
+ );
19219
+ const eventsWritten = guarded(target, () => {
19220
+ writeEvents(env, target, byOrd, record2.events);
19221
+ });
19222
+ const errorsWritten = guarded(target, () => {
19223
+ writeErrors(env, target, byOrd, record2.errors);
19224
+ });
19225
+ if (!eventsWritten || !errorsWritten) {
19226
+ return;
19227
+ }
19125
19228
  if (record2.truncated === true) {
19126
19229
  addSpanEvent("the app recorded more observability than one invocation carries", { level: "warn", attrs: { app: target.app.slug } });
19127
19230
  }
@@ -20128,7 +20231,9 @@ var handleControl = /* @__PURE__ */ __name(async (request, env) => {
20128
20231
  method: "POST",
20129
20232
  headers,
20130
20233
  body,
20131
- signal: AbortSignal.timeout(1e4)
20234
+ // Strictly inside the portal's own deadline for the same kind, so a slow tool times out here
20235
+ // first and the failure is attributable to the app worker rather than to the hop above it.
20236
+ signal: AbortSignal.timeout(envelope.kind === "tool" ? CONTROL_TOOL_FORWARD_TIMEOUT_MS : CONTROL_FORWARD_TIMEOUT_MS)
20132
20237
  });
20133
20238
  adoptAppSpans(upstream);
20134
20239
  writeControlObservability(env, upstream, envelope);
@@ -20891,10 +20996,10 @@ var verifyAppUiToken = /* @__PURE__ */ __name(async (input) => {
20891
20996
  return { ok: true, claims: claims.data, kid: signature.kid, key: signature.key };
20892
20997
  }, "verifyAppUiToken");
20893
20998
 
20894
- // ../../libs/app-ui-token/src/schedule-catalog-ticket.ts
20895
- var APP_SCHEDULE_CATALOG_TICKET_ISSUER = "qinto-portal";
20999
+ // ../../libs/app-ui-token/src/runtime-query-ticket.ts
21000
+ var APP_RUNTIME_QUERY_TICKET_ISSUER = "qinto-portal";
20896
21001
  var claimsSchema2 = external_exports.object({
20897
- iss: external_exports.literal(APP_SCHEDULE_CATALOG_TICKET_ISSUER),
21002
+ iss: external_exports.literal(APP_RUNTIME_QUERY_TICKET_ISSUER),
20898
21003
  aud: external_exports.string().min(1),
20899
21004
  sub: external_exports.string().min(1),
20900
21005
  iat: external_exports.number().int(),
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "main": {
3
3
  "version": "0.1.0",
4
- "gitSha": "66e1bc45acbe4891948cbb3851f3f43ce06abc84",
5
- "builtAt": "2026-08-28T17:17:21.657Z",
4
+ "gitSha": "83bcac7072f2feb866505c4a7b99f7281b08d972",
5
+ "builtAt": "2026-08-29T21:13:15.188Z",
6
6
  "compatibilityDate": "2026-06-23",
7
7
  "compatibilityFlags": [
8
8
  "nodejs_compat"
@@ -40,8 +40,8 @@
40
40
  },
41
41
  "app-runtime": {
42
42
  "version": "0.1.0",
43
- "gitSha": "56a8b638b8c5712c5d9de6a9292d6a0dd0536653",
44
- "builtAt": "2026-08-28T10:56:46.912Z",
43
+ "gitSha": "83bcac7072f2feb866505c4a7b99f7281b08d972",
44
+ "builtAt": "2026-08-29T21:13:19.154Z",
45
45
  "compatibilityDate": "2026-06-23",
46
46
  "compatibilityFlags": [
47
47
  "nodejs_compat"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blotout/qinto-cli",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "type": "module",
5
5
  "description": "Qinto CLI: authenticate against the Qinto platform and operate it from the terminal or from an agent.",
6
6
  "repository": {