@tangle-network/agent-interface 0.38.0 → 0.40.0

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.
@@ -164,9 +164,9 @@ export declare const agentExecutionPreparationAxisResultSchema: z.ZodObject<{
164
164
  harness: "harness";
165
165
  confidential: "confidential";
166
166
  extensions: "extensions";
167
- instructions: "instructions";
168
167
  skills: "skills";
169
168
  commands: "commands";
169
+ instructions: "instructions";
170
170
  systemPrompt: "systemPrompt";
171
171
  modelDefault: "modelDefault";
172
172
  modelSmall: "modelSmall";
@@ -308,9 +308,9 @@ export declare const agentExecutionPreparationReceiptSchema: z.ZodObject<{
308
308
  harness: "harness";
309
309
  confidential: "confidential";
310
310
  extensions: "extensions";
311
- instructions: "instructions";
312
311
  skills: "skills";
313
312
  commands: "commands";
313
+ instructions: "instructions";
314
314
  systemPrompt: "systemPrompt";
315
315
  modelDefault: "modelDefault";
316
316
  modelSmall: "modelSmall";
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { canonicalCandidateDigest, canonicalCandidateJson, isCanonicalJsonValue, isWellFormedUnicode, looksLikeCredential, omitTopLevelDigest, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
3
+ import { canonicalAgentProfileValue } from "./agent-profile-canonical.js";
3
4
  import { REASONING_EFFORTS, } from "./agent-profile.js";
4
5
  import { AGENT_PROFILE_MATERIALIZATION_AXES, profileMaterializationRequests, } from "./agent-profile-materialization.js";
5
6
  import { harnessTypeSchema } from "./harness.js";
@@ -190,7 +191,7 @@ export const agentExecutionPreparationReceiptSchema = z
190
191
  */
191
192
  export function canonicalAgentProfileDigest(profile) {
192
193
  const parsed = agentProfileSchema.parse(profile);
193
- const material = canonicalProfileValue(parsed, [], new Set());
194
+ const material = canonicalAgentProfileValue(parsed);
194
195
  if (material === undefined || !isCanonicalJsonValue(material)) {
195
196
  throw new Error("AgentProfile must contain finite, acyclic RFC 8785 JSON values");
196
197
  }
@@ -660,64 +661,6 @@ function cleanResolvedModel(model) {
660
661
  }),
661
662
  };
662
663
  }
663
- function canonicalProfileValue(value, path, ancestors) {
664
- if (value === undefined)
665
- return undefined;
666
- if (value === null ||
667
- typeof value === "boolean" ||
668
- typeof value === "string") {
669
- return value;
670
- }
671
- if (typeof value === "number") {
672
- if (!Number.isFinite(value)) {
673
- throw new Error(`AgentProfile ${renderPath(path)} must be finite`);
674
- }
675
- return value;
676
- }
677
- if (typeof value !== "object") {
678
- throw new Error(`AgentProfile ${renderPath(path)} is not JSON serializable`);
679
- }
680
- if (ancestors.has(value)) {
681
- throw new Error(`AgentProfile ${renderPath(path)} must be acyclic`);
682
- }
683
- const prototype = Object.getPrototypeOf(value);
684
- if (!Array.isArray(value) && prototype !== Object.prototype && prototype !== null) {
685
- throw new Error(`AgentProfile ${renderPath(path)} must be a plain JSON object`);
686
- }
687
- const nextAncestors = new Set(ancestors).add(value);
688
- if (Array.isArray(value)) {
689
- return Array.from({ length: value.length }, (_, index) => {
690
- if (!Object.prototype.hasOwnProperty.call(value, index)) {
691
- throw new Error(`AgentProfile ${renderPath([...path, index])} cannot be a sparse array hole`);
692
- }
693
- const entry = value[index];
694
- const normalized = canonicalProfileValue(entry, [...path, index], nextAncestors);
695
- if (normalized === undefined) {
696
- throw new Error(`AgentProfile ${renderPath([...path, index])} cannot be undefined`);
697
- }
698
- return normalized;
699
- });
700
- }
701
- const material = Object.create(null);
702
- for (const [key, entry] of Object.entries(value)) {
703
- if (!isWellFormedUnicode(key)) {
704
- throw new Error(`AgentProfile ${renderPath(path)} has a record key that is not valid Unicode`);
705
- }
706
- const normalized = canonicalProfileValue(entry, [...path, key], nextAncestors);
707
- if (normalized !== undefined) {
708
- Object.defineProperty(material, key, {
709
- value: normalized,
710
- enumerable: true,
711
- configurable: true,
712
- writable: true,
713
- });
714
- }
715
- }
716
- return material;
717
- }
718
- function renderPath(path) {
719
- return path.length === 0 ? "root" : path.join(".");
720
- }
721
664
  function readJsonPointer(root, pointer) {
722
665
  let value = root;
723
666
  for (const encoded of pointer.slice(1).split("/")) {
@@ -0,0 +1,12 @@
1
+ import type { AgentCandidateJsonValue } from "./agent-candidate.js";
2
+ /**
3
+ * Normalize profile-shaped data into the canonical JSON domain used for public
4
+ * profile identity. Undefined object entries are omitted; unsupported values,
5
+ * sparse arrays, and cycles are rejected with their profile path.
6
+ *
7
+ * This module is intentionally internal. Public callers should use the
8
+ * profile-level operations that own validation, identity, or diff semantics.
9
+ */
10
+ export declare function canonicalAgentProfileValue(value: unknown): AgentCandidateJsonValue | undefined;
11
+ /** Canonical serialization after applying AgentProfile normalization rules. */
12
+ export declare function canonicalAgentProfileJson(value: unknown): string | undefined;
@@ -0,0 +1,79 @@
1
+ import { canonicalCandidateJson, isWellFormedUnicode, } from "./agent-candidate-schema-common.js";
2
+ /**
3
+ * Normalize profile-shaped data into the canonical JSON domain used for public
4
+ * profile identity. Undefined object entries are omitted; unsupported values,
5
+ * sparse arrays, and cycles are rejected with their profile path.
6
+ *
7
+ * This module is intentionally internal. Public callers should use the
8
+ * profile-level operations that own validation, identity, or diff semantics.
9
+ */
10
+ export function canonicalAgentProfileValue(value) {
11
+ return normalizeAgentProfileValue(value, [], new Set());
12
+ }
13
+ /** Canonical serialization after applying AgentProfile normalization rules. */
14
+ export function canonicalAgentProfileJson(value) {
15
+ const normalized = canonicalAgentProfileValue(value);
16
+ return normalized === undefined
17
+ ? undefined
18
+ : canonicalCandidateJson(normalized);
19
+ }
20
+ function normalizeAgentProfileValue(value, path, ancestors) {
21
+ if (value === undefined)
22
+ return undefined;
23
+ if (value === null ||
24
+ typeof value === "boolean" ||
25
+ typeof value === "string") {
26
+ return value;
27
+ }
28
+ if (typeof value === "number") {
29
+ if (!Number.isFinite(value)) {
30
+ throw new Error(`AgentProfile ${renderPath(path)} must be finite`);
31
+ }
32
+ return value;
33
+ }
34
+ if (typeof value !== "object") {
35
+ throw new Error(`AgentProfile ${renderPath(path)} is not JSON serializable`);
36
+ }
37
+ if (ancestors.has(value)) {
38
+ throw new Error(`AgentProfile ${renderPath(path)} must be acyclic`);
39
+ }
40
+ const prototype = Object.getPrototypeOf(value);
41
+ if (!Array.isArray(value) &&
42
+ prototype !== Object.prototype &&
43
+ prototype !== null) {
44
+ throw new Error(`AgentProfile ${renderPath(path)} must be a plain JSON object`);
45
+ }
46
+ const nextAncestors = new Set(ancestors).add(value);
47
+ if (Array.isArray(value)) {
48
+ return Array.from({ length: value.length }, (_, index) => {
49
+ if (!Object.prototype.hasOwnProperty.call(value, index)) {
50
+ throw new Error(`AgentProfile ${renderPath([...path, index])} cannot be a sparse array hole`);
51
+ }
52
+ const entry = value[index];
53
+ const normalized = normalizeAgentProfileValue(entry, [...path, index], nextAncestors);
54
+ if (normalized === undefined) {
55
+ throw new Error(`AgentProfile ${renderPath([...path, index])} cannot be undefined`);
56
+ }
57
+ return normalized;
58
+ });
59
+ }
60
+ const material = Object.create(null);
61
+ for (const [key, entry] of Object.entries(value)) {
62
+ if (!isWellFormedUnicode(key)) {
63
+ throw new Error(`AgentProfile ${renderPath(path)} has a record key that is not valid Unicode`);
64
+ }
65
+ const normalized = normalizeAgentProfileValue(entry, [...path, key], nextAncestors);
66
+ if (normalized !== undefined) {
67
+ Object.defineProperty(material, key, {
68
+ value: normalized,
69
+ enumerable: true,
70
+ configurable: true,
71
+ writable: true,
72
+ });
73
+ }
74
+ }
75
+ return material;
76
+ }
77
+ function renderPath(path) {
78
+ return path.length === 0 ? "root" : path.join(".");
79
+ }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import type { AgentImprovementSurface } from "./agent-candidate.js";
2
3
  import type { AgentProfileDiff } from "./profile-diff.js";
3
4
  export declare const agentProfileImprovementTaskSchema: z.ZodObject<{
4
5
  kind: z.ZodLiteral<"agent-profile-improvement-task">;
@@ -183,11 +184,7 @@ export declare const agentProfileImprovementExecutionRefSchema: z.ZodObject<{
183
184
  digest: z.ZodType<`sha256:${string}`, unknown, z.core.$ZodTypeInternals<`sha256:${string}`, unknown>>;
184
185
  kind: z.ZodLiteral<"agent-profile-improvement-execution-ref">;
185
186
  }, z.core.$strict>;
186
- /**
187
- * The first product path changes only prompt and skills, but it uses the
188
- * shared profile-diff language so execution and activation apply identical
189
- * ordered patches.
190
- */
187
+ /** One strict canonical profile patch in an ordered measured change. */
191
188
  export declare const agentProfileImprovementChangeStepSchema: z.ZodType<AgentProfileDiff, unknown, z.core.$ZodTypeInternals<AgentProfileDiff, unknown>>;
192
189
  export declare const agentProfileImprovementChangeSchema: z.ZodTuple<[z.ZodType<AgentProfileDiff, unknown, z.core.$ZodTypeInternals<AgentProfileDiff, unknown>>], z.ZodType<AgentProfileDiff, unknown, z.core.$ZodTypeInternals<AgentProfileDiff, unknown>>>;
193
190
  export declare const agentProfileImprovementExperimentSchema: z.ZodObject<{
@@ -1208,4 +1205,4 @@ export declare const agentProfileImprovementMeasuredComparisonSchema: z.ZodObjec
1208
1205
  }, z.core.$strict>;
1209
1206
  }, z.core.$strict>>;
1210
1207
  }, z.core.$strict>;
1211
- export declare function changedProfileImprovementSurfaces(change: readonly AgentProfileDiff[]): string[];
1208
+ export declare function changedProfileImprovementSurfaces(change: readonly AgentProfileDiff[]): AgentImprovementSurface[];
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { changedAgentProfileAxes } from "./profile-diff.js";
2
3
  import { agentCandidateLineageSchema } from "./agent-candidate-lineage-schema.js";
3
4
  import { canonicalCandidateDigest, isCanonicalJsonValue, omitTopLevelDigest, sha256DigestSchema, } from "./agent-candidate-schema-common.js";
4
5
  import { refineAgentExecutionWithinLimits } from "./agent-execution-limits.js";
@@ -103,67 +104,48 @@ export const agentProfileImprovementExecutionRefSchema = profileImprovementEvide
103
104
  kind: z.literal("agent-profile-improvement-execution-ref"),
104
105
  })
105
106
  .strict();
106
- /**
107
- * The first product path changes only prompt and skills, but it uses the
108
- * shared profile-diff language so execution and activation apply identical
109
- * ordered patches.
110
- */
107
+ /** One strict canonical profile patch in an ordered measured change. */
111
108
  export const agentProfileImprovementChangeStepSchema = agentProfileDiffSchema.superRefine((change, ctx) => {
112
109
  const changed = changedProfileImprovementSurfaces([change]);
113
110
  if (changed.length === 0) {
114
111
  ctx.addIssue({
115
112
  code: "custom",
116
- message: "profile improvement patch must change prompt or skills",
113
+ message: "profile improvement patch must change the agent profile",
117
114
  });
118
115
  }
119
- const setKeys = Object.keys(change.set ?? {});
120
- if (setKeys.some((key) => key !== "prompt" && key !== "resources")) {
121
- ctx.addIssue({
122
- code: "custom",
123
- path: ["set"],
124
- message: "profile improvement patches may set only prompt or skill resources",
125
- });
126
- }
127
- const setResourceKeys = Object.keys(change.set?.resources ?? {});
128
- if (setResourceKeys.some((key) => key !== "skills")) {
129
- ctx.addIssue({
130
- code: "custom",
131
- path: ["set", "resources"],
132
- message: "profile improvement patches may set only skill resources",
133
- });
134
- }
135
- if (change.set?.resources?.skills?.some((skill) => skill.kind !== "inline")) {
136
- ctx.addIssue({
137
- code: "custom",
138
- path: ["set", "resources", "skills"],
139
- message: "measured profile skill patches require inline content with exact bytes",
140
- });
141
- }
142
- const removeKeys = Object.keys(change.remove ?? {});
143
- if (removeKeys.some((key) => key !== "prompt" && key !== "resources")) {
144
- ctx.addIssue({
145
- code: "custom",
146
- path: ["remove"],
147
- message: "profile improvement patches may remove only prompt or skill resources",
148
- });
149
- }
150
- if (change.remove?.resources === true) {
151
- ctx.addIssue({
152
- code: "custom",
153
- path: ["remove", "resources"],
154
- message: "profile improvement patches may not remove unrelated resources",
155
- });
116
+ refineInlineProfileImprovementResources(change, ctx);
117
+ });
118
+ function refineInlineProfileImprovementResources(change, ctx) {
119
+ const resources = change.set?.resources;
120
+ if (resources === undefined)
121
+ return;
122
+ const groups = [
123
+ ["files", resources.files?.map((file) => file.resource)],
124
+ ["tools", resources.tools],
125
+ ["skills", resources.skills],
126
+ ["agents", resources.agents],
127
+ ["commands", resources.commands],
128
+ ];
129
+ for (const [name, refs] of groups) {
130
+ for (const [index, ref] of (refs ?? []).entries()) {
131
+ if (ref.kind !== "inline") {
132
+ ctx.addIssue({
133
+ code: "custom",
134
+ path: ["set", "resources", name, index],
135
+ message: "measured profile resource patches require inline content with exact bytes",
136
+ });
137
+ }
138
+ }
156
139
  }
157
- const removeResources = change.remove?.resources;
158
- const removeResourceKeys = Object.keys(typeof removeResources === "object" ? removeResources : {});
159
- if (removeResourceKeys.some((key) => key !== "skills")) {
140
+ if (typeof resources.instructions === "object" &&
141
+ resources.instructions.kind !== "inline") {
160
142
  ctx.addIssue({
161
143
  code: "custom",
162
- path: ["remove", "resources"],
163
- message: "profile improvement patches may remove only skill resources",
144
+ path: ["set", "resources", "instructions"],
145
+ message: "measured profile resource patches require inline content with exact bytes",
164
146
  });
165
147
  }
166
- });
148
+ }
167
149
  export const agentProfileImprovementChangeSchema = z
168
150
  .tuple([agentProfileImprovementChangeStepSchema])
169
151
  .rest(agentProfileImprovementChangeStepSchema)
@@ -420,16 +402,72 @@ export const agentProfileImprovementMeasuredComparisonSchema = z
420
402
  export function changedProfileImprovementSurfaces(change) {
421
403
  const surfaces = new Set();
422
404
  for (const step of change) {
423
- if (step.set?.prompt !== undefined || step.remove?.prompt !== undefined) {
424
- surfaces.add("prompt");
405
+ for (const axis of changedAgentProfileAxes(step)) {
406
+ if (axis === "prompt" ||
407
+ axis === "tools" ||
408
+ axis === "mcp" ||
409
+ axis === "hooks" ||
410
+ axis === "subagents") {
411
+ surfaces.add(axis);
412
+ }
413
+ else if (axis === "resources") {
414
+ const resources = changedResourceSurfaces(step);
415
+ for (const surface of resources.granular)
416
+ surfaces.add(surface);
417
+ if (resources.other)
418
+ surfaces.add("agent-profile");
419
+ }
420
+ else {
421
+ surfaces.add("agent-profile");
422
+ }
425
423
  }
426
- if (step.set?.resources?.skills !== undefined ||
427
- (typeof step.remove?.resources === "object" &&
428
- step.remove.resources.skills !== undefined)) {
429
- surfaces.add("skills");
424
+ }
425
+ const canonicalOrder = [
426
+ "prompt",
427
+ "skills",
428
+ "tools",
429
+ "mcp",
430
+ "hooks",
431
+ "subagents",
432
+ "agent-profile",
433
+ ];
434
+ return canonicalOrder.filter((surface) => surfaces.has(surface));
435
+ }
436
+ function changedResourceSurfaces(change) {
437
+ const granular = new Set();
438
+ let other = false;
439
+ const setResources = change.set?.resources;
440
+ if (setResources !== undefined) {
441
+ const keys = Object.keys(setResources);
442
+ for (const key of keys) {
443
+ if (key === "skills" || key === "tools")
444
+ granular.add(key);
445
+ else if (key === "agents")
446
+ granular.add("subagents");
447
+ else
448
+ other = true;
449
+ }
450
+ other ||= keys.length === 0;
451
+ }
452
+ const removeResources = change.remove?.resources;
453
+ if (removeResources === true) {
454
+ granular.add("skills");
455
+ granular.add("tools");
456
+ granular.add("subagents");
457
+ other = true;
458
+ }
459
+ else if (removeResources !== undefined) {
460
+ const entries = Object.entries(removeResources).filter(([, value]) => Array.isArray(value) ? value.length > 0 : value === true);
461
+ for (const [key] of entries) {
462
+ if (key === "skills" || key === "tools")
463
+ granular.add(key);
464
+ else if (key === "agents")
465
+ granular.add("subagents");
466
+ else
467
+ other = true;
430
468
  }
431
469
  }
432
- return [...surfaces].sort();
470
+ return { granular, other };
433
471
  }
434
472
  function refineProfileImprovementComparison(comparison, ctx) {
435
473
  const { suite, tasks } = comparison.experiment.benchmark;
@@ -52,9 +52,9 @@ export interface AgentProfileImprovementExecutionRef extends AgentProfileImprove
52
52
  * Ordered portable profile patches retained for review and activation.
53
53
  *
54
54
  * Applying the steps in order matters: a full resource replacement is a reset
55
- * followed by a replacement step. Prompt and inline skill content can be
56
- * sensitive, so the product that persists this value owns access control and
57
- * redaction.
55
+ * followed by a replacement step. Complete profile diffs can contain sensitive
56
+ * host-owned configuration, so the product that persists this value owns access
57
+ * control and redaction.
58
58
  */
59
59
  export type AgentProfileImprovementChange = [AgentProfileDiff, ...AgentProfileDiff[]];
60
60
  /** A measured comparison of two states of one host-owned agent profile. */
@@ -0,0 +1,9 @@
1
+ import type { AgentProfile } from "./agent-profile.js";
2
+ /**
3
+ * Detach, validate, and recursively freeze one AgentProfile at an intake boundary.
4
+ *
5
+ * The returned value is exactly the existing schema output: this function adds no
6
+ * defaults and applies no provider, model, or execution policy. Values outside the
7
+ * existing canonical profile JSON domain fail instead of becoming mutable state.
8
+ */
9
+ export declare function snapshotAgentProfile(value: unknown): AgentProfile;
@@ -0,0 +1,24 @@
1
+ import { canonicalAgentProfileValue } from "./agent-profile-canonical.js";
2
+ import { agentProfileSchema } from "./profile-schema.js";
3
+ /**
4
+ * Detach, validate, and recursively freeze one AgentProfile at an intake boundary.
5
+ *
6
+ * The returned value is exactly the existing schema output: this function adds no
7
+ * defaults and applies no provider, model, or execution policy. Values outside the
8
+ * existing canonical profile JSON domain fail instead of becoming mutable state.
9
+ */
10
+ export function snapshotAgentProfile(value) {
11
+ const parsed = agentProfileSchema.parse(structuredClone(value));
12
+ canonicalAgentProfileValue(parsed);
13
+ return deepFreeze(parsed);
14
+ }
15
+ function deepFreeze(value, seen = new Set()) {
16
+ if (value === null || typeof value !== "object" || seen.has(value)) {
17
+ return value;
18
+ }
19
+ seen.add(value);
20
+ for (const child of Object.values(value)) {
21
+ deepFreeze(child, seen);
22
+ }
23
+ return Object.freeze(value);
24
+ }
package/dist/index.d.ts CHANGED
@@ -623,6 +623,7 @@ export * from "./agent-profile-improvement.js";
623
623
  export * from "./agent-profile-improvement-schema.js";
624
624
  export * from "./agent-execution-limits.js";
625
625
  export * from "./agent-profile.js";
626
+ export * from "./agent-profile-snapshot.js";
626
627
  export * from "./agent-profile-activation.js";
627
628
  export * from "./agent-profile-materialization.js";
628
629
  export * from "./agent-execution-preparation.js";
package/dist/index.js CHANGED
@@ -135,6 +135,7 @@ export * from "./agent-profile-improvement.js";
135
135
  export * from "./agent-profile-improvement-schema.js";
136
136
  export * from "./agent-execution-limits.js";
137
137
  export * from "./agent-profile.js";
138
+ export * from "./agent-profile-snapshot.js";
138
139
  export * from "./agent-profile-activation.js";
139
140
  export * from "./agent-profile-materialization.js";
140
141
  export * from "./agent-execution-preparation.js";
@@ -57,6 +57,17 @@ export interface AgentProfileDiff {
57
57
  metadata?: Record<string, unknown>;
58
58
  }
59
59
  export declare function defineAgentProfileDiff<T extends AgentProfileDiff>(diff: T): T;
60
+ /**
61
+ * Construct deterministic ordered profile patches that reproduce `candidate`'s
62
+ * canonical profile value exactly when applied to `baseline`.
63
+ *
64
+ * Profile overlays append arrays and merge records, so changed fields are reset
65
+ * first and then replaced. Comparison and copied values follow the same
66
+ * undefined-entry normalization as profile identity. The returned values use
67
+ * the existing {@link AgentProfileDiff} contract; an unchanged profile returns
68
+ * no steps.
69
+ */
70
+ export declare function diffAgentProfiles(baseline: AgentProfile, candidate: AgentProfile): AgentProfileDiff[];
60
71
  export declare function applyAgentProfileDiff(base: AgentProfile, diff: AgentProfileDiff): AgentProfile;
61
72
  export declare function changedAgentProfileAxes(diff: AgentProfileDiff): AgentProfileDiffAxis[];
62
73
  export declare function pruneAgentProfileDiff(diff: AgentProfileDiff, axesToRemove: readonly AgentProfileDiffAxis[]): AgentProfileDiff;
@@ -1,4 +1,5 @@
1
1
  import { mergeAgentProfiles } from "./agent-profile.js";
2
+ import { canonicalAgentProfileJson } from "./agent-profile-canonical.js";
2
3
  const agentProfileDiffPropertyAxes = [
3
4
  "prompt",
4
5
  "model",
@@ -20,6 +21,110 @@ void _agentProfileDiffPropertyAxesAreExhaustive;
20
21
  export function defineAgentProfileDiff(diff) {
21
22
  return diff;
22
23
  }
24
+ const agentProfileResourceDiffPropertyAxes = [
25
+ "files",
26
+ "tools",
27
+ "skills",
28
+ "agents",
29
+ "commands",
30
+ "instructions",
31
+ "failOnError",
32
+ ];
33
+ const _agentProfileResourceDiffPropertyAxesAreExhaustive = true;
34
+ void _agentProfileResourceDiffPropertyAxesAreExhaustive;
35
+ /**
36
+ * Construct deterministic ordered profile patches that reproduce `candidate`'s
37
+ * canonical profile value exactly when applied to `baseline`.
38
+ *
39
+ * Profile overlays append arrays and merge records, so changed fields are reset
40
+ * first and then replaced. Comparison and copied values follow the same
41
+ * undefined-entry normalization as profile identity. The returned values use
42
+ * the existing {@link AgentProfileDiff} contract; an unchanged profile returns
43
+ * no steps.
44
+ */
45
+ export function diffAgentProfiles(baseline, candidate) {
46
+ const remove = {};
47
+ const set = {};
48
+ if (profileValuesDiffer(baseline.name, candidate.name) ||
49
+ profileValuesDiffer(baseline.description, candidate.description) ||
50
+ profileValuesDiffer(baseline.version, candidate.version)) {
51
+ remove.identity = true;
52
+ if (candidate.name !== undefined)
53
+ set.name = candidate.name;
54
+ if (candidate.description !== undefined) {
55
+ set.description = candidate.description;
56
+ }
57
+ if (candidate.version !== undefined)
58
+ set.version = candidate.version;
59
+ }
60
+ if (profileValuesDiffer(baseline.tags, candidate.tags)) {
61
+ remove.tags = true;
62
+ if (candidate.tags !== undefined) {
63
+ set.tags = canonicalProfileValue(candidate.tags);
64
+ }
65
+ }
66
+ for (const axis of agentProfileDiffPropertyAxes) {
67
+ if (axis === "resources") {
68
+ replaceChangedProfileResources(baseline.resources, candidate.resources, remove, set);
69
+ continue;
70
+ }
71
+ if (!profileValuesDiffer(baseline[axis], candidate[axis]))
72
+ continue;
73
+ Object.assign(remove, { [axis]: true });
74
+ const value = candidate[axis];
75
+ if (value !== undefined) {
76
+ Object.assign(set, { [axis]: canonicalProfileValue(value) });
77
+ }
78
+ }
79
+ if (Object.keys(remove).length === 0)
80
+ return [];
81
+ const reset = {
82
+ kind: "agent-profile-diff",
83
+ remove,
84
+ };
85
+ if (Object.keys(set).length === 0)
86
+ return [reset];
87
+ return [reset, { kind: "agent-profile-diff", set }];
88
+ }
89
+ function replaceChangedProfileResources(baseline, candidate, remove, set) {
90
+ if (!profileValuesDiffer(baseline, candidate))
91
+ return;
92
+ const resourceRemove = {};
93
+ const resourceSet = {};
94
+ let changedSubfields = 0;
95
+ for (const axis of agentProfileResourceDiffPropertyAxes) {
96
+ if (!profileValuesDiffer(baseline?.[axis], candidate?.[axis]))
97
+ continue;
98
+ changedSubfields += 1;
99
+ Object.assign(resourceRemove, { [axis]: true });
100
+ const value = candidate?.[axis];
101
+ if (value !== undefined) {
102
+ Object.assign(resourceSet, { [axis]: canonicalProfileValue(value) });
103
+ }
104
+ }
105
+ // Distinguish an absent resources object from an explicitly empty one.
106
+ if (changedSubfields === 0) {
107
+ remove.resources = true;
108
+ if (candidate !== undefined) {
109
+ set.resources = canonicalProfileValue(candidate);
110
+ }
111
+ return;
112
+ }
113
+ remove.resources = resourceRemove;
114
+ const canonicalCandidate = canonicalProfileValue(candidate);
115
+ if (Object.keys(resourceSet).length > 0 ||
116
+ (canonicalCandidate !== undefined &&
117
+ Object.keys(canonicalCandidate).length === 0)) {
118
+ set.resources = resourceSet;
119
+ }
120
+ }
121
+ function profileValuesDiffer(baseline, candidate) {
122
+ return (canonicalAgentProfileJson(baseline) !== canonicalAgentProfileJson(candidate));
123
+ }
124
+ function canonicalProfileValue(value) {
125
+ const json = canonicalAgentProfileJson(value);
126
+ return (json === undefined ? undefined : JSON.parse(json));
127
+ }
23
128
  function asMutable(value) {
24
129
  return value ? [...value] : undefined;
25
130
  }
@@ -174,12 +279,26 @@ export function applyAgentProfileDiff(base, diff) {
174
279
  const merged = mergeAgentProfiles(base, diff.set) ?? {};
175
280
  return applyRemoval(merged, diff.remove);
176
281
  }
282
+ function hasRemovalOperation(value) {
283
+ if (value === true)
284
+ return true;
285
+ if (Array.isArray(value))
286
+ return value.length > 0;
287
+ if (value && typeof value === "object") {
288
+ return Object.values(value).some(hasRemovalOperation);
289
+ }
290
+ return false;
291
+ }
177
292
  export function changedAgentProfileAxes(diff) {
178
293
  const axes = new Set();
179
294
  const set = diff.set;
180
295
  if (set) {
181
- if (set.name || set.description || set.version || set.tags)
296
+ if (set.name !== undefined ||
297
+ set.description !== undefined ||
298
+ set.version !== undefined ||
299
+ set.tags !== undefined) {
182
300
  axes.add("identity");
301
+ }
183
302
  for (const axis of agentProfileDiffPropertyAxes) {
184
303
  if (set[axis] !== undefined)
185
304
  axes.add(axis);
@@ -187,10 +306,11 @@ export function changedAgentProfileAxes(diff) {
187
306
  }
188
307
  const remove = diff.remove;
189
308
  if (remove) {
190
- if (remove.identity || remove.tags)
309
+ if (hasRemovalOperation(remove.identity) || hasRemovalOperation(remove.tags)) {
191
310
  axes.add("identity");
311
+ }
192
312
  for (const axis of agentProfileDiffPropertyAxes) {
193
- if (remove[axis] !== undefined)
313
+ if (hasRemovalOperation(remove[axis]))
194
314
  axes.add(axis);
195
315
  }
196
316
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-interface",
3
- "version": "0.38.0",
3
+ "version": "0.40.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",