@extenshi/mcp 0.22.1 → 0.24.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 +3 -0
- package/dist/build-map-agent.d.ts +128 -0
- package/dist/build-map-agent.d.ts.map +1 -0
- package/dist/build-map-agent.js +270 -0
- package/dist/build-map-agent.js.map +1 -0
- package/dist/build-map-store.d.ts +47 -5
- package/dist/build-map-store.d.ts.map +1 -1
- package/dist/build-map-store.js +75 -7
- package/dist/build-map-store.js.map +1 -1
- package/dist/build-map-summary.d.ts +3 -1
- package/dist/build-map-summary.d.ts.map +1 -1
- package/dist/build-map-summary.js +3 -2
- package/dist/build-map-summary.js.map +1 -1
- package/dist/build-map-tools.d.ts +55 -0
- package/dist/build-map-tools.d.ts.map +1 -0
- package/dist/build-map-tools.js +177 -0
- package/dist/build-map-tools.js.map +1 -0
- package/dist/development-guide.d.ts +28 -21
- package/dist/development-guide.d.ts.map +1 -1
- package/dist/development-guide.js +14 -4
- package/dist/development-guide.js.map +1 -1
- package/dist/feedback-form-tools.d.ts +4 -4
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/publish.d.ts +11 -0
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +13 -0
- package/dist/publish.js.map +1 -1
- package/dist/tools.d.ts +9 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +157 -1
- package/dist/tools.js.map +1 -1
- package/dist/uninstall-form-tools.d.ts +4 -4
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -124,6 +124,9 @@ stdio or CLI. See the [development workflow](https://docs.extenshi.io/developers
|
|
|
124
124
|
| `generate_landing_page` | Static landing-page (homepage) HTML with no JavaScript — same generator as the cabinet Page generator; returns `{html, bytes, warnings, nextSteps}`. Host it on any HTTPS origin and register the URL with `upsert_hosted_page` (feeds `HOMEPAGE_URL`) | Free (no key) |
|
|
125
125
|
| `scan_extension` | Pre-publish security scan of a local artifact (.zip/.crx/.xpi), with live progress | 1 scan |
|
|
126
126
|
| `publish_extension` | Publish to Chrome/Firefox/Edge with your own store credentials (fully local) | Free |
|
|
127
|
+
| `get_build_map` | The author's build map of `projectDir` (`npx @extenshi/cli map`): progress, steps waiting on the author, steps that can start now, every step's current task, and the variants offered with the author's answer. Local server only | Free (no key) |
|
|
128
|
+
| `report_build_step` | Report on a build map step (start, progress, done with hashed file proof, stop), or record the author's answer from the chat (`choice` / `redo`). Local server only | Free (no key) |
|
|
129
|
+
| `request_build_review` | Offer the author 1–8 variants to choose from on the map (icon concepts, screenshots, listing copy), each a preview page or image in the project; returns the link to the step. Local server only | Free (no key) |
|
|
127
130
|
|
|
128
131
|
### Identifying an extension by its store id
|
|
129
132
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an agent does on the build map (plan 2026-11-03-extension-build-map
|
|
3
|
+
* §6.3–6.4, phase 2): report a step, offer the author variants to choose from,
|
|
4
|
+
* record the author's answer from the chat, and wait for the author's answer.
|
|
5
|
+
*
|
|
6
|
+
* The CLI (`extenshi map step|review|choose|wait`) and the stdio MCP server
|
|
7
|
+
* (`report_build_step`, `request_build_review`, `get_build_map`) both call
|
|
8
|
+
* these. Every write goes through updateMapState, so it lands in the one map of
|
|
9
|
+
* the project from whichever worktree the agent works in, and an open page shows
|
|
10
|
+
* it within a second. The MCP server ships a byte-identical copy of this file
|
|
11
|
+
* next to its copy of build-map-store.ts; build-map-store-sync.test.ts there
|
|
12
|
+
* fails on any drift. Edit this file, then copy it over.
|
|
13
|
+
*
|
|
14
|
+
* Files an agent names are resolved here. Evidence files are hashed, so the
|
|
15
|
+
* journal entry is `checked`. A variant's preview and image must be files of
|
|
16
|
+
* the project, in the main checkout or any of its worktrees, or https
|
|
17
|
+
* addresses. The map server serves those files only for a variant that names
|
|
18
|
+
* them (reviewFileOnDisk), so naming a file elsewhere is refused up front.
|
|
19
|
+
*/
|
|
20
|
+
import { type BuildEvidenceWhere, type BuildMapView, type BuildNodeRecord, type BuildReview, type BuildStepReport } from '@extenshi/contracts';
|
|
21
|
+
import { type MapClient, type RunningServer } from './build-map-store.js';
|
|
22
|
+
/** The file types a variant's preview or image may be, with the type the map server answers with. */
|
|
23
|
+
export declare const REVIEW_FILE_TYPES: Readonly<Record<string, string>>;
|
|
24
|
+
export declare const REVIEW_FILE_MAX_BYTES: number;
|
|
25
|
+
/**
|
|
26
|
+
* Where a variant's preview or image is, as the state file keeps it: an https
|
|
27
|
+
* address as given; a file relative to `home` when it is in the main checkout,
|
|
28
|
+
* absolute when it is in another checkout of the project. Throws for anything
|
|
29
|
+
* else, with what to do instead.
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolveReviewFile(home: string, from: string, value: string): Promise<string>;
|
|
32
|
+
/**
|
|
33
|
+
* The file behind a variant's preview or image, if the map may serve it: it
|
|
34
|
+
* is a file of a checkout of the project, of a type in REVIEW_FILE_TYPES, and
|
|
35
|
+
* not too big. Checked again at every request, since files change.
|
|
36
|
+
*/
|
|
37
|
+
export declare function reviewFileOnDisk(home: string, stored: string): Promise<{
|
|
38
|
+
file: string;
|
|
39
|
+
type: string;
|
|
40
|
+
bytes: number;
|
|
41
|
+
} | undefined>;
|
|
42
|
+
/** Evidence files hashed: relative to `home` when inside it, absolute otherwise. Throws for a file that is not there. */
|
|
43
|
+
export declare function evidenceFiles(home: string, from: string, files: readonly string[]): Promise<BuildEvidenceWhere[]>;
|
|
44
|
+
/**
|
|
45
|
+
* The map as the running map server derives it: with the project facts and
|
|
46
|
+
* the bound Dojo project. Undefined when no server runs or it does not answer.
|
|
47
|
+
* `client` tells the server which program asks (probeServer); `server` says
|
|
48
|
+
* which release and graph revision the map runs.
|
|
49
|
+
*/
|
|
50
|
+
export declare function serverSnapshot(home: string, client?: MapClient): Promise<{
|
|
51
|
+
url: string;
|
|
52
|
+
server: RunningServer;
|
|
53
|
+
snapshot: {
|
|
54
|
+
view: BuildMapView;
|
|
55
|
+
} & Record<string, unknown>;
|
|
56
|
+
} | undefined>;
|
|
57
|
+
/**
|
|
58
|
+
* What an agent should know about the release the author's map runs, in one
|
|
59
|
+
* sentence: the map draws older steps than the agent knows (switch it), newer
|
|
60
|
+
* ones (update the agent's own tool), or a newer release is out. Undefined
|
|
61
|
+
* when the two agree and nothing newer is known.
|
|
62
|
+
*/
|
|
63
|
+
export declare function mapUpdateNote(server: Pick<RunningServer, 'version' | 'graph' | 'available' | 'updating'>, client: MapClient): string | undefined;
|
|
64
|
+
/** The address that opens one step on the map. */
|
|
65
|
+
export declare const stepUrl: (url: string, node: string) => string;
|
|
66
|
+
export type StepInput = Omit<BuildStepReport, 'at' | 'where' | 'checked'> & {
|
|
67
|
+
/** Files that show the task done: hashed, so the entry is checked. */
|
|
68
|
+
files?: readonly string[];
|
|
69
|
+
urls?: readonly string[];
|
|
70
|
+
dojo?: readonly string[];
|
|
71
|
+
command?: string;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Report on a step. `view` checks that the step's inputs are done; without it
|
|
75
|
+
* (no project facts) nothing is refused for that. The entry is `checked` when
|
|
76
|
+
* every proof is a file this call hashed.
|
|
77
|
+
*/
|
|
78
|
+
export declare function reportStep(home: string, from: string, input: StepInput, view?: BuildMapView): Promise<{
|
|
79
|
+
record: BuildNodeRecord | undefined;
|
|
80
|
+
checked: boolean;
|
|
81
|
+
}>;
|
|
82
|
+
export type VariantInput = {
|
|
83
|
+
label: string;
|
|
84
|
+
preview: string;
|
|
85
|
+
image?: string;
|
|
86
|
+
};
|
|
87
|
+
/** Offer the author variants to choose from; files are resolved against `from`. */
|
|
88
|
+
export declare function openReview(home: string, from: string, input: {
|
|
89
|
+
node: string;
|
|
90
|
+
agent: string;
|
|
91
|
+
variants: readonly VariantInput[];
|
|
92
|
+
}, view?: BuildMapView): Promise<BuildReview>;
|
|
93
|
+
/** Record the author's answer from the chat: the variant they picked, or what to change. */
|
|
94
|
+
export declare function answerReview(home: string, input: {
|
|
95
|
+
node: string;
|
|
96
|
+
agent: string;
|
|
97
|
+
choice?: string;
|
|
98
|
+
redo?: string;
|
|
99
|
+
round?: number;
|
|
100
|
+
}, view?: BuildMapView): Promise<BuildReview>;
|
|
101
|
+
export type ReviewAnswer = {
|
|
102
|
+
node: string;
|
|
103
|
+
round: number;
|
|
104
|
+
status: BuildReview['status'];
|
|
105
|
+
/** The variant picked. */
|
|
106
|
+
choice?: {
|
|
107
|
+
id: string;
|
|
108
|
+
label: string;
|
|
109
|
+
preview: string;
|
|
110
|
+
image?: string;
|
|
111
|
+
};
|
|
112
|
+
/** What to change, in the author's words. */
|
|
113
|
+
feedback?: string;
|
|
114
|
+
via?: 'map' | 'chat';
|
|
115
|
+
};
|
|
116
|
+
export declare function reviewAnswer(node: string, review: BuildReview): ReviewAnswer;
|
|
117
|
+
/**
|
|
118
|
+
* Wait until the author answers the review of `node`, reading the state file
|
|
119
|
+
* every `pollMs` (no map server needed). Resolves at once when it is answered
|
|
120
|
+
* already; with `timeoutMs`, resolves with the review still waiting once that
|
|
121
|
+
* passes.
|
|
122
|
+
*/
|
|
123
|
+
export declare function waitForReview(home: string, node: string, options?: {
|
|
124
|
+
timeoutMs?: number;
|
|
125
|
+
pollMs?: number;
|
|
126
|
+
signal?: AbortSignal;
|
|
127
|
+
}): Promise<ReviewAnswer>;
|
|
128
|
+
//# sourceMappingURL=build-map-agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-map-agent.d.ts","sourceRoot":"","sources":["../src/build-map-agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an agent does on the build map (plan 2026-11-03-extension-build-map
|
|
3
|
+
* §6.3–6.4, phase 2): report a step, offer the author variants to choose from,
|
|
4
|
+
* record the author's answer from the chat, and wait for the author's answer.
|
|
5
|
+
*
|
|
6
|
+
* The CLI (`extenshi map step|review|choose|wait`) and the stdio MCP server
|
|
7
|
+
* (`report_build_step`, `request_build_review`, `get_build_map`) both call
|
|
8
|
+
* these. Every write goes through updateMapState, so it lands in the one map of
|
|
9
|
+
* the project from whichever worktree the agent works in, and an open page shows
|
|
10
|
+
* it within a second. The MCP server ships a byte-identical copy of this file
|
|
11
|
+
* next to its copy of build-map-store.ts; build-map-store-sync.test.ts there
|
|
12
|
+
* fails on any drift. Edit this file, then copy it over.
|
|
13
|
+
*
|
|
14
|
+
* Files an agent names are resolved here. Evidence files are hashed, so the
|
|
15
|
+
* journal entry is `checked`. A variant's preview and image must be files of
|
|
16
|
+
* the project, in the main checkout or any of its worktrees, or https
|
|
17
|
+
* addresses. The map server serves those files only for a variant that names
|
|
18
|
+
* them (reviewFileOnDisk), so naming a file elsewhere is refused up front.
|
|
19
|
+
*/
|
|
20
|
+
import { createHash } from 'node:crypto';
|
|
21
|
+
import { createReadStream } from 'node:fs';
|
|
22
|
+
import { realpath, stat } from 'node:fs/promises';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { answerBuildReview, buildReviewLocationValid, openBuildReview, reportBuildStep, } from '@extenshi/contracts';
|
|
25
|
+
import { mapSkew, probeServer, projectCheckouts, readMapState, updateMapState, } from './build-map-store.js';
|
|
26
|
+
/** The file types a variant's preview or image may be, with the type the map server answers with. */
|
|
27
|
+
export const REVIEW_FILE_TYPES = {
|
|
28
|
+
'.html': 'text/html; charset=utf-8',
|
|
29
|
+
'.htm': 'text/html; charset=utf-8',
|
|
30
|
+
'.svg': 'image/svg+xml',
|
|
31
|
+
'.png': 'image/png',
|
|
32
|
+
'.jpg': 'image/jpeg',
|
|
33
|
+
'.jpeg': 'image/jpeg',
|
|
34
|
+
'.webp': 'image/webp',
|
|
35
|
+
'.gif': 'image/gif',
|
|
36
|
+
};
|
|
37
|
+
export const REVIEW_FILE_MAX_BYTES = 20 * 1024 * 1024;
|
|
38
|
+
/** Evidence files bigger than this are refused rather than hashed for minutes. */
|
|
39
|
+
const EVIDENCE_FILE_MAX_BYTES = 64 * 1024 * 1024;
|
|
40
|
+
const isHttps = (value) => /^https:\/\//i.test(value);
|
|
41
|
+
const hasScheme = (value) => /^[a-z][a-z0-9+.-]*:\/\//i.test(value);
|
|
42
|
+
function inside(root, file) {
|
|
43
|
+
const relative = path.relative(root, file);
|
|
44
|
+
return relative !== '' && !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
45
|
+
}
|
|
46
|
+
/** The project directory in every checkout of the repository, resolved: the map home first. */
|
|
47
|
+
async function checkoutRoots(home) {
|
|
48
|
+
const roots = [];
|
|
49
|
+
for (const dir of await projectCheckouts(home)) {
|
|
50
|
+
try {
|
|
51
|
+
const real = await realpath(dir);
|
|
52
|
+
if (!roots.includes(real))
|
|
53
|
+
roots.push(real);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// A worktree removed since git listed it.
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return roots.length ? roots : [home];
|
|
60
|
+
}
|
|
61
|
+
async function sha256Of(file) {
|
|
62
|
+
const hash = createHash('sha256');
|
|
63
|
+
for await (const chunk of createReadStream(file))
|
|
64
|
+
hash.update(chunk);
|
|
65
|
+
return hash.digest('hex');
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Where a variant's preview or image is, as the state file keeps it: an https
|
|
69
|
+
* address as given; a file relative to `home` when it is in the main checkout,
|
|
70
|
+
* absolute when it is in another checkout of the project. Throws for anything
|
|
71
|
+
* else, with what to do instead.
|
|
72
|
+
*/
|
|
73
|
+
export async function resolveReviewFile(home, from, value) {
|
|
74
|
+
const given = value.trim();
|
|
75
|
+
if (hasScheme(given)) {
|
|
76
|
+
if (isHttps(given) && buildReviewLocationValid(given))
|
|
77
|
+
return given;
|
|
78
|
+
throw new Error(`${given}: a preview is a file of the project or an https address.`);
|
|
79
|
+
}
|
|
80
|
+
let real;
|
|
81
|
+
try {
|
|
82
|
+
real = await realpath(path.resolve(from, given));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
throw new Error(`${given}: no such file.`);
|
|
86
|
+
}
|
|
87
|
+
const type = REVIEW_FILE_TYPES[path.extname(real).toLowerCase()];
|
|
88
|
+
if (!type)
|
|
89
|
+
throw new Error(`${given}: a preview is an HTML page or an image (${Object.keys(REVIEW_FILE_TYPES).join(', ')}).`);
|
|
90
|
+
const info = await stat(real);
|
|
91
|
+
if (!info.isFile())
|
|
92
|
+
throw new Error(`${given}: not a file.`);
|
|
93
|
+
if (info.size > REVIEW_FILE_MAX_BYTES)
|
|
94
|
+
throw new Error(`${given}: larger than ${REVIEW_FILE_MAX_BYTES / 1024 / 1024} MB.`);
|
|
95
|
+
if (inside(home, real))
|
|
96
|
+
return path.relative(home, real);
|
|
97
|
+
// Asking git for the other checkouts costs a process or two: only for a file outside the main one.
|
|
98
|
+
if ((await checkoutRoots(home)).some((root) => inside(root, real)))
|
|
99
|
+
return real;
|
|
100
|
+
throw new Error(`${given}: outside the project, so the map will not serve it. Write the preview inside the project (any of its git worktrees works).`);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The file behind a variant's preview or image, if the map may serve it: it
|
|
104
|
+
* is a file of a checkout of the project, of a type in REVIEW_FILE_TYPES, and
|
|
105
|
+
* not too big. Checked again at every request, since files change.
|
|
106
|
+
*/
|
|
107
|
+
export async function reviewFileOnDisk(home, stored) {
|
|
108
|
+
if (!buildReviewLocationValid(stored) || hasScheme(stored))
|
|
109
|
+
return undefined;
|
|
110
|
+
try {
|
|
111
|
+
const real = await realpath(path.isAbsolute(stored) ? stored : path.join(home, stored));
|
|
112
|
+
const type = REVIEW_FILE_TYPES[path.extname(real).toLowerCase()];
|
|
113
|
+
if (!type)
|
|
114
|
+
return undefined;
|
|
115
|
+
const info = await stat(real);
|
|
116
|
+
if (!info.isFile() || info.size > REVIEW_FILE_MAX_BYTES)
|
|
117
|
+
return undefined;
|
|
118
|
+
if (!inside(home, real) && !(await checkoutRoots(home)).some((root) => inside(root, real)))
|
|
119
|
+
return undefined;
|
|
120
|
+
return { file: real, type, bytes: info.size };
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Evidence files hashed: relative to `home` when inside it, absolute otherwise. Throws for a file that is not there. */
|
|
127
|
+
export async function evidenceFiles(home, from, files) {
|
|
128
|
+
const where = [];
|
|
129
|
+
for (const given of files) {
|
|
130
|
+
const absolute = path.resolve(from, given);
|
|
131
|
+
let info;
|
|
132
|
+
try {
|
|
133
|
+
info = await stat(absolute);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
throw new Error(`${given}: no such file.`);
|
|
137
|
+
}
|
|
138
|
+
if (!info.isFile())
|
|
139
|
+
throw new Error(`${given}: not a file.`);
|
|
140
|
+
if (info.size > EVIDENCE_FILE_MAX_BYTES)
|
|
141
|
+
throw new Error(`${given}: larger than ${EVIDENCE_FILE_MAX_BYTES / 1024 / 1024} MB; name a smaller file as proof.`);
|
|
142
|
+
const real = await realpath(absolute);
|
|
143
|
+
where.push({ file: inside(home, real) ? path.relative(home, real) : real, sha256: await sha256Of(real) });
|
|
144
|
+
}
|
|
145
|
+
return where;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The map as the running map server derives it: with the project facts and
|
|
149
|
+
* the bound Dojo project. Undefined when no server runs or it does not answer.
|
|
150
|
+
* `client` tells the server which program asks (probeServer); `server` says
|
|
151
|
+
* which release and graph revision the map runs.
|
|
152
|
+
*/
|
|
153
|
+
export async function serverSnapshot(home, client) {
|
|
154
|
+
const server = await probeServer(home, client);
|
|
155
|
+
if (!server)
|
|
156
|
+
return undefined;
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetch(`http://127.0.0.1:${server.port}/api/map`, {
|
|
159
|
+
signal: AbortSignal.timeout(3_000),
|
|
160
|
+
});
|
|
161
|
+
if (!response.ok)
|
|
162
|
+
return undefined;
|
|
163
|
+
const snapshot = (await response.json());
|
|
164
|
+
return snapshot.view
|
|
165
|
+
? { url: server.url, server, snapshot: snapshot }
|
|
166
|
+
: undefined;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* What an agent should know about the release the author's map runs, in one
|
|
174
|
+
* sentence: the map draws older steps than the agent knows (switch it), newer
|
|
175
|
+
* ones (update the agent's own tool), or a newer release is out. Undefined
|
|
176
|
+
* when the two agree and nothing newer is known.
|
|
177
|
+
*/
|
|
178
|
+
export function mapUpdateNote(server, client) {
|
|
179
|
+
const tool = client.kind === 'cli' ? 'this CLI' : 'this MCP server';
|
|
180
|
+
const switchIt = client.kind === 'cli' ? '`extenshi map update`' : '`npx @extenshi/cli@latest map update` in the project';
|
|
181
|
+
if (server.updating)
|
|
182
|
+
return `The map is restarting on @extenshi/cli ${server.updating}; the author's page reloads by itself.`;
|
|
183
|
+
const skew = mapSkew(server, client);
|
|
184
|
+
if (skew === 'map_older' && client.graph > server.graph)
|
|
185
|
+
return `The author's map draws an older set of steps (revision ${server.graph}) than ${tool} knows (revision ${client.graph}). Run ${switchIt} to restart it on the newer steps when no choice waits on the author; their progress stays.`;
|
|
186
|
+
if (skew === 'map_older')
|
|
187
|
+
return `The author's map runs @extenshi/cli ${server.version}, older than this CLI (${client.version}). Run ${switchIt} to restart it on this release; the author's progress stays.`;
|
|
188
|
+
if (skew === 'client_older')
|
|
189
|
+
return client.kind === 'cli'
|
|
190
|
+
? `The author's map draws newer steps (revision ${server.graph}) than this CLI (revision ${client.graph}). Run the latest CLI: npx @extenshi/cli@latest map …`
|
|
191
|
+
: `The author's map draws newer steps (revision ${server.graph}) than this MCP server (revision ${client.graph}). Restart the MCP server on the latest @extenshi/mcp so its checks match the map.`;
|
|
192
|
+
if (server.available)
|
|
193
|
+
return `@extenshi/cli ${server.available} is out; the author's map runs ${server.version}. The author can press Update on the map, or run \`npx @extenshi/cli@latest map update\` in the project.`;
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
/** The address that opens one step on the map. */
|
|
197
|
+
export const stepUrl = (url, node) => `${url.replace(/#.*$/, '')}#node=${node}`;
|
|
198
|
+
/**
|
|
199
|
+
* Report on a step. `view` checks that the step's inputs are done; without it
|
|
200
|
+
* (no project facts) nothing is refused for that. The entry is `checked` when
|
|
201
|
+
* every proof is a file this call hashed.
|
|
202
|
+
*/
|
|
203
|
+
export async function reportStep(home, from, input, view) {
|
|
204
|
+
const hashed = await evidenceFiles(home, from, input.files ?? []);
|
|
205
|
+
const where = [
|
|
206
|
+
...hashed,
|
|
207
|
+
...(input.urls ?? []).map((url) => ({ url })),
|
|
208
|
+
...(input.dojo ?? []).map((dojo) => ({ dojo })),
|
|
209
|
+
...(input.command ? [{ command: input.command }] : []),
|
|
210
|
+
];
|
|
211
|
+
const checked = hashed.length > 0 && hashed.length === where.length;
|
|
212
|
+
const { files: _files, urls: _urls, dojo: _dojo, command: _command, ...report } = input;
|
|
213
|
+
const state = await updateMapState(home, (current) => reportBuildStep(current, view, { ...report, where, checked, at: new Date().toISOString() }));
|
|
214
|
+
return { record: state.nodes[input.node], checked };
|
|
215
|
+
}
|
|
216
|
+
/** Offer the author variants to choose from; files are resolved against `from`. */
|
|
217
|
+
export async function openReview(home, from, input, view) {
|
|
218
|
+
const variants = [];
|
|
219
|
+
for (const variant of input.variants)
|
|
220
|
+
variants.push({
|
|
221
|
+
label: variant.label,
|
|
222
|
+
preview: await resolveReviewFile(home, from, variant.preview),
|
|
223
|
+
...(variant.image ? { image: await resolveReviewFile(home, from, variant.image) } : {}),
|
|
224
|
+
});
|
|
225
|
+
const state = await updateMapState(home, (current) => openBuildReview(current, view, {
|
|
226
|
+
node: input.node,
|
|
227
|
+
agent: input.agent,
|
|
228
|
+
variants,
|
|
229
|
+
at: new Date().toISOString(),
|
|
230
|
+
}));
|
|
231
|
+
return state.reviews[input.node];
|
|
232
|
+
}
|
|
233
|
+
/** Record the author's answer from the chat: the variant they picked, or what to change. */
|
|
234
|
+
export async function answerReview(home, input, view) {
|
|
235
|
+
const state = await updateMapState(home, (current) => answerBuildReview(current, view, { ...input, via: 'chat', at: new Date().toISOString() }));
|
|
236
|
+
return state.reviews[input.node];
|
|
237
|
+
}
|
|
238
|
+
export function reviewAnswer(node, review) {
|
|
239
|
+
const choice = review.status === 'picked' ? review.variants.find((v) => v.id === review.choice) : undefined;
|
|
240
|
+
return {
|
|
241
|
+
node,
|
|
242
|
+
round: review.round,
|
|
243
|
+
status: review.status,
|
|
244
|
+
...(choice ? { choice } : {}),
|
|
245
|
+
...(review.status === 'redo' && review.feedback ? { feedback: review.feedback } : {}),
|
|
246
|
+
...(review.via && review.status !== 'waiting' ? { via: review.via } : {}),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Wait until the author answers the review of `node`, reading the state file
|
|
251
|
+
* every `pollMs` (no map server needed). Resolves at once when it is answered
|
|
252
|
+
* already; with `timeoutMs`, resolves with the review still waiting once that
|
|
253
|
+
* passes.
|
|
254
|
+
*/
|
|
255
|
+
export async function waitForReview(home, node, options = {}) {
|
|
256
|
+
const pollMs = options.pollMs ?? 1_000;
|
|
257
|
+
const deadline = options.timeoutMs ? Date.now() + options.timeoutMs : Number.POSITIVE_INFINITY;
|
|
258
|
+
for (;;) {
|
|
259
|
+
const read = await readMapState(home);
|
|
260
|
+
if (read.problem)
|
|
261
|
+
throw new Error(`Cannot read the build map: ${read.problem}`);
|
|
262
|
+
const review = read.state.reviews[node];
|
|
263
|
+
if (!review)
|
|
264
|
+
throw new Error(`No review is open on "${node}": open one with the variants first.`);
|
|
265
|
+
if (review.status !== 'waiting' || Date.now() >= deadline || options.signal?.aborted)
|
|
266
|
+
return reviewAnswer(node, review);
|
|
267
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(pollMs, Math.max(0, deadline - Date.now()))));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=build-map-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-map-agent.js","sourceRoot":"","sources":["../src/build-map-agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -32,7 +32,38 @@ export declare const MAP_STATE_FILE = "build-map.json";
|
|
|
32
32
|
export declare const MAP_SERVER_FILE = "build-map.server.json";
|
|
33
33
|
export declare const MAP_LOG_FILE = "build-map.log";
|
|
34
34
|
/** Bumped when the page/server protocol changes: a newer CLI replaces an older server instead of reusing it. */
|
|
35
|
-
export declare const MAP_PROTOCOL =
|
|
35
|
+
export declare const MAP_PROTOCOL = 7;
|
|
36
|
+
/** The record token, from the owner-only record file: authorizes /api/shutdown and a client's hello. */
|
|
37
|
+
export declare const MAP_TOKEN_HEADER = "x-extenshi-map-token";
|
|
38
|
+
/** Who asks: `cli/<version> graph=<revision>` or `mcp/<version> graph=<revision>` (see mapClientHeader). */
|
|
39
|
+
export declare const MAP_CLIENT_HEADER = "x-extenshi-map-client";
|
|
40
|
+
/**
|
|
41
|
+
* A program that talks to the map server on an agent's behalf: the CLI or the
|
|
42
|
+
* stdio MCP server, its package version, and the graph revision it was built
|
|
43
|
+
* with (BUILD_GRAPH_REVISION of its @extenshi/contracts).
|
|
44
|
+
*/
|
|
45
|
+
export type MapClient = {
|
|
46
|
+
kind: 'cli' | 'mcp';
|
|
47
|
+
version: string;
|
|
48
|
+
graph: number;
|
|
49
|
+
};
|
|
50
|
+
/** This program as a map client: the graph revision is the one it was built with. */
|
|
51
|
+
export declare const mapClient: (kind: MapClient["kind"], version: string) => MapClient;
|
|
52
|
+
export declare const mapClientHeader: (client: MapClient) => string;
|
|
53
|
+
export declare function parseMapClient(value: unknown): MapClient | undefined;
|
|
54
|
+
/** Order of two `x.y.z` versions (a pre-release sorts before its release); undefined when either is not one. */
|
|
55
|
+
export declare function compareVersions(a: string, b: string): number | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* How the running map and a client differ. `map_older`: the map draws an older
|
|
58
|
+
* graph than the client knows, or — for the CLI, the program that serves the
|
|
59
|
+
* map — it runs an older CLI release; `client_older`: the client knows an older
|
|
60
|
+
* graph than the map draws. A server from before graph revisions counts as
|
|
61
|
+
* revision 0.
|
|
62
|
+
*/
|
|
63
|
+
export declare function mapSkew(server: {
|
|
64
|
+
version: string;
|
|
65
|
+
graph?: number;
|
|
66
|
+
}, client: MapClient): 'map_older' | 'client_older' | undefined;
|
|
36
67
|
export type MapHome = {
|
|
37
68
|
/** Where the map's files live: this project directory in the main checkout. */
|
|
38
69
|
home: string;
|
|
@@ -90,6 +121,8 @@ export type MapServerRecord = {
|
|
|
90
121
|
token: string;
|
|
91
122
|
protocol: number;
|
|
92
123
|
version: string;
|
|
124
|
+
/** The graph revision the server draws (absent before protocol 7). */
|
|
125
|
+
graph?: number;
|
|
93
126
|
home: string;
|
|
94
127
|
startedAt: string;
|
|
95
128
|
};
|
|
@@ -99,19 +132,28 @@ export type MapHealth = {
|
|
|
99
132
|
protocol: number;
|
|
100
133
|
version: string;
|
|
101
134
|
clients: number;
|
|
135
|
+
/** The graph revision the server draws (absent before protocol 7). */
|
|
136
|
+
graph?: number;
|
|
137
|
+
/** A newer @extenshi/cli on npm that the map can restart on. */
|
|
138
|
+
available?: string;
|
|
139
|
+
/** The version the map is restarting on right now. */
|
|
140
|
+
updating?: string;
|
|
102
141
|
};
|
|
103
142
|
export declare function readServerRecord(home: string): Promise<MapServerRecord | undefined>;
|
|
104
|
-
export declare function fetchHealth(port: number, timeoutMs?: number): Promise<MapHealth | undefined>;
|
|
105
|
-
export type RunningServer = MapServerRecord & {
|
|
143
|
+
export declare function fetchHealth(port: number, timeoutMs?: number, headers?: Record<string, string>): Promise<MapHealth | undefined>;
|
|
144
|
+
export type RunningServer = MapServerRecord & Pick<MapHealth, 'available' | 'updating'> & {
|
|
106
145
|
clients: number;
|
|
146
|
+
graph: number;
|
|
107
147
|
};
|
|
108
148
|
/**
|
|
109
149
|
* The server recorded for `home`, if it is really up: same machine, process
|
|
110
150
|
* alive, and its health answer carries the recorded instance id and home. A
|
|
111
151
|
* record left by a crash, by another machine (a synced folder) or a reused pid
|
|
112
|
-
* reads as no server.
|
|
152
|
+
* reads as no server. With `client`, the probe also tells the server which
|
|
153
|
+
* program asks and which graph it knows, so the map can offer an update when
|
|
154
|
+
* an agent works with newer steps than the map draws.
|
|
113
155
|
*/
|
|
114
|
-
export declare function probeServer(home: string): Promise<RunningServer | undefined>;
|
|
156
|
+
export declare function probeServer(home: string, client?: MapClient): Promise<RunningServer | undefined>;
|
|
115
157
|
export declare const serverLockPath: (home: string) => string;
|
|
116
158
|
/**
|
|
117
159
|
* Mark the server record stopped, but only if it still names `instance` —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-map-store.d.ts","sourceRoot":"","sources":["../src/build-map-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAOH,OAAO,
|
|
1
|
+
{"version":3,"file":"build-map-store.d.ts","sourceRoot":"","sources":["../src/build-map-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAOH,OAAO,EAEN,KAAK,aAAa,EAGlB,MAAM,qBAAqB,CAAA;AAE5B,eAAO,MAAM,OAAO,cAAc,CAAA;AAClC,eAAO,MAAM,cAAc,mBAAmB,CAAA;AAC9C,eAAO,MAAM,eAAe,0BAA0B,CAAA;AACtD,eAAO,MAAM,YAAY,kBAAkB,CAAA;AAI3C,gHAAgH;AAChH,eAAO,MAAM,YAAY,IAAI,CAAA;AAE7B,wGAAwG;AACxG,eAAO,MAAM,gBAAgB,yBAAyB,CAAA;AACtD,4GAA4G;AAC5G,eAAO,MAAM,iBAAiB,0BAA0B,CAAA;AAExD;;;;GAIG;AACH,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,KAAK,GAAG,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/E,qFAAqF;AACrF,eAAO,MAAM,SAAS,GAAI,MAAM,SAAS,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,KAAG,SAInE,CAAA;AAEF,eAAO,MAAM,eAAe,GAAI,QAAQ,SAAS,WAA6D,CAAA;AAE9G,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,SAAS,CAKpE;AAED,gHAAgH;AAChH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAaxE;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CACtB,MAAM,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,EAC3C,MAAM,EAAE,SAAS,GACf,WAAW,GAAG,cAAc,GAAG,SAAS,CAM1C;AAED,MAAM,MAAM,OAAO,GAAG;IACrB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAA;IACZ,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAA;IACX,qFAAqF;IACrF,QAAQ,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,eAAO,MAAM,OAAO,GAAI,MAAM,MAAM,EAAE,MAAM,MAAM,WAAmC,CAAA;AAkBrF;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAkBlE;AAED,gHAAgH;AAChH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAetE;AAED,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAQ7C;AA4ED;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,CAAC,EAClC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACtB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACnE,OAAO,CAAC,CAAC,CAAC,CAqCZ;AAED,uFAAuF;AACvF,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,SAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAe/F;AAED,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEnF,mGAAmG;AACnG,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAkBnE;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CACnC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,aAAa,GAAG,SAAS,GACzD,OAAO,CAAC,aAAa,CAAC,CAYxB;AAED,MAAM,MAAM,eAAe,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,EAAE,MAAM,CAAA;IACX,4FAA4F;IAC5F,QAAQ,EAAE,MAAM,CAAA;IAChB,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAQzF;AAED,wBAAsB,WAAW,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,SAAQ,EACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAWhC;AAED,MAAM,MAAM,aAAa,GAAG,eAAe,GAC1C,IAAI,CAAC,SAAS,EAAE,WAAW,GAAG,UAAU,CAAC,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAA;AAE/E;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAmBtG;AAED,eAAO,MAAM,cAAc,GAAI,MAAM,MAAM,WAA+B,CAAA;AAE1E;;;;;GAKG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQtF"}
|
package/dist/build-map-store.js
CHANGED
|
@@ -31,7 +31,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
31
31
|
import { mkdir, open, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
|
|
32
32
|
import os from 'node:os';
|
|
33
33
|
import path from 'node:path';
|
|
34
|
-
import { emptyBuildMapState, normalizeBuildMapState } from '@extenshi/contracts';
|
|
34
|
+
import { BUILD_GRAPH_REVISION, emptyBuildMapState, normalizeBuildMapState, } from '@extenshi/contracts';
|
|
35
35
|
export const MAP_DIR = '.extenshi';
|
|
36
36
|
export const MAP_STATE_FILE = 'build-map.json';
|
|
37
37
|
export const MAP_SERVER_FILE = 'build-map.server.json';
|
|
@@ -39,7 +39,63 @@ export const MAP_LOG_FILE = 'build-map.log';
|
|
|
39
39
|
const STATE_LOCK = 'build-map.lock';
|
|
40
40
|
const SERVER_LOCK = 'build-map.server.lock';
|
|
41
41
|
/** Bumped when the page/server protocol changes: a newer CLI replaces an older server instead of reusing it. */
|
|
42
|
-
export const MAP_PROTOCOL =
|
|
42
|
+
export const MAP_PROTOCOL = 7;
|
|
43
|
+
/** The record token, from the owner-only record file: authorizes /api/shutdown and a client's hello. */
|
|
44
|
+
export const MAP_TOKEN_HEADER = 'x-extenshi-map-token';
|
|
45
|
+
/** Who asks: `cli/<version> graph=<revision>` or `mcp/<version> graph=<revision>` (see mapClientHeader). */
|
|
46
|
+
export const MAP_CLIENT_HEADER = 'x-extenshi-map-client';
|
|
47
|
+
/** This program as a map client: the graph revision is the one it was built with. */
|
|
48
|
+
export const mapClient = (kind, version) => ({
|
|
49
|
+
kind,
|
|
50
|
+
version,
|
|
51
|
+
graph: BUILD_GRAPH_REVISION,
|
|
52
|
+
});
|
|
53
|
+
export const mapClientHeader = (client) => `${client.kind}/${client.version} graph=${client.graph}`;
|
|
54
|
+
export function parseMapClient(value) {
|
|
55
|
+
if (typeof value !== 'string' || value.length > 80)
|
|
56
|
+
return undefined;
|
|
57
|
+
const match = /^(cli|mcp)\/([0-9A-Za-z.+-]{1,40}) graph=(\d{1,6})$/.exec(value);
|
|
58
|
+
if (!match)
|
|
59
|
+
return undefined;
|
|
60
|
+
return { kind: match[1], version: match[2], graph: Number(match[3]) };
|
|
61
|
+
}
|
|
62
|
+
/** Order of two `x.y.z` versions (a pre-release sorts before its release); undefined when either is not one. */
|
|
63
|
+
export function compareVersions(a, b) {
|
|
64
|
+
const parse = (v) => /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(v);
|
|
65
|
+
const x = parse(a);
|
|
66
|
+
const y = parse(b);
|
|
67
|
+
if (!x || !y)
|
|
68
|
+
return undefined;
|
|
69
|
+
for (let i = 1; i <= 3; i++) {
|
|
70
|
+
const d = Number(x[i]) - Number(y[i]);
|
|
71
|
+
if (d)
|
|
72
|
+
return Math.sign(d);
|
|
73
|
+
}
|
|
74
|
+
if (x[4] === y[4])
|
|
75
|
+
return 0;
|
|
76
|
+
if (!x[4])
|
|
77
|
+
return 1;
|
|
78
|
+
if (!y[4])
|
|
79
|
+
return -1;
|
|
80
|
+
return x[4] < y[4] ? -1 : 1;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* How the running map and a client differ. `map_older`: the map draws an older
|
|
84
|
+
* graph than the client knows, or — for the CLI, the program that serves the
|
|
85
|
+
* map — it runs an older CLI release; `client_older`: the client knows an older
|
|
86
|
+
* graph than the map draws. A server from before graph revisions counts as
|
|
87
|
+
* revision 0.
|
|
88
|
+
*/
|
|
89
|
+
export function mapSkew(server, client) {
|
|
90
|
+
const graph = server.graph ?? 0;
|
|
91
|
+
if (client.graph > graph)
|
|
92
|
+
return 'map_older';
|
|
93
|
+
if (client.graph < graph)
|
|
94
|
+
return 'client_older';
|
|
95
|
+
if (client.kind === 'cli' && (compareVersions(client.version, server.version) ?? 0) > 0)
|
|
96
|
+
return 'map_older';
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
43
99
|
export const mapPath = (home, file) => path.join(home, MAP_DIR, file);
|
|
44
100
|
function git(cwd, args) {
|
|
45
101
|
return new Promise((resolve) => {
|
|
@@ -303,10 +359,11 @@ export async function readServerRecord(home) {
|
|
|
303
359
|
return undefined;
|
|
304
360
|
}
|
|
305
361
|
}
|
|
306
|
-
export async function fetchHealth(port, timeoutMs = 1_500) {
|
|
362
|
+
export async function fetchHealth(port, timeoutMs = 1_500, headers) {
|
|
307
363
|
try {
|
|
308
364
|
const response = await fetch(`http://127.0.0.1:${port}/api/health`, {
|
|
309
365
|
signal: AbortSignal.timeout(timeoutMs),
|
|
366
|
+
...(headers ? { headers } : {}),
|
|
310
367
|
});
|
|
311
368
|
if (!response.ok)
|
|
312
369
|
return undefined;
|
|
@@ -320,16 +377,27 @@ export async function fetchHealth(port, timeoutMs = 1_500) {
|
|
|
320
377
|
* The server recorded for `home`, if it is really up: same machine, process
|
|
321
378
|
* alive, and its health answer carries the recorded instance id and home. A
|
|
322
379
|
* record left by a crash, by another machine (a synced folder) or a reused pid
|
|
323
|
-
* reads as no server.
|
|
380
|
+
* reads as no server. With `client`, the probe also tells the server which
|
|
381
|
+
* program asks and which graph it knows, so the map can offer an update when
|
|
382
|
+
* an agent works with newer steps than the map draws.
|
|
324
383
|
*/
|
|
325
|
-
export async function probeServer(home) {
|
|
384
|
+
export async function probeServer(home, client) {
|
|
326
385
|
const record = await readServerRecord(home);
|
|
327
386
|
if (!record || record.host !== os.hostname() || !pidAlive(record.pid))
|
|
328
387
|
return undefined;
|
|
329
|
-
const health = await fetchHealth(record.port
|
|
388
|
+
const health = await fetchHealth(record.port, 1_500, client && record.token
|
|
389
|
+
? { [MAP_TOKEN_HEADER]: record.token, [MAP_CLIENT_HEADER]: mapClientHeader(client) }
|
|
390
|
+
: undefined);
|
|
330
391
|
if (!health || health.instance !== record.instance || health.home !== home)
|
|
331
392
|
return undefined;
|
|
332
|
-
return {
|
|
393
|
+
return {
|
|
394
|
+
...record,
|
|
395
|
+
version: health.version ?? record.version,
|
|
396
|
+
clients: health.clients,
|
|
397
|
+
graph: health.graph ?? 0,
|
|
398
|
+
...(health.available ? { available: health.available } : {}),
|
|
399
|
+
...(health.updating ? { updating: health.updating } : {}),
|
|
400
|
+
};
|
|
333
401
|
}
|
|
334
402
|
export const serverLockPath = (home) => mapPath(home, SERVER_LOCK);
|
|
335
403
|
/**
|