@memnexus-ai/mx-agent-cli 0.1.208 → 0.1.209
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/__tests__/startup-health-render.test.d.ts +11 -0
- package/dist/__tests__/startup-health-render.test.d.ts.map +1 -0
- package/dist/__tests__/startup-health-render.test.js +60 -0
- package/dist/__tests__/startup-health-render.test.js.map +1 -0
- package/dist/__tests__/startup-report.test.js +34 -1
- package/dist/__tests__/startup-report.test.js.map +1 -1
- package/dist/__tests__/startup-shipper.test.d.ts +13 -0
- package/dist/__tests__/startup-shipper.test.d.ts.map +1 -0
- package/dist/__tests__/startup-shipper.test.js +149 -0
- package/dist/__tests__/startup-shipper.test.js.map +1 -0
- package/dist/commands/dashboard.d.ts +21 -0
- package/dist/commands/dashboard.d.ts.map +1 -1
- package/dist/commands/dashboard.js +72 -1
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +16 -0
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +17 -0
- package/dist/commands/sync.js.map +1 -1
- package/dist/lib/startup-report.d.ts +19 -3
- package/dist/lib/startup-report.d.ts.map +1 -1
- package/dist/lib/startup-report.js +66 -8
- package/dist/lib/startup-report.js.map +1 -1
- package/dist/lib/startup-shipper.d.ts +55 -0
- package/dist/lib/startup-shipper.d.ts.map +1 -0
- package/dist/lib/startup-shipper.js +177 -0
- package/dist/lib/startup-shipper.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* startup-shipper.ts — ships StartupReports to the central observability
|
|
3
|
+
* service (platform v2.58 Phase 2, #4350).
|
|
4
|
+
*
|
|
5
|
+
* Durability model mirrors the local JSONL from Phase 1: every report is first
|
|
6
|
+
* written to a spool dir (~/.mx-agent/logs/startup/spool/<uuid>.json), then a
|
|
7
|
+
* best-effort POST deletes it on success. A failed or token-less POST leaves the
|
|
8
|
+
* file spooled; the Stop-hook `mx-agent sync` flushes the backlog on the next
|
|
9
|
+
* session end.
|
|
10
|
+
*
|
|
11
|
+
* Discipline (identical to startup-report.ts): NOTHING here throws. Shipping is
|
|
12
|
+
* additive telemetry — it must never block, slow, or fail a session start. Every
|
|
13
|
+
* filesystem and network call is wrapped; the worst outcome of any failure is a
|
|
14
|
+
* report that stays spooled for a later retry.
|
|
15
|
+
*
|
|
16
|
+
* - spoolReport(json) — write one report to the spool. Drops files older
|
|
17
|
+
* than 14 days at write time (bounded disk/staleness).
|
|
18
|
+
* - flushSpool(cfg) — POST every spooled report, deleting each on 2xx.
|
|
19
|
+
* Enforces a 50-file cap (drops oldest beyond it) and
|
|
20
|
+
* skips entirely when no token is configured.
|
|
21
|
+
* - postSpoolFile(p, cfg) — POST one spool file with a 3 s timeout.
|
|
22
|
+
*/
|
|
23
|
+
/** Absolute path to the spool directory. Computed at call time so tests that
|
|
24
|
+
* repoint $HOME observe the change (matches startup-report.ts). */
|
|
25
|
+
export declare function spoolDir(): string;
|
|
26
|
+
export interface ShipConfig {
|
|
27
|
+
apiUrl: string;
|
|
28
|
+
apiToken?: string;
|
|
29
|
+
/** Optional dim-note logger (sync passes one; startup passes nothing). */
|
|
30
|
+
log?: (msg: string) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Write a report JSON string to the spool. Never throws. Returns the file path,
|
|
34
|
+
* or null if the write could not be performed (e.g. a read-only spool dir).
|
|
35
|
+
* Drops files older than 14 days first.
|
|
36
|
+
*/
|
|
37
|
+
export declare function spoolReport(reportJson: string): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* POST one spool file to the observability service. Returns true and deletes the
|
|
40
|
+
* file on a 2xx response. Any non-2xx, network error, or timeout returns false
|
|
41
|
+
* and leaves the file spooled. Never throws.
|
|
42
|
+
*/
|
|
43
|
+
export declare function postSpoolFile(path: string, cfg: ShipConfig): Promise<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* Flush the spool: POST every spooled report, deleting each on success.
|
|
46
|
+
*
|
|
47
|
+
* Enforces the 50-file cap first (drops the oldest beyond it, with a dim note if
|
|
48
|
+
* a logger is provided). If no token is configured the whole flush is skipped —
|
|
49
|
+
* files stay spooled so a later run with a token can retry. Never throws.
|
|
50
|
+
*/
|
|
51
|
+
export declare function flushSpool(cfg: ShipConfig): Promise<{
|
|
52
|
+
shipped: number;
|
|
53
|
+
remaining: number;
|
|
54
|
+
}>;
|
|
55
|
+
//# sourceMappingURL=startup-shipper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startup-shipper.d.ts","sourceRoot":"","sources":["../../src/lib/startup-shipper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAcH;oEACoE;AACpE,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAY7D;AAwCD;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CA8BnF;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAgCjG"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* startup-shipper.ts — ships StartupReports to the central observability
|
|
3
|
+
* service (platform v2.58 Phase 2, #4350).
|
|
4
|
+
*
|
|
5
|
+
* Durability model mirrors the local JSONL from Phase 1: every report is first
|
|
6
|
+
* written to a spool dir (~/.mx-agent/logs/startup/spool/<uuid>.json), then a
|
|
7
|
+
* best-effort POST deletes it on success. A failed or token-less POST leaves the
|
|
8
|
+
* file spooled; the Stop-hook `mx-agent sync` flushes the backlog on the next
|
|
9
|
+
* session end.
|
|
10
|
+
*
|
|
11
|
+
* Discipline (identical to startup-report.ts): NOTHING here throws. Shipping is
|
|
12
|
+
* additive telemetry — it must never block, slow, or fail a session start. Every
|
|
13
|
+
* filesystem and network call is wrapped; the worst outcome of any failure is a
|
|
14
|
+
* report that stays spooled for a later retry.
|
|
15
|
+
*
|
|
16
|
+
* - spoolReport(json) — write one report to the spool. Drops files older
|
|
17
|
+
* than 14 days at write time (bounded disk/staleness).
|
|
18
|
+
* - flushSpool(cfg) — POST every spooled report, deleting each on 2xx.
|
|
19
|
+
* Enforces a 50-file cap (drops oldest beyond it) and
|
|
20
|
+
* skips entirely when no token is configured.
|
|
21
|
+
* - postSpoolFile(p, cfg) — POST one spool file with a 3 s timeout.
|
|
22
|
+
*/
|
|
23
|
+
import { mkdirSync, writeFileSync, readdirSync, statSync, unlinkSync, readFileSync } from 'fs';
|
|
24
|
+
import { homedir } from 'os';
|
|
25
|
+
import { join } from 'path';
|
|
26
|
+
import { randomUUID } from 'crypto';
|
|
27
|
+
/** Drop spooled files older than this at write time (bounded disk / staleness). */
|
|
28
|
+
const MAX_SPOOL_AGE_MS = 14 * 24 * 60 * 60 * 1000; // 14 days
|
|
29
|
+
/** Flush keeps at most this many files; the oldest beyond it are dropped. */
|
|
30
|
+
const MAX_SPOOL_FILES = 50;
|
|
31
|
+
/** Per-POST timeout — a single ship attempt must never stall longer than this. */
|
|
32
|
+
const POST_TIMEOUT_MS = 3000;
|
|
33
|
+
/** Absolute path to the spool directory. Computed at call time so tests that
|
|
34
|
+
* repoint $HOME observe the change (matches startup-report.ts). */
|
|
35
|
+
export function spoolDir() {
|
|
36
|
+
return join(homedir(), '.mx-agent', 'logs', 'startup', 'spool');
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Write a report JSON string to the spool. Never throws. Returns the file path,
|
|
40
|
+
* or null if the write could not be performed (e.g. a read-only spool dir).
|
|
41
|
+
* Drops files older than 14 days first.
|
|
42
|
+
*/
|
|
43
|
+
export function spoolReport(reportJson) {
|
|
44
|
+
try {
|
|
45
|
+
const dir = spoolDir();
|
|
46
|
+
mkdirSync(dir, { recursive: true });
|
|
47
|
+
dropAgedFiles(dir);
|
|
48
|
+
const path = join(dir, `${randomUUID()}.json`);
|
|
49
|
+
writeFileSync(path, reportJson);
|
|
50
|
+
return path;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Spooling is best-effort — a failure here is silently tolerated.
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Remove spool files whose mtime is older than the age cap. Never throws. */
|
|
58
|
+
function dropAgedFiles(dir) {
|
|
59
|
+
try {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
for (const f of readdirSync(dir)) {
|
|
62
|
+
if (!f.endsWith('.json'))
|
|
63
|
+
continue;
|
|
64
|
+
const p = join(dir, f);
|
|
65
|
+
try {
|
|
66
|
+
if (now - statSync(p).mtimeMs > MAX_SPOOL_AGE_MS)
|
|
67
|
+
unlinkSync(p);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* individual file error — skip it */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
/* dir unreadable / absent — nothing to age out */
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** List spool files (basenames), oldest-first by mtime. Never throws. */
|
|
79
|
+
function listSpoolFilesOldestFirst(dir) {
|
|
80
|
+
let names;
|
|
81
|
+
try {
|
|
82
|
+
names = readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
return names
|
|
88
|
+
.map((f) => {
|
|
89
|
+
try {
|
|
90
|
+
return { f, m: statSync(join(dir, f)).mtimeMs };
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { f, m: 0 };
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
.sort((a, b) => a.m - b.m)
|
|
97
|
+
.map((x) => x.f);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* POST one spool file to the observability service. Returns true and deletes the
|
|
101
|
+
* file on a 2xx response. Any non-2xx, network error, or timeout returns false
|
|
102
|
+
* and leaves the file spooled. Never throws.
|
|
103
|
+
*/
|
|
104
|
+
export async function postSpoolFile(path, cfg) {
|
|
105
|
+
if (!cfg.apiToken)
|
|
106
|
+
return false; // no token → cannot ship
|
|
107
|
+
let body;
|
|
108
|
+
try {
|
|
109
|
+
body = readFileSync(path, 'utf-8');
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return false; // file vanished (concurrent flush) — nothing to do
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const res = await fetch(`${cfg.apiUrl}/v1/startup-reports`, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
Authorization: `Bearer ${cfg.apiToken}`,
|
|
120
|
+
},
|
|
121
|
+
body,
|
|
122
|
+
signal: AbortSignal.timeout(POST_TIMEOUT_MS),
|
|
123
|
+
});
|
|
124
|
+
if (res.ok) {
|
|
125
|
+
try {
|
|
126
|
+
unlinkSync(path);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
/* already removed — fine */
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return false; // 4xx/5xx — leave spooled for a later retry
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false; // network / timeout / abort — leave spooled
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Flush the spool: POST every spooled report, deleting each on success.
|
|
141
|
+
*
|
|
142
|
+
* Enforces the 50-file cap first (drops the oldest beyond it, with a dim note if
|
|
143
|
+
* a logger is provided). If no token is configured the whole flush is skipped —
|
|
144
|
+
* files stay spooled so a later run with a token can retry. Never throws.
|
|
145
|
+
*/
|
|
146
|
+
export async function flushSpool(cfg) {
|
|
147
|
+
const dir = spoolDir();
|
|
148
|
+
let files = listSpoolFilesOldestFirst(dir);
|
|
149
|
+
// 50-file cap: drop the oldest beyond the cap so disk stays bounded.
|
|
150
|
+
if (files.length > MAX_SPOOL_FILES) {
|
|
151
|
+
const drop = files.slice(0, files.length - MAX_SPOOL_FILES);
|
|
152
|
+
for (const f of drop) {
|
|
153
|
+
try {
|
|
154
|
+
unlinkSync(join(dir, f));
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
/* ignore */
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
if (cfg.log) {
|
|
161
|
+
cfg.log(`startup-report spool exceeded ${MAX_SPOOL_FILES} files — dropped ${drop.length} oldest`);
|
|
162
|
+
}
|
|
163
|
+
files = files.slice(files.length - MAX_SPOOL_FILES);
|
|
164
|
+
}
|
|
165
|
+
// No token → cannot ship anything. Leave the backlog for a later sync.
|
|
166
|
+
if (!cfg.apiToken) {
|
|
167
|
+
return { shipped: 0, remaining: files.length };
|
|
168
|
+
}
|
|
169
|
+
let shipped = 0;
|
|
170
|
+
for (const f of files) {
|
|
171
|
+
if (await postSpoolFile(join(dir, f), cfg))
|
|
172
|
+
shipped++;
|
|
173
|
+
}
|
|
174
|
+
const remaining = listSpoolFilesOldestFirst(dir).length;
|
|
175
|
+
return { shipped, remaining };
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=startup-shipper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"startup-shipper.js","sourceRoot":"","sources":["../../src/lib/startup-shipper.ts"],"names":[],"mappings":"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"}
|