@agentproto/driver-agent-cli 2.7.1 → 2.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/{chunk-Y3FGLQ4R.mjs → chunk-AN5G6ENG.mjs} +185 -34
- package/dist/chunk-AN5G6ENG.mjs.map +1 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/manifest/index.d.ts +2 -2
- package/dist/manifest/index.mjs +2 -2
- package/dist/{schema-CKrBhnuF.d.ts → schema-DQHzxzLh.d.ts} +58 -1
- package/package.json +1 -1
- package/dist/chunk-Y3FGLQ4R.mjs.map +0 -1
|
@@ -2,7 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { Writable, Readable } from 'stream';
|
|
3
3
|
import { createAcpClient } from '@agentproto/acp/client';
|
|
4
4
|
import { spawn, execFile } from 'child_process';
|
|
5
|
-
import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync, rmdirSync, realpathSync, mkdtempSync } from 'fs';
|
|
5
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync, rmdirSync, realpathSync, mkdtempSync, lstatSync, unlinkSync, symlinkSync } from 'fs';
|
|
6
6
|
import { createInterface } from 'readline';
|
|
7
7
|
import { join, isAbsolute, resolve, delimiter, dirname, basename } from 'path';
|
|
8
8
|
import { tmpdir, homedir } from 'os';
|
|
@@ -242,6 +242,13 @@ var capabilitiesSchema = z.object({
|
|
|
242
242
|
* as a PTY session. Distinct from ACP-level `resumable`.
|
|
243
243
|
*/
|
|
244
244
|
nativeTerminalResume: z.boolean().optional(),
|
|
245
|
+
/**
|
|
246
|
+
* Harness defers mounted MCP tools behind its own native search tool
|
|
247
|
+
* (Claude Code `ToolSearch`). Makes the daemon's `/mcp` self-mount default
|
|
248
|
+
* to eager for this adapter instead of double-deferring behind the
|
|
249
|
+
* daemon's `tool_search`. Defaults to false (conservative).
|
|
250
|
+
*/
|
|
251
|
+
nativeToolSearch: z.boolean().optional(),
|
|
245
252
|
/**
|
|
246
253
|
* Adapter can ingest a filesystem path that the host UI just placed
|
|
247
254
|
* on disk (host-side drag-drop into a terminal pastes the path
|
|
@@ -385,6 +392,14 @@ var agentCliFrontmatterSchema = z.object({
|
|
|
385
392
|
routeSelection: z.enum(["free", "derived-from-model"]).optional(),
|
|
386
393
|
authEnforce: z.enum(["always", "when-configured"]).optional(),
|
|
387
394
|
sandbox: z.union([z.string(), z.record(z.string(), z.unknown())]),
|
|
395
|
+
stateHome: z.object({
|
|
396
|
+
env: z.string().regex(/^[A-Z][A-Z0-9_]*$/),
|
|
397
|
+
defaultDir: z.string().min(1).refine((d) => !d.startsWith("/") && !d.split("/").includes(".."), {
|
|
398
|
+
message: "defaultDir must be relative to $HOME and stay inside it"
|
|
399
|
+
}),
|
|
400
|
+
share: z.array(z.string().regex(/^[^/]+$/)).optional(),
|
|
401
|
+
seed: z.record(z.string().regex(/^[^/]+$/).refine((n) => n !== "." && n !== "..", { message: "seed names a file of the home" }), z.string()).optional()
|
|
402
|
+
}).strict().optional(),
|
|
388
403
|
runner: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
|
|
389
404
|
protocol: z.enum(["acp", "mcp", "proprietary", "print"]),
|
|
390
405
|
acp: z.string().optional(),
|
|
@@ -713,15 +728,15 @@ function isAlive(pid) {
|
|
|
713
728
|
}
|
|
714
729
|
}
|
|
715
730
|
function listProcesses() {
|
|
716
|
-
return new Promise((
|
|
731
|
+
return new Promise((resolve3) => {
|
|
717
732
|
execFile("ps", ["-axo", "pid=,ppid="], { maxBuffer: 32 * 1024 * 1024 }, (err, stdout) => {
|
|
718
|
-
if (err) return
|
|
733
|
+
if (err) return resolve3([]);
|
|
719
734
|
const out = [];
|
|
720
735
|
for (const line of stdout.split("\n")) {
|
|
721
736
|
const m = line.match(/^\s*(\d+)\s+(\d+)\s*$/);
|
|
722
737
|
if (m) out.push({ pid: Number(m[1]), ppid: Number(m[2]) });
|
|
723
738
|
}
|
|
724
|
-
|
|
739
|
+
resolve3(out);
|
|
725
740
|
});
|
|
726
741
|
});
|
|
727
742
|
}
|
|
@@ -770,9 +785,17 @@ function defaultToolchainReadPaths() {
|
|
|
770
785
|
// macOS Keychain DB — git/gh credential retrieval (read-only; see doc above)
|
|
771
786
|
];
|
|
772
787
|
}
|
|
788
|
+
function effectiveSpawnSandboxMode(opts, configured) {
|
|
789
|
+
return opts.zones ? opts.mode ?? "workspace" : opts.mode ?? configured;
|
|
790
|
+
}
|
|
791
|
+
async function willConfineAgentCliSpawn(opts) {
|
|
792
|
+
const cfg = await loadAdapterSpawnSandboxConfig(opts.cwd);
|
|
793
|
+
const mode = effectiveSpawnSandboxMode(opts, cfg.mode);
|
|
794
|
+
return mode !== void 0 && mode !== "off";
|
|
795
|
+
}
|
|
773
796
|
async function wrapAgentCliSpawn(bin, args, opts) {
|
|
774
797
|
const cfg = await loadAdapterSpawnSandboxConfig(opts.cwd);
|
|
775
|
-
const mode = opts
|
|
798
|
+
const mode = effectiveSpawnSandboxMode(opts, cfg.mode);
|
|
776
799
|
if (mode === void 0) return [bin, args];
|
|
777
800
|
if (mode === "off" && opts.zones) {
|
|
778
801
|
throw new Error(
|
|
@@ -873,11 +896,11 @@ function createPrintSession(opts) {
|
|
|
873
896
|
});
|
|
874
897
|
activeChild = child;
|
|
875
898
|
let spawnFailure;
|
|
876
|
-
await new Promise((
|
|
877
|
-
child.once("spawn", () =>
|
|
899
|
+
await new Promise((resolve3) => {
|
|
900
|
+
child.once("spawn", () => resolve3());
|
|
878
901
|
child.once("error", (err) => {
|
|
879
902
|
spawnFailure = err;
|
|
880
|
-
|
|
903
|
+
resolve3();
|
|
881
904
|
});
|
|
882
905
|
});
|
|
883
906
|
if (spawnFailure) {
|
|
@@ -1496,16 +1519,16 @@ var EventQueue = class {
|
|
|
1496
1519
|
`print-arm: event backlog exceeded ${QUEUE_HIGH_WATER_MARK} buffered events \u2014 downstream consumer is not keeping up. The child's stdout is still being drained (no ENOBUFS), but memory will grow until the consumer catches up.`
|
|
1497
1520
|
);
|
|
1498
1521
|
}
|
|
1499
|
-
const
|
|
1522
|
+
const resolve3 = this.resolveNext;
|
|
1500
1523
|
this.resolveNext = null;
|
|
1501
|
-
|
|
1524
|
+
resolve3?.();
|
|
1502
1525
|
}
|
|
1503
1526
|
/** Signal that no further events will be pushed. */
|
|
1504
1527
|
end() {
|
|
1505
1528
|
this.ended = true;
|
|
1506
|
-
const
|
|
1529
|
+
const resolve3 = this.resolveNext;
|
|
1507
1530
|
this.resolveNext = null;
|
|
1508
|
-
|
|
1531
|
+
resolve3?.();
|
|
1509
1532
|
}
|
|
1510
1533
|
async *[Symbol.asyncIterator]() {
|
|
1511
1534
|
for (; ; ) {
|
|
@@ -1515,8 +1538,8 @@ var EventQueue = class {
|
|
|
1515
1538
|
continue;
|
|
1516
1539
|
}
|
|
1517
1540
|
if (this.ended) return;
|
|
1518
|
-
await new Promise((
|
|
1519
|
-
this.resolveNext =
|
|
1541
|
+
await new Promise((resolve3) => {
|
|
1542
|
+
this.resolveNext = resolve3;
|
|
1520
1543
|
});
|
|
1521
1544
|
}
|
|
1522
1545
|
}
|
|
@@ -1535,9 +1558,9 @@ function mapMastraFinishReason(raw) {
|
|
|
1535
1558
|
}
|
|
1536
1559
|
}
|
|
1537
1560
|
function waitForExit(child) {
|
|
1538
|
-
return new Promise((
|
|
1539
|
-
child.once("exit", (code) =>
|
|
1540
|
-
child.once("error", () =>
|
|
1561
|
+
return new Promise((resolve3) => {
|
|
1562
|
+
child.once("exit", (code) => resolve3(code));
|
|
1563
|
+
child.once("error", () => resolve3(null));
|
|
1541
1564
|
});
|
|
1542
1565
|
}
|
|
1543
1566
|
async function createProprietaryProtocolArm(options) {
|
|
@@ -1910,20 +1933,132 @@ async function applyModelCommand(arm, modelId) {
|
|
|
1910
1933
|
}
|
|
1911
1934
|
return { applied: true, model: modelId };
|
|
1912
1935
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
1936
|
+
function parseLogfmtLine(line) {
|
|
1937
|
+
const out = {};
|
|
1938
|
+
const re = /([A-Za-z0-9_.-]+)=("(?:[^"\\]|\\.)*"|\S+)/g;
|
|
1939
|
+
let m;
|
|
1940
|
+
while ((m = re.exec(line)) !== null) {
|
|
1941
|
+
const key = m[1];
|
|
1942
|
+
let value = m[2];
|
|
1943
|
+
if (value.startsWith('"')) {
|
|
1944
|
+
value = value.slice(1, -1).replace(/\\(.)/g, "$1");
|
|
1945
|
+
}
|
|
1946
|
+
out[key] = value;
|
|
1947
|
+
}
|
|
1948
|
+
return out;
|
|
1949
|
+
}
|
|
1950
|
+
function providerMessageFromError(raw) {
|
|
1951
|
+
const m = raw.match(/^[A-Za-z_][\w.]*(?:Error|Exception)\s*:\s*([\s\S]+)$/);
|
|
1952
|
+
return (m ? m[1] : raw).trim();
|
|
1953
|
+
}
|
|
1954
|
+
function parseStderrStreamError(line) {
|
|
1955
|
+
if (!line.includes("stream error")) return void 0;
|
|
1956
|
+
const fields = parseLogfmtLine(line);
|
|
1957
|
+
if ((fields.level ?? "").toUpperCase() !== "ERROR") return void 0;
|
|
1958
|
+
if (fields.message !== "stream error") return void 0;
|
|
1959
|
+
if (fields.small === "true") return void 0;
|
|
1960
|
+
if (fields.agent === "title") return void 0;
|
|
1961
|
+
const raw = fields["error.error"] ?? fields.error;
|
|
1962
|
+
if (!raw) return void 0;
|
|
1963
|
+
return providerMessageFromError(raw);
|
|
1964
|
+
}
|
|
1965
|
+
async function* promptTurn(arm, turnId, message, stderrTurnError) {
|
|
1915
1966
|
const stderrTail = arm._stderrTail;
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1967
|
+
const pendingErrors = [];
|
|
1968
|
+
let wake;
|
|
1969
|
+
const unsubscribe = stderrTurnError && arm._onStderrLine ? arm._onStderrLine((line) => {
|
|
1970
|
+
const parsed = stderrTurnError(line);
|
|
1971
|
+
if (parsed === void 0) return;
|
|
1972
|
+
pendingErrors.push(parsed);
|
|
1973
|
+
wake?.();
|
|
1974
|
+
wake = void 0;
|
|
1975
|
+
}) : void 0;
|
|
1976
|
+
let iterator;
|
|
1977
|
+
try {
|
|
1978
|
+
await arm.send(turnId, message);
|
|
1979
|
+
iterator = arm.events()[Symbol.asyncIterator]();
|
|
1980
|
+
while (true) {
|
|
1981
|
+
if (pendingErrors.length > 0) {
|
|
1982
|
+
await arm.cancel(turnId).catch(() => {
|
|
1983
|
+
});
|
|
1984
|
+
yield {
|
|
1985
|
+
kind: "error",
|
|
1986
|
+
sessionId: arm.sessionId,
|
|
1987
|
+
error: {
|
|
1988
|
+
message: pendingErrors.join("\n"),
|
|
1989
|
+
data: { stderr: stderrTail?.() ?? "" }
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
yield {
|
|
1993
|
+
kind: "turn-end",
|
|
1994
|
+
sessionId: arm.sessionId ?? "",
|
|
1995
|
+
reason: "error"
|
|
1996
|
+
};
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
const nextEvent = iterator.next();
|
|
2000
|
+
const stderrArrived = new Promise((resolve3) => {
|
|
2001
|
+
wake = resolve3;
|
|
2002
|
+
});
|
|
2003
|
+
const winner = await Promise.race([
|
|
2004
|
+
nextEvent.then((result) => ({ source: "event", result })),
|
|
2005
|
+
stderrArrived.then(() => ({ source: "stderr" }))
|
|
2006
|
+
]);
|
|
2007
|
+
if (winner.source === "stderr") continue;
|
|
2008
|
+
if (winner.result.done) return;
|
|
2009
|
+
const evt = winner.result.value;
|
|
2010
|
+
if (evt.kind === "error" && typeof stderrTail === "function") {
|
|
2011
|
+
const tail = stderrTail();
|
|
2012
|
+
if (tail) {
|
|
2013
|
+
const existing = evt.error.data ?? {};
|
|
2014
|
+
evt.error.data = { ...existing, stderr: tail };
|
|
2015
|
+
}
|
|
1922
2016
|
}
|
|
2017
|
+
yield evt;
|
|
1923
2018
|
}
|
|
1924
|
-
|
|
2019
|
+
} finally {
|
|
2020
|
+
unsubscribe?.();
|
|
2021
|
+
void iterator?.return?.()?.catch?.(() => {
|
|
2022
|
+
});
|
|
1925
2023
|
}
|
|
1926
2024
|
}
|
|
2025
|
+
function realStateHome(stateHome, env) {
|
|
2026
|
+
const fromEnv = env[stateHome.env];
|
|
2027
|
+
if (fromEnv && isAbsolute(fromEnv)) return fromEnv;
|
|
2028
|
+
return resolve(homedir(), stateHome.defaultDir);
|
|
2029
|
+
}
|
|
2030
|
+
function prepareIsolatedStateHome(stateHome, opts) {
|
|
2031
|
+
const real = realStateHome(stateHome, opts.env);
|
|
2032
|
+
let dir;
|
|
2033
|
+
if (opts.configDir) {
|
|
2034
|
+
dir = opts.configDir;
|
|
2035
|
+
mkdirSync(dir, { recursive: true });
|
|
2036
|
+
} else {
|
|
2037
|
+
dir = mkdtempSync(join(tmpdir(), `agentproto-${stateHome.env.toLowerCase()}-`));
|
|
2038
|
+
}
|
|
2039
|
+
const writePaths = [dir];
|
|
2040
|
+
for (const name of stateHome.share ?? []) {
|
|
2041
|
+
const target = join(real, name);
|
|
2042
|
+
const link = join(dir, name);
|
|
2043
|
+
try {
|
|
2044
|
+
lstatSync(link);
|
|
2045
|
+
unlinkSync(link);
|
|
2046
|
+
} catch {
|
|
2047
|
+
}
|
|
2048
|
+
if (!existsSync(target)) continue;
|
|
2049
|
+
symlinkSync(target, link);
|
|
2050
|
+
writePaths.push(realpathSync(target));
|
|
2051
|
+
}
|
|
2052
|
+
for (const [name, content] of Object.entries(stateHome.seed ?? {})) {
|
|
2053
|
+
const file = join(dir, name);
|
|
2054
|
+
try {
|
|
2055
|
+
unlinkSync(file);
|
|
2056
|
+
} catch {
|
|
2057
|
+
}
|
|
2058
|
+
writeFileSync(file, content);
|
|
2059
|
+
}
|
|
2060
|
+
return { dir, writePaths };
|
|
2061
|
+
}
|
|
1927
2062
|
function npxCacheKey(packages) {
|
|
1928
2063
|
return createHash("sha512").update([...packages].sort((a, b) => a.localeCompare(b, "en")).join("\n")).digest("hex").slice(0, 16);
|
|
1929
2064
|
}
|
|
@@ -2130,6 +2265,15 @@ function createAgentCliRuntime(definition) {
|
|
|
2130
2265
|
);
|
|
2131
2266
|
env.CLAUDE_CONFIG_DIR = claudeConfigDir;
|
|
2132
2267
|
}
|
|
2268
|
+
const extraWritePaths = claudeConfigDir ? [claudeConfigDir] : [];
|
|
2269
|
+
if (definition.stateHome && definition.protocol !== "proprietary" && await willConfineAgentCliSpawn({ mode: opts?.commandSandbox, cwd, zones: opts?.fsZones })) {
|
|
2270
|
+
const isolated = prepareIsolatedStateHome(definition.stateHome, {
|
|
2271
|
+
...opts?.configDir ? { configDir: opts.configDir } : {},
|
|
2272
|
+
env
|
|
2273
|
+
});
|
|
2274
|
+
env[definition.stateHome.env] = isolated.dir;
|
|
2275
|
+
extraWritePaths.push(...isolated.writePaths);
|
|
2276
|
+
}
|
|
2133
2277
|
if (definition.protocol === "print") {
|
|
2134
2278
|
const printModel = config?.options?.model;
|
|
2135
2279
|
return createPrintSession({
|
|
@@ -2142,7 +2286,7 @@ function createAgentCliRuntime(definition) {
|
|
|
2142
2286
|
printConfig: definition.print,
|
|
2143
2287
|
commandSandbox: opts?.commandSandbox,
|
|
2144
2288
|
...opts?.fsZones ? { fsZones: opts.fsZones } : {},
|
|
2145
|
-
...
|
|
2289
|
+
...extraWritePaths.length ? { extraWritePaths } : {},
|
|
2146
2290
|
// Exact-file read grant — see the ACP arm's wrapAgentCliSpawn call.
|
|
2147
2291
|
...opts?.additionalReadPaths?.length ? { extraReadPaths: opts.additionalReadPaths } : {},
|
|
2148
2292
|
...definition.routeSelection === "derived-from-model" && printModel ? { expectedModel: String(printModel) } : {}
|
|
@@ -2150,6 +2294,7 @@ function createAgentCliRuntime(definition) {
|
|
|
2150
2294
|
}
|
|
2151
2295
|
let child;
|
|
2152
2296
|
const stderrBuf = [];
|
|
2297
|
+
const stderrLineListeners = /* @__PURE__ */ new Set();
|
|
2153
2298
|
if (definition.protocol !== "proprietary") {
|
|
2154
2299
|
const [execBin, execArgs] = await wrapAgentCliSpawn(
|
|
2155
2300
|
finalBin,
|
|
@@ -2158,7 +2303,7 @@ function createAgentCliRuntime(definition) {
|
|
|
2158
2303
|
mode: opts?.commandSandbox,
|
|
2159
2304
|
cwd,
|
|
2160
2305
|
...opts?.fsZones ? { zones: opts.fsZones } : {},
|
|
2161
|
-
...
|
|
2306
|
+
...extraWritePaths.length ? { extraWritePaths } : {},
|
|
2162
2307
|
// Exact-file read grant (see additionalReadPaths above):
|
|
2163
2308
|
// READ-only exceptions to the confinement boundary, never writes.
|
|
2164
2309
|
...opts?.additionalReadPaths?.length ? { extraReadPaths: opts.additionalReadPaths } : {},
|
|
@@ -2178,11 +2323,11 @@ function createAgentCliRuntime(definition) {
|
|
|
2178
2323
|
});
|
|
2179
2324
|
child = spawned;
|
|
2180
2325
|
let spawnFailure;
|
|
2181
|
-
await new Promise((
|
|
2182
|
-
spawned.once("spawn", () =>
|
|
2326
|
+
await new Promise((resolve3) => {
|
|
2327
|
+
spawned.once("spawn", () => resolve3());
|
|
2183
2328
|
spawned.once("error", (err) => {
|
|
2184
2329
|
spawnFailure = err;
|
|
2185
|
-
|
|
2330
|
+
resolve3();
|
|
2186
2331
|
});
|
|
2187
2332
|
});
|
|
2188
2333
|
if (spawnFailure) {
|
|
@@ -2205,6 +2350,7 @@ cwd '${cwd}' does not exist \u2014 Node reports a missing working directory with
|
|
|
2205
2350
|
if (!line) continue;
|
|
2206
2351
|
stderrBuf.push(line);
|
|
2207
2352
|
if (stderrBuf.length > STDERR_KEEP_LINES) stderrBuf.shift();
|
|
2353
|
+
for (const listener of stderrLineListeners) listener(line);
|
|
2208
2354
|
}
|
|
2209
2355
|
});
|
|
2210
2356
|
}
|
|
@@ -2216,6 +2362,10 @@ cwd '${cwd}' does not exist \u2014 Node reports a missing working directory with
|
|
|
2216
2362
|
opts?.permissionHold ?? false
|
|
2217
2363
|
);
|
|
2218
2364
|
arm._stderrTail = () => stderrBuf.join("\n");
|
|
2365
|
+
arm._onStderrLine = (listener) => {
|
|
2366
|
+
stderrLineListeners.add(listener);
|
|
2367
|
+
return () => stderrLineListeners.delete(listener);
|
|
2368
|
+
};
|
|
2219
2369
|
const abortController = new AbortController();
|
|
2220
2370
|
if (opts?.signal) {
|
|
2221
2371
|
opts.signal.addEventListener("abort", () => abortController.abort(), {
|
|
@@ -2282,7 +2432,8 @@ cwd '${cwd}' does not exist \u2014 Node reports a missing working directory with
|
|
|
2282
2432
|
send(message) {
|
|
2283
2433
|
const turnId = randomUUID();
|
|
2284
2434
|
currentTurnId = turnId;
|
|
2285
|
-
|
|
2435
|
+
const stderrTurnError = definition.id === "opencode" ? parseStderrStreamError : void 0;
|
|
2436
|
+
return promptTurn(arm, turnId, message, stderrTurnError);
|
|
2286
2437
|
},
|
|
2287
2438
|
/**
|
|
2288
2439
|
* Cancel the in-flight turn — the wire equivalent of Ctrl-C at the
|
|
@@ -2398,5 +2549,5 @@ function derivedModelRefusalError(definition, requested, reason) {
|
|
|
2398
2549
|
}
|
|
2399
2550
|
|
|
2400
2551
|
export { LEGACY_GATEWAY_MODE_IDS, LEGACY_POSTURE_MODE_IDS, RuntimeConfigError, agentCliFrontmatterSchema, agentCliSupportsHostContextIsolation, applyModelCommand, autoAllowPermissionHandler, composeSpawn, createAcpProtocolArm, createAgentCliRuntime, createArmSessionControls, createJcodeMapperState, createMastraMapperState, createPrintSession, createProprietaryProtocolArm, defineAgentCli, hostContextExcludes, inferLegacyModeKind, isLegacyExtractedModeId, isModelSwitchAcknowledgement, mapJcodeEvent, mapMastraEvent, parseModelSwitchCommand, planModePermissionHandler, promptTurn, resolveContinuationStrategy, runtimeConfigSchema, toFileBasedMcpServers };
|
|
2401
|
-
//# sourceMappingURL=chunk-
|
|
2402
|
-
//# sourceMappingURL=chunk-
|
|
2552
|
+
//# sourceMappingURL=chunk-AN5G6ENG.mjs.map
|
|
2553
|
+
//# sourceMappingURL=chunk-AN5G6ENG.mjs.map
|