@blotout/qinto-cli 0.1.9 → 0.1.10

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.
@@ -7,6 +7,10 @@ var __export = (target, all) => {
7
7
  // ../../../libs/app-manifest/src/cron.ts
8
8
  var MAX_CRON_LENGTH = 128;
9
9
 
10
+ // ../../../libs/app-manifest/src/tool-names.ts
11
+ var MAX_APP_TOOL_NAME_LENGTH = 32;
12
+ var APP_TOOL_NAME_PATTERN = /^[a-zA-Z][a-zA-Z0-9_]*$/;
13
+
10
14
  // ../../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
11
15
  var external_exports = {};
12
16
  __export(external_exports, {
@@ -2348,13 +2352,13 @@ var $ZodType = /* @__PURE__ */ $constructor("$ZodType", (inst, def) => {
2348
2352
  canary.aborted = true;
2349
2353
  return canary;
2350
2354
  }
2351
- const checkResult = runChecks(payload, checks, ctx);
2352
- if (checkResult instanceof Promise) {
2355
+ const checkResult2 = runChecks(payload, checks, ctx);
2356
+ if (checkResult2 instanceof Promise) {
2353
2357
  if (ctx.async === false)
2354
2358
  throw new $ZodAsyncError();
2355
- return checkResult.then((checkResult2) => inst._zod.parse(checkResult2, ctx));
2359
+ return checkResult2.then((checkResult3) => inst._zod.parse(checkResult3, ctx));
2356
2360
  }
2357
- return inst._zod.parse(checkResult, ctx);
2361
+ return inst._zod.parse(checkResult2, ctx);
2358
2362
  };
2359
2363
  inst._zod.run = (payload, ctx) => {
2360
2364
  if (ctx.skipChecks) {
@@ -14628,6 +14632,11 @@ var envelopeResponseSchema = external_exports.object({
14628
14632
  var CONTROL_ENVELOPE_VERSION = 1;
14629
14633
  var MAX_CONTROL_BYTES = 256 * 1024;
14630
14634
  var MAX_MIGRATION_CHECKPOINT_BYTES = 16 * 1024;
14635
+ var MAX_TOOL_ARGUMENTS_BYTES = 32 * 1024;
14636
+ var MAX_TOOL_RESULT_BYTES = 64 * 1024;
14637
+ var MAX_TOOL_DATA_DEPTH = 8;
14638
+ var MAX_TOOL_DATA_NODES = 5e3;
14639
+ var MAX_TOOL_ERROR_MESSAGE_LENGTH = 512;
14631
14640
  var installSchema2 = external_exports.object({
14632
14641
  id: external_exports.string().min(1).max(64),
14633
14642
  ord: external_exports.number().int().min(1),
@@ -14701,15 +14710,32 @@ var scheduleSchema = external_exports.object({
14701
14710
  }).strict()
14702
14711
  ).min(1).max(MAX_INSTALLS_PER_APP)
14703
14712
  }).strict();
14713
+ var toolSchema = external_exports.object({
14714
+ // The manifest's tools[].name, never the `<slug>_` composed MCP name: the slug is already in
14715
+ // `app.slug`, and carrying it twice would give the app worker two sources for one fact.
14716
+ name: external_exports.string().min(1).max(MAX_APP_TOOL_NAME_LENGTH).regex(APP_TOOL_NAME_PATTERN),
14717
+ // Who asked, and what they may do on this site. Derived from the bearer by the portal, never
14718
+ // from the model's arguments.
14719
+ viewer: external_exports.object({ userId: external_exports.string().min(1).max(64), access: external_exports.enum(["read", "write"]) }).strict(),
14720
+ // Already validated against the tool's declared inputSchema, with the reserved routing names
14721
+ // stripped. Every declared input schema has an object root, so this is an object too — and a
14722
+ // call that names no arguments normalizes to `{}` rather than reaching the handler as undefined.
14723
+ arguments: external_exports.record(external_exports.string(), external_exports.unknown()).default({}),
14724
+ // Stable across a collapsed retry, so an app can deduplicate its external effects on it.
14725
+ callId: external_exports.string().min(1).max(64)
14726
+ }).strict().refine((tool) => {
14727
+ return new TextEncoder().encode(JSON.stringify(tool.arguments)).length <= MAX_TOOL_ARGUMENTS_BYTES;
14728
+ }, "Tool arguments exceed the size cap");
14704
14729
  var controlEnvelopeSchema = external_exports.object({
14705
14730
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
14706
- kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule"]),
14731
+ kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule", "tool"]),
14707
14732
  site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
14708
14733
  app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
14709
14734
  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(),
14710
14735
  lifecycle: lifecycleEventSchema.optional(),
14711
14736
  migration: migrationSchema.optional(),
14712
- schedule: scheduleSchema.optional()
14737
+ schedule: scheduleSchema.optional(),
14738
+ tool: toolSchema.optional()
14713
14739
  }).strict().superRefine((envelope, ctx) => {
14714
14740
  if (envelope.kind === "lifecycle" !== (envelope.lifecycle !== void 0)) {
14715
14741
  ctx.addIssue({ code: "custom", path: ["lifecycle"], message: "Lifecycle payload must match the control kind" });
@@ -14720,13 +14746,50 @@ var controlEnvelopeSchema = external_exports.object({
14720
14746
  if (envelope.kind === "schedule" !== (envelope.schedule !== void 0)) {
14721
14747
  ctx.addIssue({ code: "custom", path: ["schedule"], message: "Schedule payload must match the control kind" });
14722
14748
  }
14749
+ if (envelope.kind === "tool" !== (envelope.tool !== void 0)) {
14750
+ ctx.addIssue({ code: "custom", path: ["tool"], message: "Tool payload must match the control kind" });
14751
+ }
14723
14752
  if (envelope.kind === "lifecycle" && envelope.install === void 0) {
14724
14753
  ctx.addIssue({ code: "custom", path: ["install"], message: "Lifecycle control needs an install" });
14725
14754
  }
14755
+ if (envelope.kind === "tool" && envelope.install === void 0) {
14756
+ ctx.addIssue({ code: "custom", path: ["install"], message: "Tool control needs an install" });
14757
+ }
14726
14758
  });
14759
+ var toolDataWithinLimits = (value) => {
14760
+ const stack = [{ value, depth: 0 }];
14761
+ let seen = 0;
14762
+ while (stack.length > 0) {
14763
+ const next = stack.pop();
14764
+ seen += 1;
14765
+ if (next.depth > MAX_TOOL_DATA_DEPTH || seen > MAX_TOOL_DATA_NODES) {
14766
+ return false;
14767
+ }
14768
+ if (next.value !== null && typeof next.value === "object") {
14769
+ for (const child of Object.values(next.value)) {
14770
+ stack.push({ value: child, depth: next.depth + 1 });
14771
+ }
14772
+ }
14773
+ }
14774
+ return true;
14775
+ };
14776
+ var toolResultSchema = external_exports.object({
14777
+ content: external_exports.string().max(MAX_TOOL_RESULT_BYTES),
14778
+ // MCP defines `structuredContent` as an object, so a bare scalar or array is refused here rather
14779
+ // than surfacing as a protocol error at the client.
14780
+ data: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
14781
+ // True when the handler chose to fail with `ctx.fail`: `content` is then the app's own message
14782
+ // for the model. A thrown handler is `ok: false` with no `tool` at all.
14783
+ failed: external_exports.boolean()
14784
+ }).strict().refine((result) => {
14785
+ return toolDataWithinLimits(result.data);
14786
+ }, "Tool result nests too deeply or carries too many values").refine((result) => {
14787
+ return new TextEncoder().encode(JSON.stringify({ content: result.content, data: result.data ?? null })).length <= MAX_TOOL_RESULT_BYTES;
14788
+ }, "Tool result exceeds the size cap");
14727
14789
  var controlResponseSchema = external_exports.object({
14728
14790
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
14729
14791
  ok: external_exports.boolean(),
14792
+ tool: toolResultSchema.optional(),
14730
14793
  migration: external_exports.union([
14731
14794
  external_exports.object({ done: external_exports.literal(true) }).strict(),
14732
14795
  external_exports.object({ done: external_exports.literal(false), checkpoint: external_exports.unknown() }).strict().refine((result) => {
@@ -16177,7 +16240,8 @@ var buildCatalogQuery = ({ env, ticket, installId }) => {
16177
16240
  // src/schedule.ts
16178
16241
  var dispatchSchedule = async (envelope, bundle, env) => {
16179
16242
  const schedule = envelope.schedule;
16180
- const handler = bundle.schedules?.[schedule.id];
16243
+ const schedules = bundle.schedules;
16244
+ const handler = schedules !== void 0 && Object.hasOwn(schedules, schedule.id) ? schedules[schedule.id] : void 0;
16181
16245
  if (typeof handler !== "function") {
16182
16246
  return json2(200, {
16183
16247
  v: CONTROL_ENVELOPE_VERSION,
@@ -16239,6 +16303,141 @@ var dispatchSchedule = async (envelope, bundle, env) => {
16239
16303
  return json2(200, { v: CONTROL_ENVELOPE_VERSION, ok: true });
16240
16304
  };
16241
16305
 
16306
+ // src/tool.ts
16307
+ var failure = (message) => {
16308
+ return json2(200, { v: CONTROL_ENVELOPE_VERSION, ok: false, error: { message } });
16309
+ };
16310
+ var isFailure = (value) => {
16311
+ return value !== null && typeof value === "object" && value.kind === "tool_error" && typeof value.message === "string";
16312
+ };
16313
+ var isResult = (value) => {
16314
+ return value !== null && typeof value === "object" && typeof value.content === "string";
16315
+ };
16316
+ var isStructuredData = (data) => {
16317
+ return data === void 0 || data !== null && typeof data === "object" && !Array.isArray(data);
16318
+ };
16319
+ var isPlainContainer = (value) => {
16320
+ const prototype = Object.getPrototypeOf(value);
16321
+ return Array.isArray(value) || prototype === Object.prototype || prototype === null;
16322
+ };
16323
+ var jsonScalarVerdict = (value) => {
16324
+ if (value === null || typeof value === "string" || typeof value === "boolean") {
16325
+ return "ok";
16326
+ }
16327
+ if (typeof value === "number") {
16328
+ return Number.isFinite(value) ? "ok" : "invalid";
16329
+ }
16330
+ if (value === void 0 || typeof value !== "object" || !isPlainContainer(value)) {
16331
+ return "invalid";
16332
+ }
16333
+ return "container";
16334
+ };
16335
+ var isJsonValue = (root) => {
16336
+ const stack = [root];
16337
+ const seen = /* @__PURE__ */ new Set();
16338
+ while (stack.length > 0) {
16339
+ const value = stack.pop();
16340
+ const verdict = jsonScalarVerdict(value);
16341
+ if (verdict === "invalid") {
16342
+ return false;
16343
+ }
16344
+ if (verdict === "ok" || seen.has(value)) {
16345
+ continue;
16346
+ }
16347
+ seen.add(value);
16348
+ if (Array.isArray(value) && Object.keys(value).length !== value.length) {
16349
+ return false;
16350
+ }
16351
+ stack.push(...Object.values(value));
16352
+ }
16353
+ return true;
16354
+ };
16355
+ var truncate = (message) => {
16356
+ return message.length <= MAX_TOOL_ERROR_MESSAGE_LENGTH ? message : message.slice(0, MAX_TOOL_ERROR_MESSAGE_LENGTH);
16357
+ };
16358
+ var checkResult = (result) => {
16359
+ if (!isStructuredData(result.data)) {
16360
+ return "not_structured";
16361
+ }
16362
+ if (result.data !== void 0 && !isJsonValue(result.data)) {
16363
+ return "not_serializable";
16364
+ }
16365
+ let encoded;
16366
+ try {
16367
+ encoded = new TextEncoder().encode(JSON.stringify({ content: result.content, data: result.data ?? null }));
16368
+ } catch {
16369
+ return "not_serializable";
16370
+ }
16371
+ if (encoded.length > MAX_TOOL_RESULT_BYTES || !toolDataWithinLimits(result.data)) {
16372
+ return "too_large";
16373
+ }
16374
+ return "ok";
16375
+ };
16376
+ var buildContext2 = (envelope, env) => {
16377
+ const install = envelope.install;
16378
+ const { variableKinds, appVariableKinds } = contextKindsOf(env);
16379
+ const maps = parseVariableMaps(env);
16380
+ const appScope = projectAppValues(env, appVariableKinds);
16381
+ const { variables, secrets } = projectInstallValues(maps, variableKinds, install.ord);
16382
+ return {
16383
+ env,
16384
+ site: envelope.site,
16385
+ install: { id: install.id, label: install.label },
16386
+ viewer: envelope.tool.viewer,
16387
+ callId: envelope.tool.callId,
16388
+ startWorkflow: buildStartWorkflow({ ord: install.ord, installId: install.id, installLabel: install.label, anonId: null }),
16389
+ oauth: buildOauth(env, install.ord),
16390
+ variables,
16391
+ secrets,
16392
+ appVariables: appScope.appVariables,
16393
+ appSecrets: appScope.appSecrets,
16394
+ observability: buildObservability(install.ord),
16395
+ apps: buildAppsClient(env, { depth: 0 }),
16396
+ fail: (message) => {
16397
+ return { kind: "tool_error", message: truncate(message) };
16398
+ }
16399
+ };
16400
+ };
16401
+ var dispatchTool = async (envelope, bundle, env) => {
16402
+ const name = envelope.tool.name;
16403
+ const tools = bundle.tools;
16404
+ const handler = tools !== void 0 && Object.hasOwn(tools, name) ? tools[name] : void 0;
16405
+ if (typeof handler !== "function") {
16406
+ return failure(`missing handler: tools.${name}`);
16407
+ }
16408
+ setSpanAttributes({ "app.tool": name, "app.tool_call_id": envelope.tool.callId });
16409
+ let value;
16410
+ try {
16411
+ value = await installSpan(envelope.install, () => {
16412
+ return handler(envelope.tool.arguments, buildContext2(envelope, env));
16413
+ });
16414
+ } catch (error51) {
16415
+ console.warn(`tool ${name} failed for install ${envelope.install.id}: ${failureMessage(error51)}`);
16416
+ return failure(`tool ${name} failed`);
16417
+ }
16418
+ if (isFailure(value)) {
16419
+ return json2(200, { v: CONTROL_ENVELOPE_VERSION, ok: true, tool: { content: truncate(value.message), failed: true } });
16420
+ }
16421
+ if (!isResult(value)) {
16422
+ return failure(`tool ${name} returned no result`);
16423
+ }
16424
+ const verdict = checkResult(value);
16425
+ if (verdict === "not_structured") {
16426
+ return failure(`tool ${name} returned data that is not a JSON object`);
16427
+ }
16428
+ if (verdict === "not_serializable") {
16429
+ return failure(`tool ${name} returned data that cannot be serialized`);
16430
+ }
16431
+ if (verdict === "too_large") {
16432
+ return failure(`tool ${name} returned a result over the size cap`);
16433
+ }
16434
+ return json2(200, {
16435
+ v: CONTROL_ENVELOPE_VERSION,
16436
+ ok: true,
16437
+ tool: { content: value.content, ...value.data === void 0 ? {} : { data: value.data }, failed: false }
16438
+ });
16439
+ };
16440
+
16242
16441
  // src/control.ts
16243
16442
  var controlError = (status, message) => {
16244
16443
  return json2(status, { v: CONTROL_ENVELOPE_VERSION, ok: false, error: { message } });
@@ -16378,6 +16577,9 @@ var dispatchControl = async ({ request, env, versions }) => {
16378
16577
  case "schedule": {
16379
16578
  return dispatchSchedule(envelope, bundle, env);
16380
16579
  }
16580
+ case "tool": {
16581
+ return dispatchTool(envelope, bundle, env);
16582
+ }
16381
16583
  }
16382
16584
  };
16383
16585
 
@@ -16410,7 +16612,7 @@ var handlerRequest = (request, context, body) => {
16410
16612
  body: body.byteLength === 0 ? void 0 : body
16411
16613
  });
16412
16614
  };
16413
- var buildContext2 = ({ env, maps, kinds, appScope, install, site, params }) => {
16615
+ var buildContext3 = ({ env, maps, kinds, appScope, install, site, params }) => {
16414
16616
  const { variables, secrets } = projectInstallValues(maps, kinds, install.ord);
16415
16617
  return {
16416
16618
  env,
@@ -16502,7 +16704,7 @@ var dispatchEndpoint = async ({ request, env, versions }) => {
16502
16704
  return installSpan(install, () => {
16503
16705
  return handler(
16504
16706
  handlerRequest(request, context, body),
16505
- buildContext2({ env, maps, kinds: variableKinds, appScope, install, site: context.site, params: context.params })
16707
+ buildContext3({ env, maps, kinds: variableKinds, appScope, install, site: context.site, params: context.params })
16506
16708
  );
16507
16709
  });
16508
16710
  };
@@ -16788,7 +16990,7 @@ var runInstalls = async (envelope, env, invoke) => {
16788
16990
  const { variableKinds: kinds, appVariableKinds } = contextKindsOf(env);
16789
16991
  const appScope = projectAppValues(env, appVariableKinds);
16790
16992
  const groups = [];
16791
- let failure;
16993
+ let failure2;
16792
16994
  for (const install of envelope.installs) {
16793
16995
  const emits = [];
16794
16996
  const pushes = [];
@@ -16802,7 +17004,7 @@ var runInstalls = async (envelope, env, invoke) => {
16802
17004
  } catch (error51) {
16803
17005
  const message = failureMessage(error51);
16804
17006
  console.warn(`app hook failed for install ${install.id}: ${message}`);
16805
- failure ??= message;
17007
+ failure2 ??= message;
16806
17008
  continue;
16807
17009
  }
16808
17010
  const contextWrites = context.writes();
@@ -16814,8 +17016,8 @@ var runInstalls = async (envelope, env, invoke) => {
16814
17016
  if (groups.length > 0) {
16815
17017
  body.effects = groups;
16816
17018
  }
16817
- if (failure !== void 0) {
16818
- body.error = { message: failure };
17019
+ if (failure2 !== void 0) {
17020
+ body.error = { message: failure2 };
16819
17021
  }
16820
17022
  return json2(200, body);
16821
17023
  };
@@ -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),
@@ -15926,15 +15936,32 @@ var scheduleSchema = external_exports.object({
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
+ }).strict().refine((tool) => {
15953
+ return new TextEncoder().encode(JSON.stringify(tool.arguments)).length <= MAX_TOOL_ARGUMENTS_BYTES;
15954
+ }, "Tool arguments exceed the size cap");
15929
15955
  var controlEnvelopeSchema = external_exports.object({
15930
15956
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
15931
- kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule"]),
15957
+ kind: external_exports.enum(["noop", "lifecycle", "migration", "schedule", "tool"]),
15932
15958
  site: external_exports.object({ origin: external_exports.string().min(1).max(255) }).strict(),
15933
15959
  app: external_exports.object({ slug: external_exports.string().min(1).max(30), version: external_exports.string().min(1).max(32) }).strict(),
15934
15960
  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
15961
  lifecycle: lifecycleEventSchema.optional(),
15936
15962
  migration: migrationSchema.optional(),
15937
- schedule: scheduleSchema.optional()
15963
+ schedule: scheduleSchema.optional(),
15964
+ tool: toolSchema.optional()
15938
15965
  }).strict().superRefine((envelope, ctx) => {
15939
15966
  if (envelope.kind === "lifecycle" !== (envelope.lifecycle !== void 0)) {
15940
15967
  ctx.addIssue({ code: "custom", path: ["lifecycle"], message: "Lifecycle payload must match the control kind" });
@@ -15945,13 +15972,50 @@ var controlEnvelopeSchema = external_exports.object({
15945
15972
  if (envelope.kind === "schedule" !== (envelope.schedule !== void 0)) {
15946
15973
  ctx.addIssue({ code: "custom", path: ["schedule"], message: "Schedule payload must match the control kind" });
15947
15974
  }
15975
+ if (envelope.kind === "tool" !== (envelope.tool !== void 0)) {
15976
+ ctx.addIssue({ code: "custom", path: ["tool"], message: "Tool payload must match the control kind" });
15977
+ }
15948
15978
  if (envelope.kind === "lifecycle" && envelope.install === void 0) {
15949
15979
  ctx.addIssue({ code: "custom", path: ["install"], message: "Lifecycle control needs an install" });
15950
15980
  }
15981
+ if (envelope.kind === "tool" && envelope.install === void 0) {
15982
+ ctx.addIssue({ code: "custom", path: ["install"], message: "Tool control needs an install" });
15983
+ }
15951
15984
  });
15985
+ var toolDataWithinLimits = /* @__PURE__ */ __name((value) => {
15986
+ const stack = [{ value, depth: 0 }];
15987
+ let seen = 0;
15988
+ while (stack.length > 0) {
15989
+ const next = stack.pop();
15990
+ seen += 1;
15991
+ if (next.depth > MAX_TOOL_DATA_DEPTH || seen > MAX_TOOL_DATA_NODES) {
15992
+ return false;
15993
+ }
15994
+ if (next.value !== null && typeof next.value === "object") {
15995
+ for (const child of Object.values(next.value)) {
15996
+ stack.push({ value: child, depth: next.depth + 1 });
15997
+ }
15998
+ }
15999
+ }
16000
+ return true;
16001
+ }, "toolDataWithinLimits");
16002
+ var toolResultSchema = external_exports.object({
16003
+ content: external_exports.string().max(MAX_TOOL_RESULT_BYTES),
16004
+ // MCP defines `structuredContent` as an object, so a bare scalar or array is refused here rather
16005
+ // than surfacing as a protocol error at the client.
16006
+ data: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
16007
+ // True when the handler chose to fail with `ctx.fail`: `content` is then the app's own message
16008
+ // for the model. A thrown handler is `ok: false` with no `tool` at all.
16009
+ failed: external_exports.boolean()
16010
+ }).strict().refine((result) => {
16011
+ return toolDataWithinLimits(result.data);
16012
+ }, "Tool result nests too deeply or carries too many values").refine((result) => {
16013
+ return new TextEncoder().encode(JSON.stringify({ content: result.content, data: result.data ?? null })).length <= MAX_TOOL_RESULT_BYTES;
16014
+ }, "Tool result exceeds the size cap");
15952
16015
  var controlResponseSchema = external_exports.object({
15953
16016
  v: external_exports.literal(CONTROL_ENVELOPE_VERSION),
15954
16017
  ok: external_exports.boolean(),
16018
+ tool: toolResultSchema.optional(),
15955
16019
  migration: external_exports.union([
15956
16020
  external_exports.object({ done: external_exports.literal(true) }).strict(),
15957
16021
  external_exports.object({ done: external_exports.literal(false), checkpoint: external_exports.unknown() }).strict().refine((result) => {
@@ -19052,17 +19116,25 @@ var readableContext = /* @__PURE__ */ __name((own, shared) => {
19052
19116
  }, "readableContext");
19053
19117
 
19054
19118
  // src/apps/observability.ts
19119
+ var APP_ERROR_DATA_POINT_RESERVE = 50;
19055
19120
  var observabilityApp = /* @__PURE__ */ __name((slug, app2) => {
19056
19121
  return { slug, name: app2.name, version: app2.version };
19057
19122
  }, "observabilityApp");
19058
19123
  var createDataPointBudget = /* @__PURE__ */ __name(() => {
19059
- let spent = 0;
19124
+ let events = 0;
19125
+ let total = 0;
19060
19126
  return {
19061
- take: /* @__PURE__ */ __name(() => {
19062
- if (spent >= MAX_APP_DATA_POINTS_PER_INVOCATION) {
19127
+ take: /* @__PURE__ */ __name((kind2) => {
19128
+ if (total >= MAX_APP_DATA_POINTS_PER_INVOCATION) {
19063
19129
  return false;
19064
19130
  }
19065
- spent += 1;
19131
+ if (kind2 === "event") {
19132
+ if (events >= MAX_APP_DATA_POINTS_PER_INVOCATION - APP_ERROR_DATA_POINT_RESERVE) {
19133
+ return false;
19134
+ }
19135
+ events += 1;
19136
+ }
19137
+ total += 1;
19066
19138
  return true;
19067
19139
  }, "take")
19068
19140
  };
@@ -19085,43 +19157,71 @@ var errorBlobs = /* @__PURE__ */ __name((app2, install, record2, rowId) => {
19085
19157
  var eventBlobs = /* @__PURE__ */ __name((app2, install, record2) => {
19086
19158
  return [app2.slug, app2.name, install.id, install.label, app2.version, record2.name, record2.status];
19087
19159
  }, "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) {
19160
+ var writeEvents = /* @__PURE__ */ __name((env, target, byOrd, events) => {
19161
+ for (const [index, event] of events.entries()) {
19099
19162
  const install = byOrd.get(event.ord);
19100
19163
  if (install === void 0) {
19101
19164
  addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
19102
19165
  continue;
19103
19166
  }
19104
- if (!target.budget.take()) {
19105
- addSpanEvent("app observability dropped at the data-point cap", { level: "warn", attrs: { app: target.app.slug } });
19106
- return;
19167
+ if (!target.budget.take("event")) {
19168
+ addSpanEvent("app observability dropped events at the data-point cap", {
19169
+ level: "warn",
19170
+ attrs: { app: target.app.slug, skipped: events.length - index }
19171
+ });
19172
+ break;
19107
19173
  }
19108
19174
  env.APP_EVENTS.writeDataPoint({ indexes: [`${install.id}:${event.status}`], blobs: eventBlobs(target.app, install, event) });
19109
19175
  }
19110
- for (const saved of record2.errors) {
19176
+ }, "writeEvents");
19177
+ var writeErrors = /* @__PURE__ */ __name((env, target, byOrd, errors) => {
19178
+ for (const [index, saved] of errors.entries()) {
19111
19179
  const install = byOrd.get(saved.ord);
19112
19180
  if (install === void 0) {
19113
19181
  addSpanEvent("app observability names an install the site did not send", { level: "warn", attrs: { app: target.app.slug } });
19114
19182
  continue;
19115
19183
  }
19116
- if (!target.budget.take()) {
19117
- addSpanEvent("app observability dropped at the data-point cap", { level: "warn", attrs: { app: target.app.slug } });
19118
- return;
19184
+ if (!target.budget.take("error")) {
19185
+ addSpanEvent("app observability dropped saved errors at the data-point cap", {
19186
+ level: "warn",
19187
+ attrs: { app: target.app.slug, skipped: errors.length - index }
19188
+ });
19189
+ break;
19119
19190
  }
19120
19191
  env.APP_ERRORS.writeDataPoint({
19121
19192
  indexes: [`${install.id}:${saved.category}`],
19122
19193
  blobs: errorBlobs(target.app, install, saved, ulid3())
19123
19194
  });
19124
19195
  }
19196
+ }, "writeErrors");
19197
+ var guarded = /* @__PURE__ */ __name((target, write) => {
19198
+ try {
19199
+ write();
19200
+ return true;
19201
+ } catch (error51) {
19202
+ addSpanEvent("app observability write failed", { level: "warn", attrs: { app: target.app.slug, error: String(error51) } });
19203
+ return false;
19204
+ }
19205
+ }, "guarded");
19206
+ var writeAppObservability = /* @__PURE__ */ __name((env, response, target) => {
19207
+ const record2 = parseAppObservability(response.headers.get(APP_OBSERVABILITY_HEADER));
19208
+ if (record2 === null) {
19209
+ return;
19210
+ }
19211
+ const byOrd = new Map(
19212
+ target.installs.map((install) => {
19213
+ return [install.ord, install];
19214
+ })
19215
+ );
19216
+ const eventsWritten = guarded(target, () => {
19217
+ writeEvents(env, target, byOrd, record2.events);
19218
+ });
19219
+ const errorsWritten = guarded(target, () => {
19220
+ writeErrors(env, target, byOrd, record2.errors);
19221
+ });
19222
+ if (!eventsWritten || !errorsWritten) {
19223
+ return;
19224
+ }
19125
19225
  if (record2.truncated === true) {
19126
19226
  addSpanEvent("the app recorded more observability than one invocation carries", { level: "warn", attrs: { app: target.app.slug } });
19127
19227
  }
@@ -20128,7 +20228,9 @@ var handleControl = /* @__PURE__ */ __name(async (request, env) => {
20128
20228
  method: "POST",
20129
20229
  headers,
20130
20230
  body,
20131
- signal: AbortSignal.timeout(1e4)
20231
+ // Strictly inside the portal's own deadline for the same kind, so a slow tool times out here
20232
+ // first and the failure is attributable to the app worker rather than to the hop above it.
20233
+ signal: AbortSignal.timeout(envelope.kind === "tool" ? CONTROL_TOOL_FORWARD_TIMEOUT_MS : CONTROL_FORWARD_TIMEOUT_MS)
20132
20234
  });
20133
20235
  adoptAppSpans(upstream);
20134
20236
  writeControlObservability(env, upstream, envelope);
@@ -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": "eccf5ef7a3b8ef9131bc38d28b66cc5a3add163b",
5
+ "builtAt": "2026-08-29T05:28:12.232Z",
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": "eccf5ef7a3b8ef9131bc38d28b66cc5a3add163b",
44
+ "builtAt": "2026-08-29T05:28:14.997Z",
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.10",
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": {