myrmo 0.8.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 +7 -2
- package/dist/client.js +23 -8
- package/dist/fingerprint.d.ts +11 -0
- package/dist/fingerprint.js +41 -2
- package/dist/format.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/redact.d.ts +6 -0
- package/dist/redact.js +80 -0
- package/dist/session.js +2 -2
- package/package.json +1 -1
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.
|
|
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
|
-
/**
|
|
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
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
// 3. POST /v1/search (embedding + vector search; only for errors the colony has not fingerprinted)
|
|
7
7
|
import { agentIdentity, publishChoice } from "./config.js";
|
|
8
8
|
import { detectEnvironment, parsePackage } from "./environment.js";
|
|
9
|
-
import {
|
|
10
|
-
import { redactText, redactValue } from "./redact.js";
|
|
9
|
+
import { fingerprint2, guessErrorType } from "./fingerprint.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.
|
|
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];
|
|
@@ -141,8 +141,18 @@ export class Colony {
|
|
|
141
141
|
const hit = this.cached(fp);
|
|
142
142
|
if (hit !== undefined)
|
|
143
143
|
return hit;
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
let found;
|
|
145
|
+
try {
|
|
146
|
+
found = await this.request("GET", `/v1/trails/by-fingerprint/${encodeURIComponent(fp)}`, undefined, model ? { "x-myrmo-model": model } : undefined, true);
|
|
147
|
+
}
|
|
148
|
+
catch (err) {
|
|
149
|
+
// 400: a colony that predates fp2 does not know the key. Nothing exact to offer; the search that follows still finds the trail.
|
|
150
|
+
if (err instanceof MyrmoError && err.status === 400)
|
|
151
|
+
found = null;
|
|
152
|
+
else
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
const value = found === null || found.status === 404 ? null : { fingerprint: found.data.fingerprint, hits: found.data.results.map(toHit), notice: found.data.notice, source: "fingerprint" };
|
|
146
156
|
this.remember(fp, value);
|
|
147
157
|
return value;
|
|
148
158
|
}
|
|
@@ -150,7 +160,7 @@ export class Colony {
|
|
|
150
160
|
async search(query) {
|
|
151
161
|
const error = redactText(query.error);
|
|
152
162
|
const errorType = query.errorType ?? guessErrorType(error);
|
|
153
|
-
const fp =
|
|
163
|
+
const fp = fingerprint2(error);
|
|
154
164
|
const exact = await this.lookup(fp, query.model);
|
|
155
165
|
if (exact && exact.hits.length > 0)
|
|
156
166
|
return exact;
|
|
@@ -193,10 +203,15 @@ export class Colony {
|
|
|
193
203
|
this.cache.clear();
|
|
194
204
|
return { trailId: data.trail_id, counted: data.counted, strength: data.strength };
|
|
195
205
|
}
|
|
196
|
-
/**
|
|
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
|
+
*/
|
|
197
211
|
preview(trail) {
|
|
198
212
|
const redactions = {};
|
|
199
|
-
|
|
213
|
+
const redacted = redactValue(trail, redactions);
|
|
214
|
+
return { trail: redacted, redactions, names: possibleNames(redacted) };
|
|
200
215
|
}
|
|
201
216
|
/**
|
|
202
217
|
* Ask the colony whether it would accept this trail, without publishing it: the schema check publishing
|
package/dist/fingerprint.d.ts
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
export declare const PREFIX = "fp1_";
|
|
2
|
+
export declare const PREFIX_2 = "fp2_";
|
|
2
3
|
/** Strip everything that varies between machines but not between errors. */
|
|
3
4
|
export declare function normalizeMessage(errorType: string, message: string): string;
|
|
5
|
+
/** Drop the leading exception classes, severity words and tool codes of an error line. */
|
|
6
|
+
export declare function stripLabels(message: string): string;
|
|
7
|
+
/** fp2: strip everything that varies between machines or between wrappers, but not between errors. */
|
|
8
|
+
export declare function normalizeMessage2(message: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* The fingerprint the colony indexes: a hash of the error message alone. The key a trail is filed under is the one
|
|
11
|
+
* a searcher computes from the line it holds.
|
|
12
|
+
*/
|
|
13
|
+
export declare function fingerprint2(message: string): string;
|
|
14
|
+
/** fp1, the retired fingerprint: the colony no longer indexes it. Use `fingerprint2`. */
|
|
4
15
|
export declare function fingerprint(runtime: string, errorType: string, message: string): string;
|
|
5
16
|
/** `Type: message` → `Type`, for callers that only have the error line. */
|
|
6
17
|
export declare function guessErrorType(errorLine: string): string;
|
package/dist/fingerprint.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
// Error
|
|
2
|
-
//
|
|
1
|
+
// Error fingerprints. `fingerprint2` (fp2, the one the colony indexes) is a port of protocol/fingerprint_v2.py: a hash of
|
|
2
|
+
// the message alone, with the labels that wrap an error line dropped, because a searcher knows the line it holds and
|
|
3
|
+
// nothing reliable about the error type a trail's author declared. `fingerprint` (fp1: runtime, type and message) is
|
|
4
|
+
// the retired one, a port of protocol/fingerprint_v1.py. The shared vectors in protocol/fingerprint.v1.vectors.json and
|
|
5
|
+
// protocol/fingerprint.v2.vectors.json are normative; see test/fingerprint.test.mjs.
|
|
3
6
|
import { createHash } from "node:crypto";
|
|
4
7
|
export const PREFIX = "fp1_";
|
|
8
|
+
export const PREFIX_2 = "fp2_";
|
|
5
9
|
const MAX_NORMALIZED_CHARS = 300;
|
|
6
10
|
const SEPARATOR = "\u001f";
|
|
7
11
|
const rule = (pattern, replacement) => [new RegExp(pattern, "g"), replacement];
|
|
@@ -31,6 +35,41 @@ export function normalizeMessage(errorType, message) {
|
|
|
31
35
|
text = text.replace(pattern, replacement);
|
|
32
36
|
return Array.from(text.replace(/\s+/g, " ").trim()).slice(0, MAX_NORMALIZED_CHARS).join("");
|
|
33
37
|
}
|
|
38
|
+
// Labels removed from the start of the message, one after another, at most MAX_LABELS of them. Matched on the text as
|
|
39
|
+
// written (before lowercasing): the capital letter is what tells a class name from a word. Exactly the pattern of
|
|
40
|
+
// protocol/fingerprint_v2.py.
|
|
41
|
+
const MAX_LABELS = 4;
|
|
42
|
+
const CLASS = String.raw `(?:[A-Za-z_][A-Za-z0-9_$]*\.)*[A-Z][A-Za-z0-9_$]*(?:Error|Exception|Warning|Failure)`;
|
|
43
|
+
const SEVERITY = String.raw `(?:Error|ERROR|error|Fatal|FATAL|fatal|Warning|WARNING|warning|Panic|PANIC|panic|Exception|EXCEPTION|exception|Err|ERR|Caused by)` +
|
|
44
|
+
String.raw `(?:\[[A-Za-z0-9_]+\])?`;
|
|
45
|
+
const TOOL_CODE = String.raw `(?:error|warning)\s+[A-Z]{1,5}[0-9]{2,5}`; // "error TS2322", "warning CS0168"
|
|
46
|
+
const LABEL = new RegExp(String.raw `^(?:(?:Uncaught\s+)?(?:${CLASS}|${SEVERITY}|${TOOL_CODE})\s*:\s+|npm\s+(?:ERR!|error)\s+)`);
|
|
47
|
+
/** Drop the leading exception classes, severity words and tool codes of an error line. */
|
|
48
|
+
export function stripLabels(message) {
|
|
49
|
+
let text = message;
|
|
50
|
+
for (let i = 0; i < MAX_LABELS; i++) {
|
|
51
|
+
const shorter = text.replace(LABEL, "");
|
|
52
|
+
if (shorter === text)
|
|
53
|
+
break;
|
|
54
|
+
text = shorter;
|
|
55
|
+
}
|
|
56
|
+
return text;
|
|
57
|
+
}
|
|
58
|
+
/** fp2: strip everything that varies between machines or between wrappers, but not between errors. */
|
|
59
|
+
export function normalizeMessage2(message) {
|
|
60
|
+
let text = stripLabels(message.normalize("NFKC").trim()).toLowerCase();
|
|
61
|
+
for (const [pattern, replacement] of RULES)
|
|
62
|
+
text = text.replace(pattern, replacement);
|
|
63
|
+
return Array.from(text.replace(/\s+/g, " ").trim()).slice(0, MAX_NORMALIZED_CHARS).join("");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The fingerprint the colony indexes: a hash of the error message alone. The key a trail is filed under is the one
|
|
67
|
+
* a searcher computes from the line it holds.
|
|
68
|
+
*/
|
|
69
|
+
export function fingerprint2(message) {
|
|
70
|
+
return PREFIX_2 + createHash("sha256").update(normalizeMessage2(message), "utf8").digest("hex").slice(0, 16);
|
|
71
|
+
}
|
|
72
|
+
/** fp1, the retired fingerprint: the colony no longer indexes it. Use `fingerprint2`. */
|
|
34
73
|
export function fingerprint(runtime, errorType, message) {
|
|
35
74
|
const material = [runtime.trim().toLowerCase(), nfkcLower(errorType).trim(), normalizeMessage(errorType, message)].join(SEPARATOR);
|
|
36
75
|
return PREFIX + createHash("sha256").update(material, "utf8").digest("hex").slice(0, 16);
|
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
|
-
`
|
|
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
|
-
export { fingerprint, normalizeMessage, guessErrorType, PREFIX as FINGERPRINT_PREFIX } from "./fingerprint.js";
|
|
4
|
-
export { redactText, redactValue, type RedactionReport } from "./redact.js";
|
|
3
|
+
export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.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
|
-
export { fingerprint, normalizeMessage, guessErrorType, PREFIX as FINGERPRINT_PREFIX } from "./fingerprint.js";
|
|
4
|
-
export { redactText, redactValue } from "./redact.js";
|
|
3
|
+
export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.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/dist/session.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { minFailedAttempts } from "./config.js";
|
|
4
4
|
import { detectEnvironment, parsePackage } from "./environment.js";
|
|
5
5
|
import { formatResult } from "./format.js";
|
|
6
|
-
import {
|
|
6
|
+
import { fingerprint2, guessErrorType } from "./fingerprint.js";
|
|
7
7
|
import { redactText } from "./redact.js";
|
|
8
8
|
export class Session {
|
|
9
9
|
colony;
|
|
@@ -117,6 +117,6 @@ export class Session {
|
|
|
117
117
|
*/
|
|
118
118
|
get lastFingerprint() {
|
|
119
119
|
const last = this.failures.at(-1);
|
|
120
|
-
return last ?
|
|
120
|
+
return last ? fingerprint2(redactText(last.message)) : null;
|
|
121
121
|
}
|
|
122
122
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myrmo",
|
|
3
|
-
"version": "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",
|