@ai-agent-forge/plugin-observability 0.85.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.
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Host output serializer (inlined from the host's `src/host/output-serializer.ts`,
3
+ * D-075 S4 second batch). The observability plugin renders problem-report
4
+ * payloads through this redaction channel; the module is self-contained and
5
+ * pure, so the inline copy is behavior-identical. Sync obligation: keep
6
+ * byte-compatible with the host module (the host keeps its own copy for the
7
+ * serializer's other consumers).
8
+ */
9
+ import type { HostOutputSurfaceV1 } from "@agent-forge/plugin-sdk";
10
+ export interface HostOutputSerializerContextV1 {
11
+ readonly surface: HostOutputSurfaceV1;
12
+ readonly policyVersion: string;
13
+ readonly inspectorAuthorized: boolean;
14
+ }
15
+ export interface OutputArtifactRefV1 {
16
+ readonly sessionId: string;
17
+ readonly operationId: string;
18
+ readonly artifactId: string;
19
+ readonly kind: string;
20
+ readonly mediaType: string;
21
+ readonly sizeBytes?: number;
22
+ readonly truncated: boolean;
23
+ readonly createdAt: number;
24
+ }
25
+ export interface HostOutputProjectionV1 {
26
+ readonly text: string;
27
+ readonly artifacts?: readonly OutputArtifactRefV1[];
28
+ readonly redactions?: readonly string[];
29
+ readonly diagnostics?: readonly string[];
30
+ readonly rejected?: {
31
+ readonly code: "output_projection_rejected";
32
+ readonly reason: string;
33
+ };
34
+ }
35
+ export interface HostOutputSerializerV1 {
36
+ project(input: Readonly<Record<string, unknown>>, context: HostOutputSerializerContextV1): HostOutputProjectionV1;
37
+ }
38
+ export declare function createHostOutputSerializer(): HostOutputSerializerV1;
39
+ //# sourceMappingURL=output-serializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-serializer.d.ts","sourceRoot":"","sources":["../src/output-serializer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,MAAM,WAAW,6BAA6B;IAC7C,QAAQ,CAAC,OAAO,EAAE,mBAAmB,CAAC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,4BAA4B,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7F;AAoBD,MAAM,WAAW,sBAAsB;IACtC,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,6BAA6B,GAAG,sBAAsB,CAAC;CAClH;AA2CD,wBAAgB,0BAA0B,IAAI,sBAAsB,CA6EnE","sourcesContent":["/**\n * Host output serializer (inlined from the host's `src/host/output-serializer.ts`,\n * D-075 S4 second batch). The observability plugin renders problem-report\n * payloads through this redaction channel; the module is self-contained and\n * pure, so the inline copy is behavior-identical. Sync obligation: keep\n * byte-compatible with the host module (the host keeps its own copy for the\n * serializer's other consumers).\n */\nimport type { HostOutputSurfaceV1 } from \"@agent-forge/plugin-sdk\";\n\nexport interface HostOutputSerializerContextV1 {\n\treadonly surface: HostOutputSurfaceV1;\n\treadonly policyVersion: string;\n\treadonly inspectorAuthorized: boolean;\n}\n\nexport interface OutputArtifactRefV1 {\n\treadonly sessionId: string;\n\treadonly operationId: string;\n\treadonly artifactId: string;\n\treadonly kind: string;\n\treadonly mediaType: string;\n\treadonly sizeBytes?: number;\n\treadonly truncated: boolean;\n\treadonly createdAt: number;\n}\n\nexport interface HostOutputProjectionV1 {\n\treadonly text: string;\n\treadonly artifacts?: readonly OutputArtifactRefV1[];\n\treadonly redactions?: readonly string[];\n\treadonly diagnostics?: readonly string[];\n\treadonly rejected?: { readonly code: \"output_projection_rejected\"; readonly reason: string };\n}\n\n/** Field keys that may never reach tui/cli-text surfaces at any depth. */\nconst INTERNAL_FIELD_KEYS: readonly string[] = [\n\t\"memoryId\",\n\t\"memoryIds\",\n\t\"score\",\n\t\"scores\",\n\t\"ranking\",\n\t\"queryPlan\",\n\t\"budget\",\n\t\"budgets\",\n\t\"agentRole\",\n\t\"pluginRole\",\n\t\"lease\",\n\t\"leaseId\",\n\t\"purgeState\",\n\t\"rawSources\",\n];\n\nexport interface HostOutputSerializerV1 {\n\tproject(input: Readonly<Record<string, unknown>>, context: HostOutputSerializerContextV1): HostOutputProjectionV1;\n}\n\ninterface ArtifactLike {\n\treadonly sessionId?: unknown;\n\treadonly operationId?: unknown;\n\treadonly artifactId?: unknown;\n\treadonly kind?: unknown;\n\treadonly mediaType?: unknown;\n\treadonly sizeBytes?: unknown;\n\treadonly truncated?: unknown;\n\treadonly createdAt?: unknown;\n}\n\nfunction isArtifactLike(value: unknown): value is ArtifactLike {\n\treturn (\n\t\ttypeof value === \"object\" && value !== null && \"artifactId\" in value && \"sessionId\" in value && \"kind\" in value\n\t);\n}\n\nfunction toArtifactRef(value: ArtifactLike): OutputArtifactRefV1 | undefined {\n\tif (\n\t\ttypeof value.sessionId !== \"string\" ||\n\t\ttypeof value.operationId !== \"string\" ||\n\t\ttypeof value.artifactId !== \"string\" ||\n\t\ttypeof value.kind !== \"string\" ||\n\t\ttypeof value.mediaType !== \"string\" ||\n\t\ttypeof value.truncated !== \"boolean\" ||\n\t\ttypeof value.createdAt !== \"number\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tsessionId: value.sessionId,\n\t\toperationId: value.operationId,\n\t\tartifactId: value.artifactId,\n\t\tkind: value.kind,\n\t\tmediaType: value.mediaType,\n\t\t...(typeof value.sizeBytes === \"number\" ? { sizeBytes: value.sizeBytes } : {}),\n\t\ttruncated: value.truncated,\n\t\tcreatedAt: value.createdAt,\n\t};\n}\n\nexport function createHostOutputSerializer(): HostOutputSerializerV1 {\n\treturn {\n\t\tproject(input, context) {\n\t\t\tif (context.policyVersion.trim().length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\ttext: \"\",\n\t\t\t\t\tdiagnostics: [\"output_projection_rejected: policyVersion must be a non-empty string\"],\n\t\t\t\t\trejected: { code: \"output_projection_rejected\", reason: \"policyVersion must be a non-empty string\" },\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst redactions: string[] = [];\n\t\t\tconst diagnostics: string[] = [];\n\t\t\tconst artifacts: OutputArtifactRefV1[] = [];\n\t\t\tconst showInternal = context.surface === \"inspector\" && context.inspectorAuthorized;\n\t\t\t// §5.4 requires building the field allowlist FIRST — the renderer can\n\t\t\t// only ever see allowlisted facts, so no fallback path can leak.\n\t\t\tconst isAllowedKey = (key: string): boolean => !INTERNAL_FIELD_KEYS.includes(key) || showInternal;\n\n\t\t\tconst renderValue = (key: string, value: unknown, path: string): string => {\n\t\t\t\tif (!isAllowedKey(key)) {\n\t\t\t\t\tredactions.push(`${path}.${key}`);\n\t\t\t\t\treturn `[redacted:${key}]`;\n\t\t\t\t}\n\t\t\t\tif (isArtifactLike(value)) {\n\t\t\t\t\tconst ref = toArtifactRef(value);\n\t\t\t\t\tif (ref) {\n\t\t\t\t\t\tartifacts.push(ref);\n\t\t\t\t\t\treturn `[artifact:${ref.artifactId}]`;\n\t\t\t\t\t}\n\t\t\t\t\tdiagnostics.push(`output_projection_rejected: ${path}.${key} is a malformed artifact reference`);\n\t\t\t\t\treturn `[unrenderable:${key}]`;\n\t\t\t\t}\n\t\t\t\tif (typeof value === \"string\") return value;\n\t\t\t\tif (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n\t\t\t\tif (value === null) return \"null\";\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tconst rendered = value.map((item) => renderValue(key, item, path));\n\t\t\t\t\treturn `[${rendered.join(\", \")}]`;\n\t\t\t\t}\n\t\t\t\tif (typeof value === \"object\") {\n\t\t\t\t\tconst rendered = Object.entries(value)\n\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t([nestedKey, nestedValue]) =>\n\t\t\t\t\t\t\t\t`${nestedKey}:${renderValue(nestedKey, nestedValue, `${path}.${key}`)}`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.join(\", \");\n\t\t\t\t\treturn `{${rendered}}`;\n\t\t\t\t}\n\t\t\t\tdiagnostics.push(`output_projection_rejected: ${path}.${key} cannot be serialized`);\n\t\t\t\treturn `[unrenderable:${key}]`;\n\t\t\t};\n\n\t\t\tconst textParts: string[] = [];\n\t\t\tfor (const [key, value] of Object.entries(input)) {\n\t\t\t\tif (key === \"artifacts\" && Array.isArray(value)) {\n\t\t\t\t\tfor (const entry of value) {\n\t\t\t\t\t\tif (isArtifactLike(entry)) {\n\t\t\t\t\t\t\tconst ref = toArtifactRef(entry);\n\t\t\t\t\t\t\tif (ref) artifacts.push(ref);\n\t\t\t\t\t\t\telse diagnostics.push(\"output_projection_rejected: artifacts entry is malformed\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdiagnostics.push(\"output_projection_rejected: artifacts entry is malformed\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\ttextParts.push(`${key}=${renderValue(key, value, \"root\")}`);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\ttext: textParts.join(\"\\n\"),\n\t\t\t\t...(artifacts.length > 0 ? { artifacts: Object.freeze(artifacts) } : {}),\n\t\t\t\t...(redactions.length > 0 ? { redactions: Object.freeze([...redactions]) } : {}),\n\t\t\t\t...(diagnostics.length > 0 ? { diagnostics: Object.freeze([...diagnostics]) } : {}),\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
@@ -0,0 +1,120 @@
1
+ /** Field keys that may never reach tui/cli-text surfaces at any depth. */
2
+ const INTERNAL_FIELD_KEYS = [
3
+ "memoryId",
4
+ "memoryIds",
5
+ "score",
6
+ "scores",
7
+ "ranking",
8
+ "queryPlan",
9
+ "budget",
10
+ "budgets",
11
+ "agentRole",
12
+ "pluginRole",
13
+ "lease",
14
+ "leaseId",
15
+ "purgeState",
16
+ "rawSources",
17
+ ];
18
+ function isArtifactLike(value) {
19
+ return (typeof value === "object" && value !== null && "artifactId" in value && "sessionId" in value && "kind" in value);
20
+ }
21
+ function toArtifactRef(value) {
22
+ if (typeof value.sessionId !== "string" ||
23
+ typeof value.operationId !== "string" ||
24
+ typeof value.artifactId !== "string" ||
25
+ typeof value.kind !== "string" ||
26
+ typeof value.mediaType !== "string" ||
27
+ typeof value.truncated !== "boolean" ||
28
+ typeof value.createdAt !== "number") {
29
+ return undefined;
30
+ }
31
+ return {
32
+ sessionId: value.sessionId,
33
+ operationId: value.operationId,
34
+ artifactId: value.artifactId,
35
+ kind: value.kind,
36
+ mediaType: value.mediaType,
37
+ ...(typeof value.sizeBytes === "number" ? { sizeBytes: value.sizeBytes } : {}),
38
+ truncated: value.truncated,
39
+ createdAt: value.createdAt,
40
+ };
41
+ }
42
+ export function createHostOutputSerializer() {
43
+ return {
44
+ project(input, context) {
45
+ if (context.policyVersion.trim().length === 0) {
46
+ return {
47
+ text: "",
48
+ diagnostics: ["output_projection_rejected: policyVersion must be a non-empty string"],
49
+ rejected: { code: "output_projection_rejected", reason: "policyVersion must be a non-empty string" },
50
+ };
51
+ }
52
+ const redactions = [];
53
+ const diagnostics = [];
54
+ const artifacts = [];
55
+ const showInternal = context.surface === "inspector" && context.inspectorAuthorized;
56
+ // §5.4 requires building the field allowlist FIRST — the renderer can
57
+ // only ever see allowlisted facts, so no fallback path can leak.
58
+ const isAllowedKey = (key) => !INTERNAL_FIELD_KEYS.includes(key) || showInternal;
59
+ const renderValue = (key, value, path) => {
60
+ if (!isAllowedKey(key)) {
61
+ redactions.push(`${path}.${key}`);
62
+ return `[redacted:${key}]`;
63
+ }
64
+ if (isArtifactLike(value)) {
65
+ const ref = toArtifactRef(value);
66
+ if (ref) {
67
+ artifacts.push(ref);
68
+ return `[artifact:${ref.artifactId}]`;
69
+ }
70
+ diagnostics.push(`output_projection_rejected: ${path}.${key} is a malformed artifact reference`);
71
+ return `[unrenderable:${key}]`;
72
+ }
73
+ if (typeof value === "string")
74
+ return value;
75
+ if (typeof value === "number" || typeof value === "boolean")
76
+ return String(value);
77
+ if (value === null)
78
+ return "null";
79
+ if (Array.isArray(value)) {
80
+ const rendered = value.map((item) => renderValue(key, item, path));
81
+ return `[${rendered.join(", ")}]`;
82
+ }
83
+ if (typeof value === "object") {
84
+ const rendered = Object.entries(value)
85
+ .map(([nestedKey, nestedValue]) => `${nestedKey}:${renderValue(nestedKey, nestedValue, `${path}.${key}`)}`)
86
+ .join(", ");
87
+ return `{${rendered}}`;
88
+ }
89
+ diagnostics.push(`output_projection_rejected: ${path}.${key} cannot be serialized`);
90
+ return `[unrenderable:${key}]`;
91
+ };
92
+ const textParts = [];
93
+ for (const [key, value] of Object.entries(input)) {
94
+ if (key === "artifacts" && Array.isArray(value)) {
95
+ for (const entry of value) {
96
+ if (isArtifactLike(entry)) {
97
+ const ref = toArtifactRef(entry);
98
+ if (ref)
99
+ artifacts.push(ref);
100
+ else
101
+ diagnostics.push("output_projection_rejected: artifacts entry is malformed");
102
+ }
103
+ else {
104
+ diagnostics.push("output_projection_rejected: artifacts entry is malformed");
105
+ }
106
+ }
107
+ continue;
108
+ }
109
+ textParts.push(`${key}=${renderValue(key, value, "root")}`);
110
+ }
111
+ return {
112
+ text: textParts.join("\n"),
113
+ ...(artifacts.length > 0 ? { artifacts: Object.freeze(artifacts) } : {}),
114
+ ...(redactions.length > 0 ? { redactions: Object.freeze([...redactions]) } : {}),
115
+ ...(diagnostics.length > 0 ? { diagnostics: Object.freeze([...diagnostics]) } : {}),
116
+ };
117
+ },
118
+ };
119
+ }
120
+ //# sourceMappingURL=output-serializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-serializer.js","sourceRoot":"","sources":["../src/output-serializer.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Host output serializer (inlined from the host's `src/host/output-serializer.ts`,\n * D-075 S4 second batch). The observability plugin renders problem-report\n * payloads through this redaction channel; the module is self-contained and\n * pure, so the inline copy is behavior-identical. Sync obligation: keep\n * byte-compatible with the host module (the host keeps its own copy for the\n * serializer's other consumers).\n */\nimport type { HostOutputSurfaceV1 } from \"@agent-forge/plugin-sdk\";\n\nexport interface HostOutputSerializerContextV1 {\n\treadonly surface: HostOutputSurfaceV1;\n\treadonly policyVersion: string;\n\treadonly inspectorAuthorized: boolean;\n}\n\nexport interface OutputArtifactRefV1 {\n\treadonly sessionId: string;\n\treadonly operationId: string;\n\treadonly artifactId: string;\n\treadonly kind: string;\n\treadonly mediaType: string;\n\treadonly sizeBytes?: number;\n\treadonly truncated: boolean;\n\treadonly createdAt: number;\n}\n\nexport interface HostOutputProjectionV1 {\n\treadonly text: string;\n\treadonly artifacts?: readonly OutputArtifactRefV1[];\n\treadonly redactions?: readonly string[];\n\treadonly diagnostics?: readonly string[];\n\treadonly rejected?: { readonly code: \"output_projection_rejected\"; readonly reason: string };\n}\n\n/** Field keys that may never reach tui/cli-text surfaces at any depth. */\nconst INTERNAL_FIELD_KEYS: readonly string[] = [\n\t\"memoryId\",\n\t\"memoryIds\",\n\t\"score\",\n\t\"scores\",\n\t\"ranking\",\n\t\"queryPlan\",\n\t\"budget\",\n\t\"budgets\",\n\t\"agentRole\",\n\t\"pluginRole\",\n\t\"lease\",\n\t\"leaseId\",\n\t\"purgeState\",\n\t\"rawSources\",\n];\n\nexport interface HostOutputSerializerV1 {\n\tproject(input: Readonly<Record<string, unknown>>, context: HostOutputSerializerContextV1): HostOutputProjectionV1;\n}\n\ninterface ArtifactLike {\n\treadonly sessionId?: unknown;\n\treadonly operationId?: unknown;\n\treadonly artifactId?: unknown;\n\treadonly kind?: unknown;\n\treadonly mediaType?: unknown;\n\treadonly sizeBytes?: unknown;\n\treadonly truncated?: unknown;\n\treadonly createdAt?: unknown;\n}\n\nfunction isArtifactLike(value: unknown): value is ArtifactLike {\n\treturn (\n\t\ttypeof value === \"object\" && value !== null && \"artifactId\" in value && \"sessionId\" in value && \"kind\" in value\n\t);\n}\n\nfunction toArtifactRef(value: ArtifactLike): OutputArtifactRefV1 | undefined {\n\tif (\n\t\ttypeof value.sessionId !== \"string\" ||\n\t\ttypeof value.operationId !== \"string\" ||\n\t\ttypeof value.artifactId !== \"string\" ||\n\t\ttypeof value.kind !== \"string\" ||\n\t\ttypeof value.mediaType !== \"string\" ||\n\t\ttypeof value.truncated !== \"boolean\" ||\n\t\ttypeof value.createdAt !== \"number\"\n\t) {\n\t\treturn undefined;\n\t}\n\treturn {\n\t\tsessionId: value.sessionId,\n\t\toperationId: value.operationId,\n\t\tartifactId: value.artifactId,\n\t\tkind: value.kind,\n\t\tmediaType: value.mediaType,\n\t\t...(typeof value.sizeBytes === \"number\" ? { sizeBytes: value.sizeBytes } : {}),\n\t\ttruncated: value.truncated,\n\t\tcreatedAt: value.createdAt,\n\t};\n}\n\nexport function createHostOutputSerializer(): HostOutputSerializerV1 {\n\treturn {\n\t\tproject(input, context) {\n\t\t\tif (context.policyVersion.trim().length === 0) {\n\t\t\t\treturn {\n\t\t\t\t\ttext: \"\",\n\t\t\t\t\tdiagnostics: [\"output_projection_rejected: policyVersion must be a non-empty string\"],\n\t\t\t\t\trejected: { code: \"output_projection_rejected\", reason: \"policyVersion must be a non-empty string\" },\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst redactions: string[] = [];\n\t\t\tconst diagnostics: string[] = [];\n\t\t\tconst artifacts: OutputArtifactRefV1[] = [];\n\t\t\tconst showInternal = context.surface === \"inspector\" && context.inspectorAuthorized;\n\t\t\t// §5.4 requires building the field allowlist FIRST — the renderer can\n\t\t\t// only ever see allowlisted facts, so no fallback path can leak.\n\t\t\tconst isAllowedKey = (key: string): boolean => !INTERNAL_FIELD_KEYS.includes(key) || showInternal;\n\n\t\t\tconst renderValue = (key: string, value: unknown, path: string): string => {\n\t\t\t\tif (!isAllowedKey(key)) {\n\t\t\t\t\tredactions.push(`${path}.${key}`);\n\t\t\t\t\treturn `[redacted:${key}]`;\n\t\t\t\t}\n\t\t\t\tif (isArtifactLike(value)) {\n\t\t\t\t\tconst ref = toArtifactRef(value);\n\t\t\t\t\tif (ref) {\n\t\t\t\t\t\tartifacts.push(ref);\n\t\t\t\t\t\treturn `[artifact:${ref.artifactId}]`;\n\t\t\t\t\t}\n\t\t\t\t\tdiagnostics.push(`output_projection_rejected: ${path}.${key} is a malformed artifact reference`);\n\t\t\t\t\treturn `[unrenderable:${key}]`;\n\t\t\t\t}\n\t\t\t\tif (typeof value === \"string\") return value;\n\t\t\t\tif (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n\t\t\t\tif (value === null) return \"null\";\n\t\t\t\tif (Array.isArray(value)) {\n\t\t\t\t\tconst rendered = value.map((item) => renderValue(key, item, path));\n\t\t\t\t\treturn `[${rendered.join(\", \")}]`;\n\t\t\t\t}\n\t\t\t\tif (typeof value === \"object\") {\n\t\t\t\t\tconst rendered = Object.entries(value)\n\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t([nestedKey, nestedValue]) =>\n\t\t\t\t\t\t\t\t`${nestedKey}:${renderValue(nestedKey, nestedValue, `${path}.${key}`)}`,\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.join(\", \");\n\t\t\t\t\treturn `{${rendered}}`;\n\t\t\t\t}\n\t\t\t\tdiagnostics.push(`output_projection_rejected: ${path}.${key} cannot be serialized`);\n\t\t\t\treturn `[unrenderable:${key}]`;\n\t\t\t};\n\n\t\t\tconst textParts: string[] = [];\n\t\t\tfor (const [key, value] of Object.entries(input)) {\n\t\t\t\tif (key === \"artifacts\" && Array.isArray(value)) {\n\t\t\t\t\tfor (const entry of value) {\n\t\t\t\t\t\tif (isArtifactLike(entry)) {\n\t\t\t\t\t\t\tconst ref = toArtifactRef(entry);\n\t\t\t\t\t\t\tif (ref) artifacts.push(ref);\n\t\t\t\t\t\t\telse diagnostics.push(\"output_projection_rejected: artifacts entry is malformed\");\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdiagnostics.push(\"output_projection_rejected: artifacts entry is malformed\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\ttextParts.push(`${key}=${renderValue(key, value, \"root\")}`);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\ttext: textParts.join(\"\\n\"),\n\t\t\t\t...(artifacts.length > 0 ? { artifacts: Object.freeze(artifacts) } : {}),\n\t\t\t\t...(redactions.length > 0 ? { redactions: Object.freeze([...redactions]) } : {}),\n\t\t\t\t...(diagnostics.length > 0 ? { diagnostics: Object.freeze([...diagnostics]) } : {}),\n\t\t\t};\n\t\t},\n\t};\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@ai-agent-forge/plugin-observability",
3
+ "version": "0.85.0",
4
+ "description": "Observability plugin for Agent Forge (event journal, problem reports, session trace sink, OTLP export)",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "agent-forge.json",
17
+ "plugin.json",
18
+ "README.md"
19
+ ],
20
+ "scripts": {
21
+ "clean": "shx rm -rf dist",
22
+ "build": "tsgo -p tsconfig.build.json",
23
+ "test": "vitest --run",
24
+ "prepublishOnly": "npm run clean && npm run build"
25
+ },
26
+ "keywords": [
27
+ "agent-forge",
28
+ "plugin",
29
+ "observability"
30
+ ],
31
+ "author": "Agent Forge contributors",
32
+ "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/agent-forge/agent-forge.git",
36
+ "directory": "plugins/observability"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/agent-forge/agent-forge/issues"
40
+ },
41
+ "homepage": "https://github.com/agent-forge/agent-forge#readme",
42
+ "engines": {
43
+ "node": ">=22.19.0"
44
+ },
45
+ "dependencies": {
46
+ "@agent-forge/plugin-sdk": "^0.85.0"
47
+ },
48
+ "devDependencies": {
49
+ "@agent-forge/agent-forge": "^0.85.0",
50
+ "shx": "0.4.0",
51
+ "vitest": "4.1.9"
52
+ }
53
+ }
package/plugin.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "id": "agent-forge.plugin.observability",
3
+ "version": "0.84.4",
4
+ "apiVersion": "1-draft",
5
+ "entry": "./dist/entry.js",
6
+ "hostVersion": ">=0.84.4",
7
+ "requiredCapabilities": ["events"],
8
+ "optionalCapabilities": ["output-artifacts", "logger-v1"]
9
+ }