myrmo 0.7.0 → 0.9.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 +1 -1
- package/dist/client.js +15 -5
- package/dist/config.d.ts +4 -4
- package/dist/config.js +5 -5
- package/dist/fingerprint.d.ts +11 -0
- package/dist/fingerprint.js +41 -2
- package/dist/format.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- 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.9.0";
|
|
6
6
|
export interface ColonyOptions {
|
|
7
7
|
url?: string;
|
|
8
8
|
apiKey?: string;
|
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 {
|
|
9
|
+
import { fingerprint2, guessErrorType } from "./fingerprint.js";
|
|
10
10
|
import { 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.9.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;
|
package/dist/config.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { PublishMode } from "./types.js";
|
|
2
|
-
export type HookMode = "on" | "off";
|
|
2
|
+
export type HookMode = "on" | "failures" | "off";
|
|
3
3
|
export interface MyrmoConfig {
|
|
4
4
|
publish?: PublishMode;
|
|
5
5
|
agent_id?: string;
|
|
6
6
|
/** Failed attempts before a fix is worth publishing. */
|
|
7
7
|
min_failed_attempts?: number;
|
|
8
|
-
/** The Claude Code plugin's
|
|
8
|
+
/** The Claude Code plugin's reminders: on (failures, errors hidden by a pipe, publishing a fix Myrmo lacked), failures only, or off. */
|
|
9
9
|
hook?: HookMode;
|
|
10
10
|
/** Send no agent id at all. */
|
|
11
11
|
anonymous?: boolean;
|
|
@@ -49,9 +49,9 @@ export declare const SETTINGS: readonly [{
|
|
|
49
49
|
}, {
|
|
50
50
|
readonly key: "hook";
|
|
51
51
|
readonly file: "hook";
|
|
52
|
-
readonly values: "on | off";
|
|
52
|
+
readonly values: "on | failures | off";
|
|
53
53
|
readonly default: "on";
|
|
54
|
-
readonly about: "Claude Code plugin:
|
|
54
|
+
readonly about: "Claude Code plugin reminders. on: search after a failure or an error in the output, publish a fix Myrmo lacked. failures: only after a failed command.";
|
|
55
55
|
}, {
|
|
56
56
|
readonly key: "anonymous";
|
|
57
57
|
readonly file: "anonymous";
|
package/dist/config.js
CHANGED
|
@@ -34,7 +34,7 @@ export function readConfig() {
|
|
|
34
34
|
const attempts = parseAttempts(raw.min_failed_attempts);
|
|
35
35
|
if (attempts !== undefined)
|
|
36
36
|
config.min_failed_attempts = attempts;
|
|
37
|
-
if (raw.hook === "on" || raw.hook === "off")
|
|
37
|
+
if (raw.hook === "on" || raw.hook === "failures" || raw.hook === "off")
|
|
38
38
|
config.hook = raw.hook;
|
|
39
39
|
if (typeof raw.anonymous === "boolean")
|
|
40
40
|
config.anonymous = raw.anonymous;
|
|
@@ -89,7 +89,7 @@ export function minFailedAttempts(explicit) {
|
|
|
89
89
|
export const SETTINGS = [
|
|
90
90
|
{ key: "publish", file: "publish", values: "ask | auto | off", default: "ask (asked the first time an agent wants to publish)", about: "Whether agents may publish fixes for you. ask shows you each one first." },
|
|
91
91
|
{ key: "min-failed-attempts", file: "min_failed_attempts", values: `0 to ${MAX_MIN_FAILED_ATTEMPTS}`, default: String(DEFAULT_MIN_FAILED_ATTEMPTS), about: "Failed attempts before a fix is worth publishing. Higher means fewer, more selective trails." },
|
|
92
|
-
{ key: "hook", file: "hook", values: "on | off", default: "on", about: "Claude Code plugin:
|
|
92
|
+
{ key: "hook", file: "hook", values: "on | failures | off", default: "on", about: "Claude Code plugin reminders. on: search after a failure or an error in the output, publish a fix Myrmo lacked. failures: only after a failed command." },
|
|
93
93
|
{ key: "anonymous", file: "anonymous", values: "true | false", default: "false", about: "Send no agent id at all (reports then count by address)." },
|
|
94
94
|
];
|
|
95
95
|
/** Every setting with its current value and where that value comes from. */
|
|
@@ -102,7 +102,7 @@ export function settingsReport() {
|
|
|
102
102
|
const rows = {
|
|
103
103
|
publish: { value: publish.source === "default" ? "ask" : publish.mode, source: publish.source },
|
|
104
104
|
"min-failed-attempts": { value: String(attempts.value), source: attempts.source },
|
|
105
|
-
hook: hookEnv === "on" || hookEnv === "off" ? { value: hookEnv, source: "env" } : file.hook ? { value: file.hook, source: "file" } : { value: "on", source: "default" },
|
|
105
|
+
hook: hookEnv === "on" || hookEnv === "failures" || hookEnv === "off" ? { value: hookEnv, source: "env" } : file.hook ? { value: file.hook, source: "file" } : { value: "on", source: "default" },
|
|
106
106
|
anonymous: anonEnv ? { value: String(["1", "true"].includes(anonEnv)), source: "env" } : file.anonymous !== undefined ? { value: String(file.anonymous), source: "file" } : { value: "false", source: "default" },
|
|
107
107
|
};
|
|
108
108
|
return SETTINGS.map((s) => ({ key: s.key, ...rows[s.key], about: s.about, values: s.values, default: s.default }));
|
|
@@ -129,8 +129,8 @@ export function setSetting(key, value) {
|
|
|
129
129
|
patch = { min_failed_attempts: n };
|
|
130
130
|
}
|
|
131
131
|
else if (key === "hook") {
|
|
132
|
-
if (value !== "on" && value !== "off")
|
|
133
|
-
return { ok: false, error: "hook takes on or off." };
|
|
132
|
+
if (value !== "on" && value !== "failures" && value !== "off")
|
|
133
|
+
return { ok: false, error: "hook takes on, failures or off." };
|
|
134
134
|
patch = { hook: value };
|
|
135
135
|
}
|
|
136
136
|
else {
|
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
|
+
`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.`,
|
|
93
93
|
].join("\n");
|
|
94
94
|
}
|
|
95
95
|
return [
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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";
|
|
3
|
+
export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.js";
|
|
4
4
|
export { 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";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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";
|
|
3
|
+
export { fingerprint, fingerprint2, normalizeMessage, normalizeMessage2, guessErrorType, PREFIX as FINGERPRINT_PREFIX, PREFIX_2 as FINGERPRINT_PREFIX_2 } from "./fingerprint.js";
|
|
4
4
|
export { 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";
|
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.9.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",
|