primitive-admin 1.2.0-alpha.3 → 1.2.0-alpha.4

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.
Files changed (57) hide show
  1. package/dist/src/commands/agent-sessions.js +4 -0
  2. package/dist/src/commands/agent-sessions.js.map +1 -1
  3. package/dist/src/commands/databases.js +13 -38
  4. package/dist/src/commands/databases.js.map +1 -1
  5. package/dist/src/commands/documents.d.ts +1 -1
  6. package/dist/src/commands/documents.js +70 -33
  7. package/dist/src/commands/documents.js.map +1 -1
  8. package/dist/src/commands/prompts.d.ts +7 -0
  9. package/dist/src/commands/prompts.js +44 -0
  10. package/dist/src/commands/prompts.js.map +1 -1
  11. package/dist/src/commands/sync.d.ts +12 -29
  12. package/dist/src/commands/sync.js +128 -138
  13. package/dist/src/commands/sync.js.map +1 -1
  14. package/dist/src/lib/aggregate-output.d.ts +25 -0
  15. package/dist/src/lib/aggregate-output.js +53 -0
  16. package/dist/src/lib/aggregate-output.js.map +1 -0
  17. package/dist/src/lib/api-client.d.ts +29 -16
  18. package/dist/src/lib/api-client.js +30 -28
  19. package/dist/src/lib/api-client.js.map +1 -1
  20. package/dist/src/lib/config-object-descriptor.js +14 -5
  21. package/dist/src/lib/config-object-descriptor.js.map +1 -1
  22. package/dist/src/lib/db-codegen/dbGenerator.js +5 -16
  23. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
  24. package/dist/src/lib/db-codegen/dbNaming.d.ts +3 -12
  25. package/dist/src/lib/db-codegen/dbNaming.js +3 -16
  26. package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
  27. package/dist/src/lib/db-codegen/dbTemplates.d.ts +7 -56
  28. package/dist/src/lib/db-codegen/dbTemplates.js +25 -125
  29. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  30. package/dist/src/lib/db-codegen/dbTypeIR.d.ts +1 -1
  31. package/dist/src/lib/function-sync.d.ts +3 -16
  32. package/dist/src/lib/function-sync.js +14 -18
  33. package/dist/src/lib/function-sync.js.map +1 -1
  34. package/dist/src/lib/generated-config-surfaces.d.ts +172 -47
  35. package/dist/src/lib/generated-config-surfaces.js +599 -90
  36. package/dist/src/lib/generated-config-surfaces.js.map +1 -1
  37. package/dist/src/lib/generated-sdk-types.d.ts +1 -1
  38. package/dist/src/lib/generated-sdk-types.js +1 -1
  39. package/dist/src/lib/generated-sdk-types.js.map +1 -1
  40. package/dist/src/lib/prompt-schema-codegen.js +5 -2
  41. package/dist/src/lib/prompt-schema-codegen.js.map +1 -1
  42. package/dist/src/lib/swift-codegen/dbGenerator.d.ts +4 -3
  43. package/dist/src/lib/swift-codegen/dbGenerator.js +14 -117
  44. package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -1
  45. package/dist/src/lib/swift-codegen/generator.d.ts +5 -5
  46. package/dist/src/lib/swift-codegen/generator.js +8 -13
  47. package/dist/src/lib/swift-codegen/generator.js.map +1 -1
  48. package/dist/src/lib/workflow-codegen/generator.d.ts +7 -7
  49. package/dist/src/lib/workflow-codegen/generator.js +16 -23
  50. package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
  51. package/dist/src/lib/workflow-codegen/invokerIR.d.ts +6 -12
  52. package/dist/src/lib/workflow-codegen/invokerIR.js +4 -5
  53. package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -1
  54. package/dist/src/lib/workflow-toml-validator.d.ts +2 -0
  55. package/dist/src/lib/workflow-toml-validator.js +13 -0
  56. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  57. package/package.json +2 -2
@@ -42,7 +42,10 @@ import { CONFIG_SURFACES, isEmptyForEmit, tomlGroupsOf, } from "../lib/generated
42
42
  // preflight refuses exactly what the admin routes refuse.
43
43
  import { promptKindOf, resolvePromptKind, } from "../lib/generated-config-surfaces.js";
44
44
  // #3798 — the `[prompt.agent]` declaration rule, vendored the same way.
45
- import { agentDeclarationFunctionKeys, agentTestCaseRefusal, resolveAgentDeclaration, } from "../lib/generated-config-surfaces.js";
45
+ import { agentDeclarationFunctionKeys, resolveAgentDeclaration, } from "../lib/generated-config-surfaces.js";
46
+ // #4262 — the agent test case's shape rule (`[test.agent]`), vendored the same
47
+ // way, so the preflight refuses exactly the sidecars the admin routes refuse.
48
+ import { AGENT_TEST_CASE_FIELDS, resolveAgentTestCase, } from "../lib/generated-config-surfaces.js";
46
49
  // #3799 — the meaning-preserving schema translation, vendored the same way, so
47
50
  // the preflight refuses exactly the schemas the admin routes refuse.
48
51
  import { agentSchemaTranslationRefusals } from "../lib/generated-config-surfaces.js";
@@ -51,7 +54,7 @@ import { agentSchemaTranslationRefusals } from "../lib/generated-config-surfaces
51
54
  import { resolvePromptStrictOutput } from "../lib/generated-config-surfaces.js";
52
55
  // #3179 — the function's two halves: the header payloads and the envelope
53
56
  // `config push` sends, and the bytes `config pull` writes back.
54
- import { applyFunctionPull, buildFunctionConfigPush, createDocumentSchemaDropWarner, documentSchemaRetentionFailure, buildFunctionCreatePayload, buildFunctionUpdatePayload, functionDiffEntity, functionTomlIgnoredKeyWarnings, functionTomlPreflightErrors, planDocumentSchemaPull, planFunctionPull, serializeFunction, } from "../lib/function-sync.js";
57
+ import { applyFunctionPull, buildFunctionConfigPush, createDocumentSchemaDropWarner, documentSchemaRetentionFailure, buildFunctionCreatePayload, buildFunctionUpdatePayload, functionDiffEntity, functionTomlPreflightErrors, planDocumentSchemaPull, planFunctionPull, serializeFunction, } from "../lib/function-sync.js";
55
58
  import { newerVersionLine, pullNewerVersionLine, rollbackDiffHint, } from "../lib/function-versions.js";
56
59
  import { FunctionBuildError } from "../lib/function-bundle.js";
57
60
  // #3182 — the local half of capability validation. The grammar is the vendored
@@ -1672,9 +1675,9 @@ export function serializeBlobBucket(bucket, ruleSetIdToName = new Map(), logger)
1672
1675
  * `config push` builds its bodies from and `config diff` compares.
1673
1676
  *
1674
1677
  * Read in CREATE mode, so the immutable `ttlTier` stays nameable. The rule set
1675
- * is left as `_ruleSetName` for `resolveRuleSetReference`; the deprecated
1676
- * `ruleSetId` spelling is forwarded as the id it already is. A file carrying
1677
- * both is refused by the preflight (`blobBucketFileErrors`) before this runs.
1678
+ * is left as `_ruleSetName` for `resolveRuleSetReference`. The id spelling
1679
+ * `ruleSetId` is a retired key (#4283): the preflight refuses a file carrying
1680
+ * it, and this parse never reads it.
1678
1681
  *
1679
1682
  * An omitted `name` stays omitted: the update leaves the server's name alone,
1680
1683
  * and only the create body falls back to the key.
@@ -1697,43 +1700,8 @@ export function parseBlobBucketToml(doc) {
1697
1700
  // Trimmed, as `applyRuleSetReference` does for the type configs.
1698
1701
  entity._ruleSetName = section.ruleSetName.trim();
1699
1702
  }
1700
- else if (typeof section.ruleSetId === "string" && section.ruleSetId !== "") {
1701
- entity.ruleSetId = section.ruleSetId;
1702
- }
1703
1703
  return entity;
1704
1704
  }
1705
- /**
1706
- * The advisory `config push` prints for a bucket file still naming its rule set
1707
- * by id (#4116). The key is deprecated, not retired: the file pushes.
1708
- */
1709
- export function blobBucketDeprecatedKeyWarning(filePath, tomlData) {
1710
- const bucket = tomlData?.bucket;
1711
- if (!bucket || bucket.ruleSetId === undefined)
1712
- return null;
1713
- // Beside `ruleSetName` it is a refusal instead (`blobBucketFileErrors`).
1714
- if (bucket.ruleSetName !== undefined)
1715
- return null;
1716
- return (`${filePath}: bucket.ruleSetId is deprecated — a rule-set id belongs to one app, ` +
1717
- `so a copied tree cannot resolve it; write ruleSetName = "<name>" ` +
1718
- "(config pull rewrites the file). Retiring the key is a later change.");
1719
- }
1720
- /** A bucket file's own refusals, decided from the file alone (#4116). */
1721
- export function blobBucketFileErrors(filePath, tomlData) {
1722
- const bucket = tomlData?.bucket;
1723
- if (bucket && bucket.ruleSetId !== undefined && bucket.ruleSetName !== undefined) {
1724
- return [`${filePath}: bucket.ruleSetId is set beside bucket.ruleSetName; keep ruleSetName`];
1725
- }
1726
- // The deprecated spelling is a TOML-only key, so the declared-type check
1727
- // reads only `ruleSetName`; the old key keeps the field's type under its
1728
- // own name, or a mistyped id would be dropped from the body unreported.
1729
- const field = BLOB_BUCKET_TABLE.fields.find((f) => f.field === "ruleSetId");
1730
- if (!field)
1731
- return [];
1732
- return configDeclaredTypeErrors(filePath, tomlData, {
1733
- ...BLOB_BUCKET_TABLE,
1734
- fields: [{ ...field, tomlKey: "ruleSetId" }],
1735
- });
1736
- }
1737
1705
  /**
1738
1706
  * Build the PATCH payload for a blob-bucket update from local TOML.
1739
1707
  *
@@ -2648,24 +2616,6 @@ export async function runConfigPushPreflight(input) {
2648
2616
  }
2649
2617
  }
2650
2618
  }
2651
- // #4116 — the deprecated `ruleSetId` spelling in a bucket file. Advisory
2652
- // only: the file pushes. Bounded by `--only`, like the notice above.
2653
- const preflightBucketsDir = join(configDir, "blob-buckets");
2654
- if (existsSync(preflightBucketsDir)) {
2655
- for (const file of selectFiles("blob-bucket", preflightBucketsDir, readdirSync(preflightBucketsDir).filter((f) => f.endsWith(".toml")))) {
2656
- let tomlData;
2657
- try {
2658
- tomlData = parseTomlFile(join(preflightBucketsDir, file));
2659
- }
2660
- catch {
2661
- // A file that does not parse is reported by the collector below.
2662
- continue;
2663
- }
2664
- const advice = blobBucketDeprecatedKeyWarning(`blob-buckets/${file}`, tomlData);
2665
- if (advice)
2666
- warn(` ${advice}`);
2667
- }
2668
- }
2669
2619
  // Validate app.toml settings up-front (issue #1033). A mistyped value
2670
2620
  // is a hard error that must abort BEFORE any mutation — same
2671
2621
  // fail-before-mutate contract as the vars preflight below. The apply
@@ -2781,7 +2731,7 @@ export async function runConfigPushPreflight(input) {
2781
2731
  preflightValidationErrors.push(` ${message}`);
2782
2732
  }
2783
2733
  // #3320 — and everything else the file decides on its own: the
2784
- // `entry`, the mode the two spellings resolve to, the trigger
2734
+ // `entry`, the retired runtime keys it refuses (#3188), the trigger
2785
2735
  // rules, and a webhook trigger's signing-secret requirement and
2786
2736
  // reference shape. These used to be raised inside the apply loop,
2787
2737
  // after webhooks, integrations, vars and database types had
@@ -2796,19 +2746,6 @@ export async function runConfigPushPreflight(input) {
2796
2746
  })) {
2797
2747
  preflightValidationErrors.push(` ${filePath}: ${message}`);
2798
2748
  }
2799
- // #3482 — the retired runtime keys. NOT refusals through phase 4:
2800
- // the file pushes with every other key applied, and the author
2801
- // hears which line to delete before phase 5 refuses it. On
2802
- // stderr, like every other advisory, so a scripted push reading
2803
- // stdout is unaffected. Inside this loop rather than over the
2804
- // whole tree, so `--only` bounds the advice as it bounds the
2805
- // refusals (#2645).
2806
- for (const message of functionTomlIgnoredKeyWarnings({
2807
- functionKey,
2808
- tomlData,
2809
- })) {
2810
- warn(` ${message}`);
2811
- }
2812
2749
  }
2813
2750
  }
2814
2751
  }
@@ -2868,8 +2805,10 @@ export async function runConfigPushPreflight(input) {
2868
2805
  }
2869
2806
  }
2870
2807
  }
2871
- // #3798 — a test case on an agent is refused by the admin routes; say so
2872
- // here, before any other file is applied.
2808
+ // #3798, #4262 — a prompt's sidecar against its prompt's kind: an agent's
2809
+ // case is checked by the agent test case's shape rule, and `[test.agent]`
2810
+ // on a chat or decisions prompt's case is refused, before any file is
2811
+ // applied.
2873
2812
  for (const message of promptAgentSidecarErrors(configDir)) {
2874
2813
  preflightValidationErrors.push(` ${message}`);
2875
2814
  }
@@ -3239,10 +3178,39 @@ export function partitionPromptFilesForPush(promptsDir, files) {
3239
3178
  return { beforeFunctions, afterFunctions };
3240
3179
  }
3241
3180
  /**
3242
- * Every `prompts/<key>.tests/*.toml` sidecar whose prompt is an agent — #3798
3243
- * (§D1: prompt test cases are refused for the kind in v1). The admin test-case
3244
- * routes refuse the same case with `AGENT_TEST_CASE_NOT_SUPPORTED`; this says
3245
- * so before anything is applied.
3181
+ * The kind of the prompt a `prompts/<key>.tests/` sidecar belongs to, read
3182
+ * from the prompt's own file — `undefined` for another block type, or when the
3183
+ * file is absent or does not parse (the prompt leg reports that).
3184
+ */
3185
+ function testCaseOwnerPromptKind(configDir, blockType, blockKey) {
3186
+ if (blockType !== "prompt")
3187
+ return undefined;
3188
+ const owner = join(configDir, "prompts", `${blockKey}.toml`);
3189
+ if (!existsSync(owner))
3190
+ return undefined;
3191
+ try {
3192
+ return promptKindOf(parseTomlFile(owner)?.prompt);
3193
+ }
3194
+ catch {
3195
+ return undefined;
3196
+ }
3197
+ }
3198
+ /** A sidecar's agent block and `expectedJsonSubset`, in wire spelling. */
3199
+ function agentTestCaseWireFields(payload) {
3200
+ const fields = {};
3201
+ for (const key of AGENT_TEST_CASE_FIELDS)
3202
+ fields[key] = payload?.[key];
3203
+ fields.expectedJsonSubset = payload?.expectedJsonSubset;
3204
+ return fields;
3205
+ }
3206
+ /**
3207
+ * Every `prompts/<key>.tests/*.toml` sidecar the agent test case's rule
3208
+ * refuses — #4262 (#3798 refused every sidecar on an agent). An agent's case
3209
+ * is checked by the vendored shape rule (`resolveAgentTestCase`), which the
3210
+ * admin routes run too; a `[test.agent]` block on a chat or decisions
3211
+ * prompt's case is refused by name. One line per file, naming it. What needs
3212
+ * the app (the input, context and event schemas, the declared names) is the
3213
+ * routes' to decide at apply.
3246
3214
  */
3247
3215
  export function promptAgentSidecarErrors(configDir) {
3248
3216
  const promptsDir = join(configDir, "prompts");
@@ -3252,21 +3220,25 @@ export function promptAgentSidecarErrors(configDir) {
3252
3220
  if (!filePath.startsWith(promptsDir) || !parent.endsWith(".tests"))
3253
3221
  continue;
3254
3222
  const key = parent.slice(0, -".tests".length);
3255
- const owner = join(promptsDir, `${key}.toml`);
3256
- if (!existsSync(owner))
3223
+ const kind = testCaseOwnerPromptKind(configDir, "prompt", key);
3224
+ if (kind === undefined)
3257
3225
  continue;
3258
- let tomlData;
3226
+ let sidecar;
3259
3227
  try {
3260
- tomlData = parseTomlFile(owner);
3228
+ sidecar = parseTomlFile(filePath);
3261
3229
  }
3262
3230
  catch {
3231
+ // The test-case collector reports a file that does not parse.
3263
3232
  continue;
3264
3233
  }
3265
- if (promptKindOf(tomlData?.prompt) !== "agent")
3234
+ const resolved = resolveAgentTestCase({
3235
+ kind,
3236
+ fields: agentTestCaseWireFields(parseTestCaseToml(sidecar)),
3237
+ });
3238
+ if (!("refusals" in resolved))
3266
3239
  continue;
3267
- const promptKey = tomlData?.prompt?.key || key;
3268
3240
  errors.push(`${filePath.slice(configDir.length + 1)}: ` +
3269
- agentTestCaseRefusal(promptKey, "subject").message);
3241
+ resolved.refusals.map((refusal) => refusal.message).join("; "));
3270
3242
  }
3271
3243
  return errors;
3272
3244
  }
@@ -3339,18 +3311,12 @@ export function configFileValidationMessages(filePath, tomlData, surface, tables
3339
3311
  if (surface.label === "prompt") {
3340
3312
  messages.push(...promptKindFileErrors(filePath, tomlData));
3341
3313
  }
3342
- // #4116 — a bucket naming its rule set both ways.
3343
- if (surface.label === "blob-bucket") {
3344
- messages.push(...blobBucketFileErrors(filePath, tomlData));
3345
- }
3346
3314
  return messages;
3347
3315
  }
3348
3316
  /**
3349
3317
  * Whether `config pull` must rewrite a local file whatever it says (#4160):
3350
- * `config push` would refuse it, or it spells something in a deprecated form
3351
- * pull exists to migrate (a bucket naming its rule set by id). Keeping such a
3352
- * file because its VALUES match would leave the tree unpushable, or the
3353
- * deprecation unresolved, after a pull that reported success.
3318
+ * `config push` would refuse it. Keeping such a file because its VALUES match
3319
+ * would leave the tree unpushable after a pull that reported success.
3354
3320
  */
3355
3321
  export function pullMustRewrite(label, tomlData) {
3356
3322
  // A test-case sidecar is not one of the preflight's per-directory targets;
@@ -3365,7 +3331,7 @@ export function pullMustRewrite(label, tomlData) {
3365
3331
  return true;
3366
3332
  }
3367
3333
  }
3368
- return (label === "blob-bucket" && blobBucketDeprecatedKeyWarning("", tomlData) !== null);
3334
+ return false;
3369
3335
  }
3370
3336
  export function collectConfigFileValidationErrors(configDir) {
3371
3337
  const found = [];
@@ -3443,35 +3409,6 @@ export function collectFunctionTomlValidationErrors(configDir) {
3443
3409
  }
3444
3410
  return found;
3445
3411
  }
3446
- /**
3447
- * The retired runtime keys every `functions/*.toml` in the tree still carries
3448
- * — #3482, the warning half of the collector above.
3449
- *
3450
- * Separate from the errors because it IS separate: a file carrying one of
3451
- * these pushes, with every other key applied, so it is not a row `config diff`
3452
- * calls invalid. It is a line an author reads on stderr from both commands, so
3453
- * the two keep reading identically (the #2880 symmetry the errors collector
3454
- * exists for, applied to advice rather than refusals).
3455
- */
3456
- export function collectFunctionTomlIgnoredKeyWarnings(configDir) {
3457
- const found = [];
3458
- const functionsDir = join(configDir, "functions");
3459
- if (!existsSync(functionsDir))
3460
- return found;
3461
- for (const file of readdirSync(functionsDir).filter(isFunctionDeclarationFile)) {
3462
- const filePath = join(functionsDir, file);
3463
- let tomlData;
3464
- try {
3465
- tomlData = parseTomlFile(filePath);
3466
- }
3467
- catch {
3468
- continue;
3469
- }
3470
- const key = tomlData?.function?.key || basename(file, ".toml");
3471
- found.push(...functionTomlIgnoredKeyWarnings({ functionKey: key, tomlData }));
3472
- }
3473
- return found;
3474
- }
3475
3412
  /**
3476
3413
  * The collectors' rows, one per diff row (#2880 symmetry, #3320).
3477
3414
  *
@@ -5001,12 +4938,11 @@ const CONFIG_DIFF_SPECS = {
5001
4938
  keys.push("name");
5002
4939
  if (!bucket.preset)
5003
4940
  keys.push("preset");
5004
- // Either spelling manages the rule set (#4116) — read as the parse reads
5005
- // it, so a blank name is no reference rather than a difference the
5006
- // update cannot carry.
4941
+ // A name manages the rule set (#4116) — read as the parse reads it, so a
4942
+ // blank name is no reference rather than a difference the update cannot
4943
+ // carry. The retired id spelling (#4283) manages nothing.
5007
4944
  const named = typeof bucket.ruleSetName === "string" && bucket.ruleSetName.trim() !== "";
5008
- const legacy = typeof bucket.ruleSetId === "string" && bucket.ruleSetId !== "";
5009
- if (!named && !legacy)
4945
+ if (!named)
5010
4946
  keys.push("ruleSetId");
5011
4947
  return keys;
5012
4948
  },
@@ -5974,6 +5910,17 @@ function testCaseSidecarTable(testCase, lookupMaps, unresolvedSpelling) {
5974
5910
  expectedOutputPattern: (record) => record.expectedOutputPattern || "",
5975
5911
  expectedOutputContains: (record) => jsonFieldText(record.expectedOutputContains, "[]"),
5976
5912
  expectedJsonSubset: (record) => jsonFieldText(record.expectedJsonSubset, "{}"),
5913
+ // #4262 — `[test.agent]`, written only when a key is set, so a chat case's
5914
+ // sidecar is the one it always was. The history is the text the author
5915
+ // pushed; the two JSON fields come back parsed and are written as text.
5916
+ agentHistory: (record) => agentJsonText(record.agentHistory),
5917
+ agentContext: (record) => agentJsonText(record.agentContext),
5918
+ agentTimezone: (record) => record.agentTimezone || undefined,
5919
+ agentLocale: (record) => record.agentLocale || undefined,
5920
+ agentExpectedToolCall: (record) => agentJsonText(record.agentExpectedToolCall),
5921
+ agentExpectedNoToolCall: (record) => typeof record.agentExpectedNoToolCall === "boolean"
5922
+ ? record.agentExpectedNoToolCall
5923
+ : undefined,
5977
5924
  });
5978
5925
  return { test, unresolved };
5979
5926
  }
@@ -6028,6 +5975,38 @@ function jsonFieldText(value, empty) {
6028
5975
  return empty;
6029
5976
  }
6030
5977
  }
5978
+ /**
5979
+ * #4262 — an agent block field as the sidecar's JSON text, or `undefined`
5980
+ * (omitted) when unset: `[test.agent]` has no empty spellings.
5981
+ */
5982
+ function agentJsonText(value) {
5983
+ if (value === undefined || value === null)
5984
+ return undefined;
5985
+ if (typeof value === "string")
5986
+ return value === "" ? undefined : value;
5987
+ try {
5988
+ return JSON.stringify(value);
5989
+ }
5990
+ catch {
5991
+ return undefined;
5992
+ }
5993
+ }
5994
+ /**
5995
+ * #4262 — an agent block JSON field on the wire: parsed when the text parses,
5996
+ * the text itself otherwise (the shape rule names it), `undefined` when unset.
5997
+ */
5998
+ function agentJsonWireValue(value) {
5999
+ if (value === undefined || value === null || value === "")
6000
+ return undefined;
6001
+ if (typeof value !== "string")
6002
+ return value;
6003
+ try {
6004
+ return JSON.parse(value);
6005
+ }
6006
+ catch {
6007
+ return value;
6008
+ }
6009
+ }
6031
6010
  export function parseTestCaseToml(tomlData) {
6032
6011
  const test = tomlData.test || {};
6033
6012
  /** A JSON-string TOML value, or `undefined` when it is absent/the default. */
@@ -6068,6 +6047,15 @@ export function parseTestCaseToml(tomlData) {
6068
6047
  expectedOutputPattern: (value) => (value ? value : undefined),
6069
6048
  expectedOutputContains: (value) => parseJsonText(value, ["[]"]),
6070
6049
  expectedJsonSubset: (value) => parseJsonText(value, ["{}"]),
6050
+ // #4262 — `[test.agent]`. The history travels as the JSON text it is
6051
+ // authored as; context and expectedToolCall travel parsed; `false` is a
6052
+ // value, not an absence.
6053
+ agentHistory: (value) => (value === "" ? undefined : value),
6054
+ agentContext: (value) => agentJsonWireValue(value),
6055
+ agentTimezone: (value) => (value === "" ? undefined : value),
6056
+ agentLocale: (value) => (value === "" ? undefined : value),
6057
+ agentExpectedToolCall: (value) => agentJsonWireValue(value),
6058
+ agentExpectedNoToolCall: (value) => value,
6071
6059
  },
6072
6060
  });
6073
6061
  // An omitted field must be absent from the body, not present-and-undefined:
@@ -7293,6 +7281,15 @@ export async function pushTestCasesForBlock(params) {
7293
7281
  evaluatorPromptId: testPayload.evaluatorPromptId ?? null,
7294
7282
  evaluatorConfigId: testPayload.evaluatorConfigId ?? null,
7295
7283
  };
7284
+ // #4262 — an agent prompt's case carries its `[test.agent]` block as
7285
+ // full state too: a key the file no longer sets goes as `null`, so
7286
+ // removing an assertion clears it. A chat or decisions prompt's case
7287
+ // sends none, so its body is the one it always was.
7288
+ if (testCaseOwnerPromptKind(configDir, blockType, blockKey) === "agent") {
7289
+ for (const key of AGENT_TEST_CASE_FIELDS) {
7290
+ updatePayload[key] = testPayload[key] ?? null;
7291
+ }
7292
+ }
7296
7293
  // #2896 — the identity travels with the content: this is the lazy
7297
7294
  // migration of a legacy case and the second half of a rename.
7298
7295
  if (pendingKey !== undefined)
@@ -17467,18 +17464,11 @@ What a push guarantees (two stages):
17467
17464
  // verbatim, so the two commands read identically — replaces whatever
17468
17465
  // the comparison had to say about the file.
17469
17466
  // #3320 — the function checks join them. Same posture, same messages:
17470
- // a function file whose `entry`, mode, triggers or webhook
17471
- // signing-secret the push preflight refuses is not a row this command
17472
- // calls Synced, Modified or "local only — new".
17467
+ // a function file whose `entry`, retired runtime keys (#3188),
17468
+ // triggers or webhook signing-secret the push preflight refuses is not
17469
+ // a row this command calls Synced, Modified or "local only — new".
17473
17470
  // Merged first: a file both collectors reject keeps every message push
17474
17471
  // prints, rather than one collector's hint overwriting the other's.
17475
- // #3482 — the same advisory `config push` prints, so an author sees
17476
- // the retired key before they push rather than only during it. Not a
17477
- // row status: the file is pushable, and saying otherwise would make
17478
- // `config diff` disagree with the push it predicts.
17479
- for (const message of collectFunctionTomlIgnoredKeyWarnings(configDir)) {
17480
- warn(` ${message}`);
17481
- }
17482
17472
  for (const rejected of mergeConfigFileValidationErrors([
17483
17473
  ...collectConfigFileValidationErrors(configDir),
17484
17474
  ...collectFunctionTomlValidationErrors(configDir),