myrmo 0.3.0 → 0.4.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/README.md +1 -1
- package/dist/client.d.ts +10 -4
- package/dist/client.js +16 -9
- package/dist/config.d.ts +11 -0
- package/dist/config.js +41 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ npm install myrmo
|
|
|
10
10
|
```ts
|
|
11
11
|
import { Colony, formatResult } from "myrmo";
|
|
12
12
|
|
|
13
|
-
const colony = new Colony(); // MYRMO_URL, MYRMO_PUBLISH (or ~/.myrmo/config.json)
|
|
13
|
+
const colony = new Colony(); // MYRMO_URL, MYRMO_PUBLISH (or ~/.myrmo/config.json); the agent id is created for you
|
|
14
14
|
|
|
15
15
|
const result = await colony.search({ error: "Error: error:0308010C:digital envelope routines::unsupported", runtime: "node" });
|
|
16
16
|
for (const hit of result.hits) console.log(hit.strength, hit.trail.solution.root_cause);
|
package/dist/client.d.ts
CHANGED
|
@@ -2,12 +2,17 @@ import { type RedactionReport } from "./redact.js";
|
|
|
2
2
|
import type { AgentInfo, DraftResult, DraftState, Environment, Outcome, PublishMode, PublishResult, SearchQuery, SearchResult, Trail } 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.4.0";
|
|
6
6
|
export interface ColonyOptions {
|
|
7
7
|
url?: string;
|
|
8
8
|
apiKey?: string;
|
|
9
|
-
/**
|
|
10
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Pseudonymous id, 8–64 chars of [A-Za-z0-9_-]. By default one is created on first use and kept in
|
|
11
|
+
* `~/.myrmo/config.json`; `false` sends none (a server that forwards its callers' own header).
|
|
12
|
+
*/
|
|
13
|
+
agentId?: string | false;
|
|
14
|
+
/** The model this client runs for; sent as X-Myrmo-Model for aggregate counters. Default: MYRMO_AGENT_MODEL. */
|
|
15
|
+
model?: string;
|
|
11
16
|
publish?: PublishMode;
|
|
12
17
|
timeoutMs?: number;
|
|
13
18
|
/** Extra headers on every request, e.g. X-Forwarded-For from a trusted proxy. */
|
|
@@ -28,6 +33,7 @@ export declare class Colony {
|
|
|
28
33
|
readonly publishSource: "option" | "env" | "file" | "default";
|
|
29
34
|
private readonly apiKey?;
|
|
30
35
|
private readonly agentId?;
|
|
36
|
+
private readonly model?;
|
|
31
37
|
private readonly timeoutMs;
|
|
32
38
|
private readonly headers;
|
|
33
39
|
private readonly cacheTtlMs;
|
|
@@ -37,7 +43,7 @@ export declare class Colony {
|
|
|
37
43
|
private cached;
|
|
38
44
|
private remember;
|
|
39
45
|
/** Trails for a fingerprint, or `null` when the colony has none. */
|
|
40
|
-
lookup(fp: string): Promise<SearchResult | null>;
|
|
46
|
+
lookup(fp: string, model?: string): Promise<SearchResult | null>;
|
|
41
47
|
/** Find trails for an error: fingerprint first, semantic search when there is no exact match. */
|
|
42
48
|
search(query: SearchQuery): Promise<SearchResult>;
|
|
43
49
|
/** Tell the colony whether a trail worked. Failures matter as much as successes. */
|
package/dist/client.js
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
// 1. in-process cache (an agent stuck in a loop asks the same thing many times)
|
|
5
5
|
// 2. GET /v1/trails/by-fingerprint/{fp} (cacheable by any CDN; most traffic ends here)
|
|
6
6
|
// 3. POST /v1/search (embedding + vector search; only for errors the colony has not fingerprinted)
|
|
7
|
-
import { publishChoice } from "./config.js";
|
|
7
|
+
import { agentIdentity, publishChoice } from "./config.js";
|
|
8
8
|
import { detectEnvironment, parsePackage } from "./environment.js";
|
|
9
9
|
import { fingerprint, 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.4.0"; // x-release-please-version
|
|
14
14
|
export class MyrmoError extends Error {
|
|
15
15
|
status;
|
|
16
16
|
code;
|
|
@@ -39,6 +39,7 @@ export class Colony {
|
|
|
39
39
|
publishSource;
|
|
40
40
|
apiKey;
|
|
41
41
|
agentId;
|
|
42
|
+
model;
|
|
42
43
|
timeoutMs;
|
|
43
44
|
headers;
|
|
44
45
|
cacheTtlMs;
|
|
@@ -46,7 +47,9 @@ export class Colony {
|
|
|
46
47
|
constructor(options = {}) {
|
|
47
48
|
this.url = (options.url ?? env("MYRMO_URL") ?? DEFAULT_URL).replace(/\/+$/, "");
|
|
48
49
|
this.apiKey = options.apiKey ?? env("MYRMO_API_KEY");
|
|
49
|
-
this.agentId = options.agentId
|
|
50
|
+
this.agentId = agentIdentity(options.agentId).id;
|
|
51
|
+
const model = (options.model ?? env("MYRMO_AGENT_MODEL"))?.trim();
|
|
52
|
+
this.model = model && model.toLowerCase() !== "unknown" ? model : undefined;
|
|
50
53
|
const choice = publishChoice(options.publish);
|
|
51
54
|
this.publishMode = choice.mode;
|
|
52
55
|
this.publishSource = choice.source;
|
|
@@ -54,14 +57,18 @@ export class Colony {
|
|
|
54
57
|
this.headers = options.headers ?? {};
|
|
55
58
|
this.cacheTtlMs = options.cacheTtlMs ?? 60_000;
|
|
56
59
|
}
|
|
57
|
-
async request(method, path, body) {
|
|
60
|
+
async request(method, path, body, extra) {
|
|
58
61
|
const headers = { accept: "application/json", "user-agent": `myrmo-js/${SDK_VERSION}`, ...this.headers };
|
|
59
62
|
if (body !== undefined)
|
|
60
63
|
headers["content-type"] = "application/json";
|
|
61
64
|
if (this.apiKey)
|
|
62
65
|
headers.authorization = `Bearer ${this.apiKey}`;
|
|
63
|
-
|
|
66
|
+
// A header the caller passed (a hosted server forwarding its users' ids) wins over this client's own.
|
|
67
|
+
if (this.agentId && !Object.keys(headers).some((k) => k.toLowerCase() === "x-myrmo-agent"))
|
|
64
68
|
headers["x-myrmo-agent"] = this.agentId;
|
|
69
|
+
if (this.model && !Object.keys(headers).some((k) => k.toLowerCase() === "x-myrmo-model"))
|
|
70
|
+
headers["x-myrmo-model"] = this.model;
|
|
71
|
+
Object.assign(headers, extra);
|
|
65
72
|
const res = await fetch(this.url + path, {
|
|
66
73
|
method,
|
|
67
74
|
headers,
|
|
@@ -93,11 +100,11 @@ export class Colony {
|
|
|
93
100
|
this.cache.set(key, { at: Date.now(), value });
|
|
94
101
|
}
|
|
95
102
|
/** Trails for a fingerprint, or `null` when the colony has none. */
|
|
96
|
-
async lookup(fp) {
|
|
103
|
+
async lookup(fp, model) {
|
|
97
104
|
const hit = this.cached(fp);
|
|
98
105
|
if (hit !== undefined)
|
|
99
106
|
return hit;
|
|
100
|
-
const { status, data } = await this.request("GET", `/v1/trails/by-fingerprint/${encodeURIComponent(fp)}
|
|
107
|
+
const { status, data } = await this.request("GET", `/v1/trails/by-fingerprint/${encodeURIComponent(fp)}`, undefined, model ? { "x-myrmo-model": model } : undefined);
|
|
101
108
|
const value = status === 404 ? null : { fingerprint: data.fingerprint, hits: data.results.map(toHit), notice: data.notice, source: "fingerprint" };
|
|
102
109
|
this.remember(fp, value);
|
|
103
110
|
return value;
|
|
@@ -107,7 +114,7 @@ export class Colony {
|
|
|
107
114
|
const error = redactText(query.error);
|
|
108
115
|
const errorType = query.errorType ?? guessErrorType(error);
|
|
109
116
|
const fp = fingerprint(query.runtime ?? "", errorType, error);
|
|
110
|
-
const exact = await this.lookup(fp);
|
|
117
|
+
const exact = await this.lookup(fp, query.model);
|
|
111
118
|
if (exact && exact.hits.length > 0)
|
|
112
119
|
return exact;
|
|
113
120
|
const environment = {
|
|
@@ -127,7 +134,7 @@ export class Colony {
|
|
|
127
134
|
environment,
|
|
128
135
|
limit: query.limit ?? 3,
|
|
129
136
|
min_strength: query.minStrength ?? 0,
|
|
130
|
-
});
|
|
137
|
+
}, query.model ? { "x-myrmo-model": query.model } : undefined);
|
|
131
138
|
const value = { fingerprint: data.fingerprint, hits: data.results.map(toHit), notice: data.notice, source: "search" };
|
|
132
139
|
this.remember(key, value);
|
|
133
140
|
return value;
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PublishMode } from "./types.js";
|
|
2
2
|
export interface MyrmoConfig {
|
|
3
3
|
publish?: PublishMode;
|
|
4
|
+
agent_id?: string;
|
|
4
5
|
}
|
|
5
6
|
/** `MYRMO_CONFIG` if set, else `~/.myrmo/config.json`. */
|
|
6
7
|
export declare function configPath(): string;
|
|
@@ -14,3 +15,13 @@ export declare function publishChoice(explicit?: PublishMode): {
|
|
|
14
15
|
mode: PublishMode;
|
|
15
16
|
source: "option" | "env" | "file" | "default";
|
|
16
17
|
};
|
|
18
|
+
export type AgentIdSource = "option" | "env" | "file" | "generated" | "none";
|
|
19
|
+
/**
|
|
20
|
+
* Who this client says it is. Nothing to configure: the first run creates a random id and keeps it in
|
|
21
|
+
* the config file. `MYRMO_AGENT_ID` (or the `agentId` option) overrides it, `false` or
|
|
22
|
+
* `MYRMO_ANONYMOUS=1` sends none. If the file cannot be written the id lives for this process only.
|
|
23
|
+
*/
|
|
24
|
+
export declare function agentIdentity(explicit?: string | false): {
|
|
25
|
+
id?: string;
|
|
26
|
+
source: AgentIdSource;
|
|
27
|
+
};
|
package/dist/config.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
// The user's own Myrmo settings, kept in one small file that the TypeScript and Python
|
|
2
2
|
// clients and the MCP server all read:
|
|
3
3
|
//
|
|
4
|
-
// ~/.myrmo/config.json { "publish": "auto" }
|
|
4
|
+
// ~/.myrmo/config.json { "publish": "auto", "agent_id": "3f9c0a7e2b1d4c68a5e0b7d91c2f4a86" }
|
|
5
5
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
6
|
+
// `publish` is a choice only a person should make (whether agents may publish for them), so it is
|
|
7
|
+
// written by `myrmo-mcp config` or by the user's answer to a prompt, never by a model.
|
|
8
|
+
// `agent_id` is a random pseudonym the client creates by itself the first time it runs, so that
|
|
9
|
+
// reports from different machines behind one address are not mistaken for a single agent. It holds
|
|
10
|
+
// no personal data and can be deleted at any time.
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
8
12
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
13
|
import { homedir } from "node:os";
|
|
10
14
|
import { dirname, join } from "node:path";
|
|
15
|
+
const AGENT_ID = /^[A-Za-z0-9_-]{8,64}$/;
|
|
11
16
|
const MODES = ["off", "ask", "auto"];
|
|
12
17
|
/** `MYRMO_CONFIG` if set, else `~/.myrmo/config.json`. */
|
|
13
18
|
export function configPath() {
|
|
@@ -16,8 +21,12 @@ export function configPath() {
|
|
|
16
21
|
export function readConfig() {
|
|
17
22
|
try {
|
|
18
23
|
const raw = JSON.parse(readFileSync(configPath(), "utf8"));
|
|
19
|
-
const
|
|
20
|
-
|
|
24
|
+
const config = {};
|
|
25
|
+
if (MODES.includes(raw.publish))
|
|
26
|
+
config.publish = raw.publish;
|
|
27
|
+
if (typeof raw.agent_id === "string" && AGENT_ID.test(raw.agent_id))
|
|
28
|
+
config.agent_id = raw.agent_id;
|
|
29
|
+
return config;
|
|
21
30
|
}
|
|
22
31
|
catch {
|
|
23
32
|
// Missing or unreadable: no choice has been made.
|
|
@@ -45,3 +54,30 @@ export function publishChoice(explicit) {
|
|
|
45
54
|
return { mode: fromFile, source: "file" };
|
|
46
55
|
return { mode: "off", source: "default" };
|
|
47
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Who this client says it is. Nothing to configure: the first run creates a random id and keeps it in
|
|
59
|
+
* the config file. `MYRMO_AGENT_ID` (or the `agentId` option) overrides it, `false` or
|
|
60
|
+
* `MYRMO_ANONYMOUS=1` sends none. If the file cannot be written the id lives for this process only.
|
|
61
|
+
*/
|
|
62
|
+
export function agentIdentity(explicit) {
|
|
63
|
+
if (explicit === false)
|
|
64
|
+
return { source: "none" };
|
|
65
|
+
if (explicit)
|
|
66
|
+
return { id: explicit, source: "option" };
|
|
67
|
+
const fromEnv = process.env.MYRMO_AGENT_ID?.trim();
|
|
68
|
+
if (fromEnv)
|
|
69
|
+
return { id: fromEnv, source: "env" };
|
|
70
|
+
if (["1", "true"].includes(process.env.MYRMO_ANONYMOUS?.trim().toLowerCase() ?? ""))
|
|
71
|
+
return { source: "none" };
|
|
72
|
+
const stored = readConfig().agent_id;
|
|
73
|
+
if (stored)
|
|
74
|
+
return { id: stored, source: "file" };
|
|
75
|
+
const id = randomUUID().replace(/-/g, "");
|
|
76
|
+
try {
|
|
77
|
+
writeConfig({ agent_id: id });
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// A read-only home directory: the id then lasts as long as this process.
|
|
81
|
+
}
|
|
82
|
+
return { id, source: "generated" };
|
|
83
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { Colony, MyrmoError, DEFAULT_URL, SDK_VERSION, type ColonyOptions } from
|
|
|
2
2
|
export { Session, type SessionOptions, type Success, type Hints } from "./session.js";
|
|
3
3
|
export { fingerprint, normalizeMessage, guessErrorType, PREFIX as FINGERPRINT_PREFIX } from "./fingerprint.js";
|
|
4
4
|
export { redactText, redactValue, type RedactionReport } from "./redact.js";
|
|
5
|
-
export { configPath, publishChoice, readConfig, writeConfig, type MyrmoConfig } from "./config.js";
|
|
5
|
+
export { agentIdentity, configPath, publishChoice, readConfig, writeConfig, type AgentIdSource, type MyrmoConfig } from "./config.js";
|
|
6
6
|
export { detectEnvironment, parsePackage } from "./environment.js";
|
|
7
7
|
export { formatResult, type FormatOptions } from "./format.js";
|
|
8
8
|
export type * from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { Colony, MyrmoError, DEFAULT_URL, SDK_VERSION } from "./client.js";
|
|
|
2
2
|
export { Session } from "./session.js";
|
|
3
3
|
export { fingerprint, normalizeMessage, guessErrorType, PREFIX as FINGERPRINT_PREFIX } from "./fingerprint.js";
|
|
4
4
|
export { redactText, redactValue } from "./redact.js";
|
|
5
|
-
export { configPath, publishChoice, readConfig, writeConfig } from "./config.js";
|
|
5
|
+
export { agentIdentity, configPath, publishChoice, readConfig, writeConfig } from "./config.js";
|
|
6
6
|
export { detectEnvironment, parsePackage } from "./environment.js";
|
|
7
7
|
export { formatResult } from "./format.js";
|
|
8
8
|
import { Colony } from "./client.js";
|
package/dist/types.d.ts
CHANGED
|
@@ -124,6 +124,8 @@ export interface SearchQuery {
|
|
|
124
124
|
packages?: (string | Package)[];
|
|
125
125
|
limit?: number;
|
|
126
126
|
minStrength?: number;
|
|
127
|
+
/** The model asking, for aggregate counters. Overrides the client's own `model` for this search. */
|
|
128
|
+
model?: string;
|
|
127
129
|
}
|
|
128
130
|
/** A trail held by the colony until a person approves it in a browser. */
|
|
129
131
|
export interface DraftResult {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myrmo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.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",
|