@helmetfire-labs/cartridge-common 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,130 @@
1
+ # PolyForm Noncommercial License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree
8
+ to them as both strict obligations and conditions to all your
9
+ licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the software
14
+ to do everything you might do with the software that would
15
+ otherwise infringe the licensor's copyright in it for any
16
+ permitted purpose. However, you may only distribute the
17
+ software according to [Distribution License](#distribution-license) and make
18
+ changes or new works based on the software according to
19
+ [Changes and New Works License](#changes-and-new-works-license).
20
+
21
+ ## Distribution License
22
+
23
+ The licensor grants you an additional copyright license to
24
+ distribute copies of the software. Your license to distribute
25
+ covers distributing the software with changes and new works
26
+ permitted by [Changes and New Works License](#changes-and-new-works-license).
27
+
28
+ ## Notices
29
+
30
+ You must ensure that anyone who gets a copy of any part of
31
+ the software from you also gets a copy of these terms or the
32
+ URL for them above, as well as copies of any plain-text lines
33
+ beginning with `Required Notice:` that the licensor provided
34
+ with the software. For example:
35
+
36
+ > Required Notice: Copyright Kevin Kruusi (https://github.com/k-kruusi)
37
+
38
+ ## Changes and New Works License
39
+
40
+ The licensor grants you an additional copyright license to
41
+ make changes and new works based on the software for any
42
+ permitted purpose.
43
+
44
+ ## Patent License
45
+
46
+ The licensor grants you a patent license for the software
47
+ that covers patent claims the licensor can license, or
48
+ becomes able to license, that you would infringe by using
49
+ the software.
50
+
51
+ ## Noncommercial Purposes
52
+
53
+ Any noncommercial purpose is a **permitted purpose**.
54
+
55
+ ## Personal Uses
56
+
57
+ Personal use for research, experiment, and testing for the
58
+ benefit of public knowledge, personal study, private
59
+ entertainment, hobby projects, amateur pursuits, or religious
60
+ observance, without any anticipated commercial application,
61
+ is use for a permitted purpose.
62
+
63
+ ## Noncommercial Organizations
64
+
65
+ Use by any charitable organization, educational institution,
66
+ public research organization, public safety or health
67
+ organization, environmental protection organization, or
68
+ government institution is use for a permitted purpose
69
+ regardless of the source of funding or obligations resulting
70
+ from the funding.
71
+
72
+ ## Fair Use
73
+
74
+ You may have "fair use" rights for the software under the
75
+ law. These terms do not limit them.
76
+
77
+ ## No Other Rights
78
+
79
+ These terms do not allow you to sublicense or transfer any
80
+ of your licenses to anyone else, or prevent the licensor
81
+ from granting licenses to anyone else. These terms do not
82
+ imply any other licenses.
83
+
84
+ ## Patent Defense
85
+
86
+ If you make any written claim that the software infringes or
87
+ contributes to infringement of any patent, your patent license
88
+ for the software granted under these terms ends immediately. If
89
+ your company makes such a claim, your patent license ends
90
+ immediately for work on behalf of your company.
91
+
92
+ ## Violations
93
+
94
+ The first time you are notified in writing that you have
95
+ violated any of these terms, or done anything with the software
96
+ not covered by your licenses, your licenses can nonetheless
97
+ continue if you come into full compliance with these terms, and
98
+ take practical steps to correct past violations, within 32 days
99
+ of receiving notice. Otherwise, all your licenses end
100
+ immediately.
101
+
102
+ ## No Liability
103
+
104
+ ***As far as the law allows, the software comes as is, without
105
+ any warranty or condition, and the licensor will not be liable
106
+ to you for any damages arising out of these terms or the use
107
+ or nature of the software, under any kind of legal claim.***
108
+
109
+ ## Definitions
110
+
111
+ The **licensor** is the individual or entity offering these
112
+ terms, and the **software** is the software the licensor makes
113
+ available under these terms.
114
+
115
+ **You** refers to the individual or entity agreeing to these
116
+ terms.
117
+
118
+ **Your company** is any legal entity, sole proprietorship, or
119
+ other kind of organization that you work for, plus all
120
+ organizations that have control over, are under the control of,
121
+ or are under common control with that organization. **Control**
122
+ means ownership of substantially all the assets of an entity,
123
+ or the power to direct its management and policies by vote,
124
+ contract, or otherwise. Control can be direct or indirect.
125
+
126
+ **Your licenses** are all the licenses granted to you for the
127
+ software under these terms.
128
+
129
+ **Use** means anything you do with the software requiring one
130
+ of your licenses.
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Canonical action verbs for infrastructure cartridge log events.
3
+ * Using these consistently makes logs pattern-matchable across cartridges.
4
+ */
5
+ export declare const ACTIONS: {
6
+ readonly recv: "recv";
7
+ readonly forward: "forward";
8
+ readonly startup: "startup";
9
+ readonly shutdown: "shutdown";
10
+ readonly disconnect: "disconnect";
11
+ readonly acquire: "acquire";
12
+ readonly release: "release";
13
+ readonly queued: "queued";
14
+ readonly ok: "ok";
15
+ readonly error: "error";
16
+ readonly timeout: "timeout";
17
+ readonly blocked: "blocked";
18
+ readonly hit: "hit";
19
+ readonly miss: "miss";
20
+ readonly evict: "evict";
21
+ readonly OPEN: "OPEN";
22
+ readonly CLOSED: "CLOSED";
23
+ readonly HALF_OPEN: "HALF_OPEN";
24
+ readonly retry: "retry";
25
+ readonly exhausted: "exhausted";
26
+ readonly inject: "inject";
27
+ readonly refresh: "refresh";
28
+ readonly rewrite: "rewrite";
29
+ readonly redact: "redact";
30
+ readonly pending: "pending";
31
+ readonly approved: "approved";
32
+ readonly rejected: "rejected";
33
+ };
34
+ export type ActionKey = keyof typeof ACTIONS;
35
+ export type ActionValue = (typeof ACTIONS)[ActionKey];
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Canonical action verbs for infrastructure cartridge log events.
3
+ * Using these consistently makes logs pattern-matchable across cartridges.
4
+ */
5
+ export const ACTIONS = {
6
+ // Lifecycle
7
+ recv: 'recv',
8
+ forward: 'forward',
9
+ startup: 'startup',
10
+ shutdown: 'shutdown',
11
+ disconnect: 'disconnect',
12
+ // Locking
13
+ acquire: 'acquire',
14
+ release: 'release',
15
+ queued: 'queued',
16
+ // Outcomes
17
+ ok: 'ok',
18
+ error: 'error',
19
+ timeout: 'timeout',
20
+ blocked: 'blocked',
21
+ // Cache
22
+ hit: 'hit',
23
+ miss: 'miss',
24
+ evict: 'evict',
25
+ // Circuit breaker
26
+ OPEN: 'OPEN',
27
+ CLOSED: 'CLOSED',
28
+ HALF_OPEN: 'HALF_OPEN',
29
+ // Retry
30
+ retry: 'retry',
31
+ exhausted: 'exhausted',
32
+ // Auth
33
+ inject: 'inject',
34
+ refresh: 'refresh',
35
+ // Transform
36
+ rewrite: 'rewrite',
37
+ redact: 'redact',
38
+ // Approval
39
+ pending: 'pending',
40
+ approved: 'approved',
41
+ rejected: 'rejected',
42
+ };
43
+ //# sourceMappingURL=actions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../src/actions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,OAAO,GAAG;IACrB,YAAY;IACZ,IAAI,EAAE,MAAM;IACZ,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,UAAU;IACpB,UAAU,EAAE,YAAY;IAExB,UAAU;IACV,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAEhB,WAAW;IACX,EAAE,EAAE,IAAI;IACR,KAAK,EAAE,OAAO;IACd,OAAO,EAAE,SAAS;IAClB,OAAO,EAAE,SAAS;IAElB,QAAQ;IACR,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IAEd,kBAAkB;IAClB,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,SAAS,EAAE,WAAW;IAEtB,QAAQ;IACR,KAAK,EAAE,OAAO;IACd,SAAS,EAAE,WAAW;IAEtB,OAAO;IACP,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,SAAS;IAElB,YAAY;IACZ,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAEhB,WAAW;IACX,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;CACZ,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Error boundary wrapper and classification for infrastructure cartridges.
3
+ *
4
+ * Catches errors from upstream MCP calls, classifies them into a standard
5
+ * shape, logs them in terse format, and re-throws so callers can handle.
6
+ */
7
+ /**
8
+ * Standard error shapes for common failure modes.
9
+ * Used as log shapes — values are [placeholders] for pattern matching.
10
+ */
11
+ export declare const ERROR_SHAPES: {
12
+ readonly connectionRefused: "error: upstream [cartridge] unreachable";
13
+ readonly timeout: "timeout: [tool] exceeded [N]ms";
14
+ readonly toolNotFound: "error: tool [tool] not found on [cartridge]";
15
+ readonly authFailed: "error: authentication failed on [cartridge]";
16
+ readonly malformedResponse: "error: malformed response from [cartridge]";
17
+ readonly queueFull: "error: queue full on [tool]";
18
+ readonly lockTimeout: "timeout: acquire TTL expired on [tool]";
19
+ };
20
+ export type ErrorShapeKey = keyof typeof ERROR_SHAPES;
21
+ /**
22
+ * Classify an error into a standard shape.
23
+ * Falls back to a generic extracted shape if no pattern matches.
24
+ */
25
+ export declare function classifyError(err: Error | string): string;
26
+ /**
27
+ * Wrap a function call with an error boundary that:
28
+ * 1. Catches any thrown error
29
+ * 2. Classifies it into a standard shape
30
+ * 3. Logs in terse format using logEvent()
31
+ * 4. Re-throws the original error
32
+ */
33
+ export declare function withErrorBoundary<T>(opts: {
34
+ trace: string;
35
+ tag: string;
36
+ tool: string;
37
+ fn: () => Promise<T>;
38
+ }): Promise<T>;
package/dist/errors.js ADDED
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Error boundary wrapper and classification for infrastructure cartridges.
3
+ *
4
+ * Catches errors from upstream MCP calls, classifies them into a standard
5
+ * shape, logs them in terse format, and re-throws so callers can handle.
6
+ */
7
+ import { logEvent } from './logger.js';
8
+ import { extractShape } from './shape.js';
9
+ /**
10
+ * Standard error shapes for common failure modes.
11
+ * Used as log shapes — values are [placeholders] for pattern matching.
12
+ */
13
+ export const ERROR_SHAPES = {
14
+ connectionRefused: 'error: upstream [cartridge] unreachable',
15
+ timeout: 'timeout: [tool] exceeded [N]ms',
16
+ toolNotFound: 'error: tool [tool] not found on [cartridge]',
17
+ authFailed: 'error: authentication failed on [cartridge]',
18
+ malformedResponse: 'error: malformed response from [cartridge]',
19
+ queueFull: 'error: queue full on [tool]',
20
+ lockTimeout: 'timeout: acquire TTL expired on [tool]',
21
+ };
22
+ /**
23
+ * Classify an error into a standard shape.
24
+ * Falls back to a generic extracted shape if no pattern matches.
25
+ */
26
+ export function classifyError(err) {
27
+ const msg = err instanceof Error ? err.message : err;
28
+ const lower = msg.toLowerCase();
29
+ if (lower.includes('econnrefused') || lower.includes('unreachable') || lower.includes('connect')) {
30
+ return ERROR_SHAPES.connectionRefused;
31
+ }
32
+ if (lower.includes('timed out') || lower.includes('timeout') || lower.includes('ttl expired')) {
33
+ if (lower.includes('ttl') || lower.includes('acquire')) {
34
+ return ERROR_SHAPES.lockTimeout;
35
+ }
36
+ return ERROR_SHAPES.timeout;
37
+ }
38
+ if (lower.includes('tool') && (lower.includes('not found') || lower.includes('unknown'))) {
39
+ return ERROR_SHAPES.toolNotFound;
40
+ }
41
+ if (lower.includes('auth') || lower.includes('unauthorized') || lower.includes('forbidden')) {
42
+ return ERROR_SHAPES.authFailed;
43
+ }
44
+ if (lower.includes('malformed') || lower.includes('invalid response') || lower.includes('parse')) {
45
+ return ERROR_SHAPES.malformedResponse;
46
+ }
47
+ if (lower.includes('queue') && (lower.includes('full') || lower.includes('depth'))) {
48
+ return ERROR_SHAPES.queueFull;
49
+ }
50
+ // Fall back to heuristic shape extraction from the raw message
51
+ return extractShape(msg);
52
+ }
53
+ /**
54
+ * Wrap a function call with an error boundary that:
55
+ * 1. Catches any thrown error
56
+ * 2. Classifies it into a standard shape
57
+ * 3. Logs in terse format using logEvent()
58
+ * 4. Re-throws the original error
59
+ */
60
+ export async function withErrorBoundary(opts) {
61
+ const { trace, tag, tool, fn } = opts;
62
+ try {
63
+ return await fn();
64
+ }
65
+ catch (err) {
66
+ const error = err instanceof Error ? err : new Error(String(err));
67
+ const shape = classifyError(error);
68
+ const cleanMessage = error.message.replace(/[\n\r]/g, ' ').slice(0, 120);
69
+ logEvent({
70
+ trace,
71
+ tag,
72
+ action: 'error',
73
+ shape,
74
+ details: { tool, message: cleanMessage },
75
+ });
76
+ throw err;
77
+ }
78
+ }
79
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @agentruntime/cartridge-common
3
+ *
4
+ * Shared logging, error boundaries, and trace helpers for infrastructure cartridges.
5
+ *
6
+ * Philosophy: infrastructure cartridges NEVER expose their own MCP tools.
7
+ * They log to stdout in terse, scannable format. The operator agent reads logs.
8
+ * No dashboards. No admin APIs. Just logs and conversation.
9
+ */
10
+ export { logEvent } from './logger.js';
11
+ export type { LogEventOpts } from './logger.js';
12
+ export { TAGS, isValidTag } from './tags.js';
13
+ export type { TagKey, TagValue } from './tags.js';
14
+ export { ACTIONS } from './actions.js';
15
+ export type { ActionKey, ActionValue } from './actions.js';
16
+ export { generateTraceId, extractTraceId, traceMetadata, extractHop, NO_TRACE, } from './trace.js';
17
+ export { extractShape } from './shape.js';
18
+ export { withErrorBoundary, classifyError, ERROR_SHAPES, } from './errors.js';
19
+ export type { ErrorShapeKey } from './errors.js';
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @agentruntime/cartridge-common
3
+ *
4
+ * Shared logging, error boundaries, and trace helpers for infrastructure cartridges.
5
+ *
6
+ * Philosophy: infrastructure cartridges NEVER expose their own MCP tools.
7
+ * They log to stdout in terse, scannable format. The operator agent reads logs.
8
+ * No dashboards. No admin APIs. Just logs and conversation.
9
+ */
10
+ export { logEvent } from './logger.js';
11
+ export { TAGS, isValidTag } from './tags.js';
12
+ export { ACTIONS } from './actions.js';
13
+ export { generateTraceId, extractTraceId, traceMetadata, extractHop, NO_TRACE, } from './trace.js';
14
+ export { extractShape } from './shape.js';
15
+ export { withErrorBoundary, classifyError, ERROR_SHAPES, } from './errors.js';
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAGvC,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAG7C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,OAAO,EACL,eAAe,EACf,cAAc,EACd,aAAa,EACb,UAAU,EACV,QAAQ,GACT,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,YAAY,GACb,MAAM,aAAa,CAAC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Core logging function for infrastructure cartridges.
3
+ *
4
+ * Format: [trace] tag action: shape, key: value, key: value
5
+ * Example: [abc123] mtx acquire: handle_pr, caller: worker-1, waited: 0ms
6
+ *
7
+ * Rules:
8
+ * - One line per event. Never multi-line. Never JSON. Never stack traces.
9
+ * - Shape (before first comma) is the generalizable pattern for grep.
10
+ * - Details (after) are instance-specific key: value pairs.
11
+ * - This is the ONLY way infrastructure cartridges should log.
12
+ */
13
+ export interface LogEventOpts {
14
+ /** 6-char correlation ID */
15
+ trace: string;
16
+ /** 3-char source tag (e.g. 'mtx', 'brk') */
17
+ tag: string;
18
+ /** Action verb (e.g. 'acquire', 'error', 'OPEN') */
19
+ action: string;
20
+ /** Generalizable pattern — replaces specifics with [placeholders] */
21
+ shape: string;
22
+ /** Instance-specific key-value pairs appended after the shape */
23
+ details?: Record<string, string | number>;
24
+ }
25
+ export declare function logEvent(opts: LogEventOpts): void;
package/dist/logger.js ADDED
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Core logging function for infrastructure cartridges.
3
+ *
4
+ * Format: [trace] tag action: shape, key: value, key: value
5
+ * Example: [abc123] mtx acquire: handle_pr, caller: worker-1, waited: 0ms
6
+ *
7
+ * Rules:
8
+ * - One line per event. Never multi-line. Never JSON. Never stack traces.
9
+ * - Shape (before first comma) is the generalizable pattern for grep.
10
+ * - Details (after) are instance-specific key: value pairs.
11
+ * - This is the ONLY way infrastructure cartridges should log.
12
+ */
13
+ export function logEvent(opts) {
14
+ const { trace, tag, action, shape, details } = opts;
15
+ let line = `[${trace}] ${tag} ${action}: ${shape}`;
16
+ if (details && Object.keys(details).length > 0) {
17
+ const pairs = Object.entries(details)
18
+ .map(([k, v]) => `${k}: ${v}`)
19
+ .join(', ');
20
+ line += `, ${pairs}`;
21
+ }
22
+ process.stdout.write(line + '\n');
23
+ }
24
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAeH,MAAM,UAAU,QAAQ,CAAC,IAAkB;IACzC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IACpD,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK,GAAG,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;IACnD,IAAI,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;aAClC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;aAC7B,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;IACvB,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;AACpC,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Shape extraction — replaces specific values with [placeholders].
3
+ *
4
+ * Shapes are the generalizable part of a log message, used for pattern matching
5
+ * across many instances. The detail values are logged separately.
6
+ *
7
+ * Example:
8
+ * extractShape("acquire: edit_file, caller: worker-3, waited: 340ms")
9
+ * → "acquire: [tool], caller: [caller], waited: [duration]"
10
+ */
11
+ /**
12
+ * Extract the generalizable shape from a log message.
13
+ *
14
+ * @param message Raw message string (e.g. from an error or log call)
15
+ * @param knownTools List of known tool names to replace with [tool]
16
+ * @param knownCallers List of known caller names to replace with [caller]
17
+ */
18
+ export declare function extractShape(message: string, knownTools?: string[], knownCallers?: string[]): string;
package/dist/shape.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shape extraction — replaces specific values with [placeholders].
3
+ *
4
+ * Shapes are the generalizable part of a log message, used for pattern matching
5
+ * across many instances. The detail values are logged separately.
6
+ *
7
+ * Example:
8
+ * extractShape("acquire: edit_file, caller: worker-3, waited: 340ms")
9
+ * → "acquire: [tool], caller: [caller], waited: [duration]"
10
+ */
11
+ // Matches durations: 340ms, 1.2s, 5m, 30m10s, etc.
12
+ const DURATION_RE = /\b\d+(\.\d+)?(ms|s|m|h)\b/g;
13
+ // Matches plain integers or decimals used as counts/sizes
14
+ const NUMBER_RE = /\b\d{2,}\b/g;
15
+ // Matches absolute file paths (Unix and Windows)
16
+ const FILE_PATH_RE = /(?:[A-Za-z]:[\\\/][\w.\-\\\/]+|\b(?:\/[\w.\-/]+)+)/g;
17
+ // Matches URLs
18
+ const URL_RE = /https?:\/\/[^\s,]+/g;
19
+ /**
20
+ * Extract the generalizable shape from a log message.
21
+ *
22
+ * @param message Raw message string (e.g. from an error or log call)
23
+ * @param knownTools List of known tool names to replace with [tool]
24
+ * @param knownCallers List of known caller names to replace with [caller]
25
+ */
26
+ export function extractShape(message, knownTools = [], knownCallers = []) {
27
+ let s = message;
28
+ // Replace known tool names first (most specific)
29
+ for (const tool of knownTools) {
30
+ s = s.split(tool).join('[tool]');
31
+ }
32
+ // Replace known caller names
33
+ for (const caller of knownCallers) {
34
+ s = s.split(caller).join('[caller]');
35
+ }
36
+ // Replace URLs before paths (URLs contain slashes)
37
+ s = s.replace(URL_RE, '[url]');
38
+ // Replace file paths
39
+ s = s.replace(FILE_PATH_RE, '[path]');
40
+ // Replace durations before generic numbers
41
+ s = s.replace(DURATION_RE, '[duration]');
42
+ // Replace remaining multi-digit numbers (leave single digits — often semantic)
43
+ s = s.replace(NUMBER_RE, '[N]');
44
+ return s;
45
+ }
46
+ //# sourceMappingURL=shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shape.js","sourceRoot":"","sources":["../src/shape.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,mDAAmD;AACnD,MAAM,WAAW,GAAG,4BAA4B,CAAC;AAEjD,0DAA0D;AAC1D,MAAM,SAAS,GAAG,aAAa,CAAC;AAEhC,iDAAiD;AACjD,MAAM,YAAY,GAAG,qDAAqD,CAAC;AAE3E,eAAe;AACf,MAAM,MAAM,GAAG,qBAAqB,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAe,EACf,aAAuB,EAAE,EACzB,eAAyB,EAAE;IAE3B,IAAI,CAAC,GAAG,OAAO,CAAC;IAEhB,iDAAiD;IACjD,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED,6BAA6B;IAC7B,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;QAClC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACvC,CAAC;IAED,mDAAmD;IACnD,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE/B,qBAAqB;IACrB,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAEtC,2CAA2C;IAC3C,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,YAAY,CAAC,CAAC;IAEzC,+EAA+E;IAC/E,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAEhC,OAAO,CAAC,CAAC;AACX,CAAC"}
package/dist/tags.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Canonical 3-char source tags for infrastructure cartridges.
3
+ * These appear in every log line: [trace] tag action: shape
4
+ */
5
+ export declare const TAGS: {
6
+ readonly mutex: "mtx";
7
+ readonly semaphore: "sem";
8
+ readonly readonly: "rdo";
9
+ readonly retry: "rty";
10
+ readonly timeout: "tmo";
11
+ readonly circuitBreaker: "brk";
12
+ readonly router: "rtr";
13
+ readonly cache: "cch";
14
+ readonly auth: "aut";
15
+ readonly transform: "tfm";
16
+ readonly sanitize: "san";
17
+ readonly webhook: "whk";
18
+ readonly cron: "crn";
19
+ readonly audit: "aud";
20
+ readonly relay: "rly";
21
+ readonly mock: "mck";
22
+ readonly shadow: "shd";
23
+ readonly bridge: "brg";
24
+ readonly tee: "tee";
25
+ readonly approval: "apv";
26
+ };
27
+ export type TagKey = keyof typeof TAGS;
28
+ export type TagValue = (typeof TAGS)[TagKey];
29
+ export declare function isValidTag(s: string): s is TagValue;
package/dist/tags.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Canonical 3-char source tags for infrastructure cartridges.
3
+ * These appear in every log line: [trace] tag action: shape
4
+ */
5
+ export const TAGS = {
6
+ mutex: 'mtx',
7
+ semaphore: 'sem',
8
+ readonly: 'rdo',
9
+ retry: 'rty',
10
+ timeout: 'tmo',
11
+ circuitBreaker: 'brk',
12
+ router: 'rtr',
13
+ cache: 'cch',
14
+ auth: 'aut',
15
+ transform: 'tfm',
16
+ sanitize: 'san',
17
+ webhook: 'whk',
18
+ cron: 'crn',
19
+ audit: 'aud',
20
+ relay: 'rly',
21
+ mock: 'mck',
22
+ shadow: 'shd',
23
+ bridge: 'brg',
24
+ tee: 'tee',
25
+ approval: 'apv',
26
+ };
27
+ const TAG_VALUES = new Set(Object.values(TAGS));
28
+ export function isValidTag(s) {
29
+ return TAG_VALUES.has(s);
30
+ }
31
+ //# sourceMappingURL=tags.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tags.js","sourceRoot":"","sources":["../src/tags.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,KAAK,EAAE,KAAK;IACZ,SAAS,EAAE,KAAK;IAChB,QAAQ,EAAE,KAAK;IACf,KAAK,EAAE,KAAK;IACZ,OAAO,EAAE,KAAK;IACd,cAAc,EAAE,KAAK;IACrB,MAAM,EAAE,KAAK;IACb,KAAK,EAAE,KAAK;IACZ,IAAI,EAAE,KAAK;IACX,SAAS,EAAE,KAAK;IAChB,QAAQ,EAAE,KAAK;IACf,OAAO,EAAE,KAAK;IACd,IAAI,EAAE,KAAK;IACX,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IACZ,IAAI,EAAE,KAAK;IACX,MAAM,EAAE,KAAK;IACb,MAAM,EAAE,KAAK;IACb,GAAG,EAAE,KAAK;IACV,QAAQ,EAAE,KAAK;CACP,CAAC;AAKX,MAAM,UAAU,GAAG,IAAI,GAAG,CAAS,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;AAExD,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Correlation ID generation and propagation helpers.
3
+ * Every log line begins with [traceId] to correlate events across cartridges.
4
+ */
5
+ /**
6
+ * Sentinel value for calls that arrive without any trace context.
7
+ */
8
+ export declare const NO_TRACE = "------";
9
+ /**
10
+ * Generate a 6-char alphanumeric correlation ID.
11
+ * Used by entry points (webhook, cron, direct calls) to start a new trace.
12
+ */
13
+ export declare function generateTraceId(): string;
14
+ /**
15
+ * Extract trace ID from MCP tool call metadata.
16
+ * Returns the trace ID if present, or NO_TRACE sentinel if absent.
17
+ */
18
+ export declare function extractTraceId(meta?: Record<string, unknown>): string;
19
+ /**
20
+ * Create metadata object with trace ID for forwarding.
21
+ * Attach this to MCP tool calls when proxying upstream.
22
+ */
23
+ export declare function traceMetadata(traceId: string, hop: number): Record<string, unknown>;
24
+ /**
25
+ * Extract hop count from metadata (defaults to 0 if not present).
26
+ */
27
+ export declare function extractHop(meta?: Record<string, unknown>): number;
package/dist/trace.js ADDED
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Correlation ID generation and propagation helpers.
3
+ * Every log line begins with [traceId] to correlate events across cartridges.
4
+ */
5
+ const TRACE_ID_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789';
6
+ const TRACE_ID_LENGTH = 6;
7
+ /**
8
+ * Sentinel value for calls that arrive without any trace context.
9
+ */
10
+ export const NO_TRACE = '------';
11
+ /**
12
+ * Generate a 6-char alphanumeric correlation ID.
13
+ * Used by entry points (webhook, cron, direct calls) to start a new trace.
14
+ */
15
+ export function generateTraceId() {
16
+ let id = '';
17
+ for (let i = 0; i < TRACE_ID_LENGTH; i++) {
18
+ id += TRACE_ID_CHARS[Math.floor(Math.random() * TRACE_ID_CHARS.length)];
19
+ }
20
+ return id;
21
+ }
22
+ const TRACE_META_KEY = '_agentruntime_trace';
23
+ const TRACE_HOP_KEY = '_agentruntime_hop';
24
+ /**
25
+ * Extract trace ID from MCP tool call metadata.
26
+ * Returns the trace ID if present, or NO_TRACE sentinel if absent.
27
+ */
28
+ export function extractTraceId(meta) {
29
+ if (!meta)
30
+ return NO_TRACE;
31
+ const traceId = meta[TRACE_META_KEY];
32
+ if (typeof traceId === 'string' && traceId.length === TRACE_ID_LENGTH) {
33
+ return traceId;
34
+ }
35
+ return NO_TRACE;
36
+ }
37
+ /**
38
+ * Create metadata object with trace ID for forwarding.
39
+ * Attach this to MCP tool calls when proxying upstream.
40
+ */
41
+ export function traceMetadata(traceId, hop) {
42
+ return {
43
+ [TRACE_META_KEY]: traceId,
44
+ [TRACE_HOP_KEY]: hop,
45
+ };
46
+ }
47
+ /**
48
+ * Extract hop count from metadata (defaults to 0 if not present).
49
+ */
50
+ export function extractHop(meta) {
51
+ if (!meta)
52
+ return 0;
53
+ const hop = meta[TRACE_HOP_KEY];
54
+ return typeof hop === 'number' ? hop : 0;
55
+ }
56
+ //# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trace.js","sourceRoot":"","sources":["../src/trace.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,cAAc,GAAG,sCAAsC,CAAC;AAC9D,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,QAAQ,CAAC;AAEjC;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,eAAe,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,EAAE,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAC7C,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAE1C;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAA8B;IAC3D,IAAI,CAAC,IAAI;QAAE,OAAO,QAAQ,CAAC;IAC3B,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;IACrC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,eAAe,EAAE,CAAC;QACtE,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,GAAW;IACxD,OAAO;QACL,CAAC,cAAc,CAAC,EAAE,OAAO;QACzB,CAAC,aAAa,CAAC,EAAE,GAAG;KACrB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,IAA8B;IACvD,IAAI,CAAC,IAAI;QAAE,OAAO,CAAC,CAAC;IACpB,MAAM,GAAG,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC;IAChC,OAAO,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@helmetfire-labs/cartridge-common",
3
+ "version": "0.1.0",
4
+ "description": "Shared logging, error boundaries, and trace helpers for infrastructure cartridges",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist/"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "typecheck": "tsc --noEmit",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "license": "PolyForm-Noncommercial-1.0.0",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/k-kruusi/claude-supervisor.git",
20
+ "directory": "packages/cartridge-common"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^25.3.2",
24
+ "typescript": "^5.0.0"
25
+ }
26
+ }