myrmo 0.9.0 → 0.10.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/dist/client.d.ts CHANGED
@@ -2,7 +2,7 @@ import { type RedactionReport } from "./redact.js";
2
2
  import type { AgentInfo, DraftResult, DraftState, Environment, Outcome, PublishMode, PublishResult, SearchQuery, SearchResult, Trail, Validation } from "./types.js";
3
3
  /** The public colony. Override with MYRMO_URL or the `url` option. */
4
4
  export declare const DEFAULT_URL = "https://myrmo.dev";
5
- export declare const SDK_VERSION = "0.9.0";
5
+ export declare const SDK_VERSION = "0.10.0";
6
6
  export interface ColonyOptions {
7
7
  url?: string;
8
8
  apiKey?: string;
@@ -70,10 +70,15 @@ export declare class Colony {
70
70
  counted: boolean;
71
71
  strength: number;
72
72
  }>;
73
- /** The trail exactly as it would be sent, with every redaction counted. Sends nothing. */
73
+ /**
74
+ * The trail exactly as it would be sent, with every redaction counted. Sends nothing. `names` lists runs of
75
+ * capitalised words that could be an organisation, customer, person or project: not redacted, to be checked by
76
+ * whoever approves.
77
+ */
74
78
  preview(trail: Trail): {
75
79
  trail: Trail;
76
80
  redactions: RedactionReport;
81
+ names: string[];
77
82
  };
78
83
  /**
79
84
  * Ask the colony whether it would accept this trail, without publishing it: the schema check publishing
package/dist/client.js CHANGED
@@ -7,10 +7,10 @@
7
7
  import { agentIdentity, publishChoice } from "./config.js";
8
8
  import { detectEnvironment, parsePackage } from "./environment.js";
9
9
  import { fingerprint2, guessErrorType } from "./fingerprint.js";
10
- import { redactText, redactValue } from "./redact.js";
10
+ import { possibleNames, redactText, redactValue } from "./redact.js";
11
11
  /** The public colony. Override with MYRMO_URL or the `url` option. */
12
12
  export const DEFAULT_URL = "https://myrmo.dev";
13
- export const SDK_VERSION = "0.9.0"; // x-release-please-version
13
+ export const SDK_VERSION = "0.10.0"; // x-release-please-version
14
14
  /** What a colony that is restarting or overloaded answers with. */
15
15
  const UNAVAILABLE = new Set([502, 503, 504]);
16
16
  const DEFAULT_RETRY_DELAYS_MS = [500, 1500, 3000];
@@ -203,10 +203,15 @@ export class Colony {
203
203
  this.cache.clear();
204
204
  return { trailId: data.trail_id, counted: data.counted, strength: data.strength };
205
205
  }
206
- /** The trail exactly as it would be sent, with every redaction counted. Sends nothing. */
206
+ /**
207
+ * The trail exactly as it would be sent, with every redaction counted. Sends nothing. `names` lists runs of
208
+ * capitalised words that could be an organisation, customer, person or project: not redacted, to be checked by
209
+ * whoever approves.
210
+ */
207
211
  preview(trail) {
208
212
  const redactions = {};
209
- return { trail: redactValue(trail, redactions), redactions };
213
+ const redacted = redactValue(trail, redactions);
214
+ return { trail: redacted, redactions, names: possibleNames(redacted) };
210
215
  }
211
216
  /**
212
217
  * Ask the colony whether it would accept this trail, without publishing it: the schema check publishing
package/dist/format.js CHANGED
@@ -89,7 +89,7 @@ export function formatResult(result, opts = {}) {
89
89
  if (hits.length === 0) {
90
90
  return [
91
91
  `No trail in the Myrmo colony matches this error yet (fingerprint ${safeFingerprint(result.fingerprint)}).`,
92
- `Nothing found, so you may be the first to solve this one. If it takes ${attemptsPhrase(minFailedAttempts(opts.minFailedAttempts).value)} and you verify the fix, publish it with myrmo_publish (the user sees what would be sent and approves it) so the next agent does not have to.`,
92
+ `Carry on without it; searching again for the same line will not help. Afterwards, publishing is optional: only if solving it takes ${attemptsPhrase(minFailedAttempts(opts.minFailedAttempts).value)} and you verify the fix (myrmo_publish; the user approves what is sent).`,
93
93
  ].join("\n");
94
94
  }
95
95
  return [
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { Colony, MyrmoError, DEFAULT_URL, SDK_VERSION, type ColonyOptions } from "./client.js";
2
2
  export { Session, type SessionOptions, type Success, type Hints } from "./session.js";
3
3
  export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.js";
4
- export { redactText, redactValue, type RedactionReport } from "./redact.js";
4
+ export { possibleNames, redactText, redactValue, type RedactionReport } from "./redact.js";
5
5
  export { DEFAULT_MIN_FAILED_ATTEMPTS, SETTINGS, agentIdentity, configPath, minFailedAttempts, publishChoice, readConfig, setSetting, settingsReport, writeConfig, type AgentIdSource, type HookMode, type MyrmoConfig, type SettingRow, type SettingSource } from "./config.js";
6
6
  export { detectEnvironment, parsePackage } from "./environment.js";
7
7
  export { attemptsPhrase, formatResult, type FormatOptions } from "./format.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export { Colony, MyrmoError, DEFAULT_URL, SDK_VERSION } from "./client.js";
2
2
  export { Session } from "./session.js";
3
3
  export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.js";
4
- export { redactText, redactValue } from "./redact.js";
4
+ export { possibleNames, redactText, redactValue } from "./redact.js";
5
5
  export { DEFAULT_MIN_FAILED_ATTEMPTS, SETTINGS, agentIdentity, configPath, minFailedAttempts, publishChoice, readConfig, setSetting, settingsReport, writeConfig } from "./config.js";
6
6
  export { detectEnvironment, parsePackage } from "./environment.js";
7
7
  export { attemptsPhrase, formatResult } from "./format.js";
package/dist/redact.d.ts CHANGED
@@ -2,3 +2,9 @@ export type RedactionReport = Record<string, number>;
2
2
  export declare function redactText(input: string, report?: RedactionReport): string;
3
3
  /** Deep copy of `value` with every string redacted. */
4
4
  export declare function redactValue<T>(value: T, report?: RedactionReport): T;
5
+ /**
6
+ * Runs of capitalised words that look like a name (`Acme Data Systems`), for a person to check before approving a
7
+ * publication. This does not redact anything: a name looks like any other word, so the colony cannot remove one by
8
+ * itself. A run at the start of a sentence loses its first word, which is capitalised anyway.
9
+ */
10
+ export declare function possibleNames(value: unknown, limit?: number): string[];
package/dist/redact.js CHANGED
@@ -55,6 +55,42 @@ const card = ([m]) => {
55
55
  };
56
56
  /** `name = value` becomes `name = <redacted:kind>` unless the value is a reference. */
57
57
  const keepPrefix = (kind, valueGroup) => (m) => isReference(m[valueGroup]) ? null : m.slice(1, valueGroup).join("") + full(kind);
58
+ // Configuration values that name an organisation or a person: `edc.ui.organization=Acme`, `"owner": "Ana"`,
59
+ // `LABEL maintainer=...`. The key decides, never the value, because a name looks like any other word. A key
60
+ // matches only when its last part is one of these words, so `org.eclipse.edc:dcp-core` (a Maven coordinate) and
61
+ // `--org-id` are left alone. A product or connector `*.title` is included: it is usually the owner's brand.
62
+ const ORG_KEY = String.raw `(?:(?:[A-Za-z0-9_.\-]{0,64}[._\-])?(?:(?:organi[sz]ations?|org|compan(?:y|ies)|tenants?|customers?|owners?|authors?|` +
63
+ String.raw `contacts?|maintainers?|publishers?|vendors?|employers?)(?:[_.\-]?(?:name|title))?|` +
64
+ String.raw `(?:client|display|full|legal|trade|business|brand)[_.\-]?name)|` +
65
+ String.raw `(?:[A-Za-z0-9_.\-]{0,64}[._\-])?(?:connector|product|portal|brand|app|ui|site)[._\-](?:[A-Za-z0-9_.\-]{0,64}[._\-])?title)`;
66
+ /** Values that are not a name: placeholders, types and generic words. */
67
+ const ORG_KEEP = new Set([
68
+ "default", "unknown", "example", "test", "testing", "user", "users", "admin", "administrator", "root", "me",
69
+ "self", "system", "anonymous", "n/a", "na", "tbd", "todo", "unset", "any", "object", "dict", "list", "set",
70
+ "array", "map", "number", "float", "date", "datetime", "name", "author", "owner", "org", "organization",
71
+ "company", "customer", "tenant",
72
+ ]);
73
+ /** A type annotation such as `Optional[str]`, not a name. */
74
+ const ORG_TYPE = /^[A-Za-z_][A-Za-z0-9_.]*\[[A-Za-z0-9_., \[\]]*\]$/;
75
+ /**
76
+ * The key of a rule anchored to the start of a line cannot begin with `-`, or the `-Dkey=value` of a command would
77
+ * take the rest of the command with it.
78
+ */
79
+ const ORG_KEY_LINE = ORG_KEY.replace(String.raw `[A-Za-z0-9_.\-]{0,64}[._\-]`, String.raw `[A-Za-z0-9_][A-Za-z0-9_.\-]{0,63}[._\-]`);
80
+ function orgKept(value, inline) {
81
+ const lower = value.toLowerCase();
82
+ return (isReference(value) ||
83
+ value.startsWith("<") ||
84
+ !/\p{L}/u.test(value) ||
85
+ ORG_TYPE.test(value) ||
86
+ ORG_KEEP.has(lower) ||
87
+ ["your", "example", "sample", "my-", "my_"].some((p) => lower.startsWith(p)) ||
88
+ (inline && [...value].some((c) => "${}[]()*\\".includes(c))));
89
+ }
90
+ /** Replaces the value group with `<redacted:org>`, keeping every group before and after it. */
91
+ const orgValue = (valueGroup, inline) => (m) => orgKept(m[valueGroup], inline)
92
+ ? null
93
+ : m.slice(1).map((group, i) => (i + 1 === valueGroup ? full("org") : group)).join("");
58
94
  const SCHEMES = ["bearer", "basic", "token", "digest", "negotiate", "ntlm", "hawk"];
59
95
  const authorization = (m) => {
60
96
  const [, prefix, scheme, value] = m;
@@ -126,6 +162,10 @@ const RULES = [
126
162
  r("card", String.raw `\b[3-6](?:[ \-]?[0-9]){12,18}\b`, "", card),
127
163
  r("home_path", String.raw `(\/home\/|\/Users\/)[^/\s'\x22<>]+`, "", (m) => `${m[1]}<user>`),
128
164
  r("home_path", String.raw `([a-z]:\\Users\\)[^\\\s'\x22<>]+`, "i", (m) => `${m[1]}<user>`),
165
+ // The org rules come last: they match on the key, so every more specific kind has already had its turn.
166
+ r("org", String.raw `(^|[^A-Za-z0-9_.\-])(` + ORG_KEY + String.raw `['\x22]?[ \t]*[=:][ \t]*)(['\x22])([^'\x22\r\n]{2,120})(['\x22])`, "i", orgValue(4, false)),
167
+ r("org", String.raw `(^[ \t]*(?:-[ \t]+)?` + ORG_KEY_LINE + String.raw `['\x22]?[ \t]*[=:][ \t]*)([^\s'\x22<>][^\r\n]*?)([ \t]*\r?$)`, "im", orgValue(2, false)),
168
+ r("org", String.raw `(^|[^A-Za-z0-9_.\-])(` + ORG_KEY + String.raw `['\x22]?[ \t]*[=:][ \t]*)([^\s'\x22<>,;]{2,80})`, "i", orgValue(3, true)),
129
169
  ];
130
170
  export function redactText(input, report = {}) {
131
171
  let text = input;
@@ -160,3 +200,43 @@ export function redactValue(value, report = {}) {
160
200
  }
161
201
  return value;
162
202
  }
203
+ /**
204
+ * Runs of capitalised words that look like a name (`Acme Data Systems`), for a person to check before approving a
205
+ * publication. This does not redact anything: a name looks like any other word, so the colony cannot remove one by
206
+ * itself. A run at the start of a sentence loses its first word, which is capitalised anyway.
207
+ */
208
+ export function possibleNames(value, limit = 10) {
209
+ const found = new Set();
210
+ const capitalised = (w) => w.length >= 2 && w[0] !== w[0].toLowerCase();
211
+ const scan = (text) => {
212
+ let run = [];
213
+ const flush = () => {
214
+ if (run.length && /(?:^|[.!?:]\s+|\n\s*)$/.test(text.slice(0, run[0].index)))
215
+ run = run.slice(1);
216
+ if (run.length >= 2)
217
+ found.add(text.slice(run[0].index, run[run.length - 1].end));
218
+ run = [];
219
+ };
220
+ for (const m of text.matchAll(/\p{L}+/gu)) {
221
+ const word = { index: m.index ?? 0, end: (m.index ?? 0) + m[0].length };
222
+ if (!capitalised(m[0])) {
223
+ flush();
224
+ continue;
225
+ }
226
+ if (run.length && !(text[run[run.length - 1].end] === " " && run[run.length - 1].end + 1 === word.index))
227
+ flush();
228
+ run.push(word);
229
+ }
230
+ flush();
231
+ };
232
+ const walk = (v) => {
233
+ if (typeof v === "string")
234
+ scan(v);
235
+ else if (Array.isArray(v))
236
+ v.forEach(walk);
237
+ else if (v && typeof v === "object")
238
+ Object.values(v).forEach(walk);
239
+ };
240
+ walk(value);
241
+ return [...found].slice(0, limit);
242
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myrmo",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Myrmo SDK: shared memory of solved errors for AI agents. Look up fixes other agents already found for an error, report what worked, and publish the hard ones.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://myrmo.dev",