@primitive.ai/prim 0.1.0-alpha.90 → 0.1.0-alpha.92
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 +20 -12
- package/SKILL.md +6 -6
- package/dist/chunk-26P57LAR.js +1491 -0
- package/dist/{chunk-LMRYZL3D.js → chunk-5AFRMOPX.js} +1 -1
- package/dist/{chunk-RO3F6JYV.js → chunk-5GYQ4J3D.js} +2 -2
- package/dist/chunk-5HRR4AOR.js +264 -0
- package/dist/{chunk-3EEFL6NJ.js → chunk-67VNLLBN.js} +261 -1
- package/dist/{chunk-UZPBB7V4.js → chunk-ADUTW66W.js} +60 -48
- package/dist/{chunk-LEMX3IOG.js → chunk-BTNNEH2W.js} +1 -1
- package/dist/{chunk-YWO7O6QG.js → chunk-FBAYCS3X.js} +2 -2
- package/dist/{chunk-R26A7TWY.js → chunk-FKXFUUWJ.js} +1 -1
- package/dist/chunk-H4UNYFUC.js +196 -0
- package/dist/{chunk-CUE2LKLR.js → chunk-KFXF2V5G.js} +1 -1
- package/dist/{chunk-6IWNH776.js → chunk-LFFLHSQO.js} +4 -3
- package/dist/{chunk-C7T7B45R.js → chunk-LGGMJYY4.js} +150 -21
- package/dist/{chunk-MAZWKJGL.js → chunk-M6RIJVRE.js} +42 -232
- package/dist/{chunk-2JRX7ZR4.js → chunk-NAHHFSLH.js} +16 -5
- package/dist/{chunk-FI3LIRCB.js → chunk-OAAP2MY6.js} +2 -2
- package/dist/{chunk-Z7YPFWAZ.js → chunk-PCVGBE5B.js} +10 -268
- package/dist/chunk-WX52U7Z2.js +112 -0
- package/dist/{chunk-PPQYQEIR.js → chunk-ZB4F75SZ.js} +28 -0
- package/dist/daemon/server.js +30 -21
- package/dist/hook-runtime/daemon/server.js +30 -21
- package/dist/hook-runtime/hooks/post-commit.js +20 -20
- package/dist/hook-runtime/hooks/post-rewrite.js +20 -20
- package/dist/hook-runtime/hooks/post-tool-use.js +666 -147
- package/dist/hook-runtime/hooks/pre-tool-use.js +659 -111
- package/dist/hook-runtime/hooks/prim-hook.js +627 -111
- package/dist/hook-runtime/hooks/session-end.js +610 -27
- package/dist/hook-runtime/hooks/session-start.js +1132 -533
- package/dist/hook-runtime/index.js +764 -267
- package/dist/hook-runtime/statusline-main.js +48 -4
- package/dist/hooks/post-commit.js +11 -9
- package/dist/hooks/post-rewrite.js +11 -9
- package/dist/hooks/post-tool-use.js +73 -48
- package/dist/hooks/pre-commit.js +4 -4
- package/dist/hooks/pre-tool-use.js +46 -44
- package/dist/hooks/prim-hook.js +47 -25
- package/dist/hooks/session-end.js +15 -26
- package/dist/hooks/session-start.js +59 -32
- package/dist/index.js +561 -1430
- package/dist/statusline-main.js +48 -4
- package/package.json +1 -1
- package/dist/chunk-PMRDE2DA.js +0 -310
- package/dist/chunk-WB7AUZ4C.js +0 -52
|
@@ -311,7 +311,7 @@ function daemonRequest(method, params = {}, opts = {}) {
|
|
|
311
311
|
...caller ? { caller } : {}
|
|
312
312
|
};
|
|
313
313
|
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
314
|
-
return new Promise((
|
|
314
|
+
return new Promise((resolve4) => {
|
|
315
315
|
let settled = false;
|
|
316
316
|
let socket;
|
|
317
317
|
let buffer = Buffer.alloc(0);
|
|
@@ -326,7 +326,7 @@ function daemonRequest(method, params = {}, opts = {}) {
|
|
|
326
326
|
} catch {
|
|
327
327
|
}
|
|
328
328
|
}
|
|
329
|
-
|
|
329
|
+
resolve4(value);
|
|
330
330
|
};
|
|
331
331
|
const timer = setTimeout(() => settle(null), timeoutMs);
|
|
332
332
|
timer.unref();
|
|
@@ -389,14 +389,494 @@ function daemonRequest(method, params = {}, opts = {}) {
|
|
|
389
389
|
function parseAgent(argv) {
|
|
390
390
|
const i = argv.indexOf("--agent");
|
|
391
391
|
const value = i !== -1 ? argv[i + 1] : void 0;
|
|
392
|
-
if (value === "codex" || value === "hermes") {
|
|
392
|
+
if (value === "codex" || value === "cursor" || value === "hermes") {
|
|
393
393
|
return value;
|
|
394
394
|
}
|
|
395
395
|
return "claude_code";
|
|
396
396
|
}
|
|
397
397
|
|
|
398
|
+
// src/commands/cursor-install.ts
|
|
399
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
400
|
+
import { homedir as homedir3 } from "os";
|
|
401
|
+
import { isAbsolute as isAbsolute5, join as join8 } from "path";
|
|
402
|
+
|
|
403
|
+
// src/lib/atomic-file.ts
|
|
404
|
+
import { randomUUID } from "crypto";
|
|
405
|
+
import {
|
|
406
|
+
constants,
|
|
407
|
+
closeSync,
|
|
408
|
+
fchmodSync,
|
|
409
|
+
fsyncSync,
|
|
410
|
+
mkdirSync,
|
|
411
|
+
openSync,
|
|
412
|
+
renameSync,
|
|
413
|
+
rmSync,
|
|
414
|
+
writeFileSync
|
|
415
|
+
} from "fs";
|
|
416
|
+
import { dirname } from "path";
|
|
417
|
+
|
|
418
|
+
// src/lib/bin-path.ts
|
|
419
|
+
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
420
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4 } from "path";
|
|
421
|
+
import { fileURLToPath } from "url";
|
|
422
|
+
var PKG_NAME = "@primitive.ai/prim";
|
|
423
|
+
var STABLE_HOOK_LAUNCHER_NAME = "prim-hook-launcher-v1";
|
|
424
|
+
function shellQuote(value) {
|
|
425
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
426
|
+
}
|
|
427
|
+
var STABLE_HOOK_ARGUMENTS_RE = /^[-A-Za-z0-9_ ]*$/u;
|
|
428
|
+
var STABLE_HOOK_BIN_RE = /^[A-Za-z0-9][A-Za-z0-9-]*$/u;
|
|
429
|
+
function stableHookCommand(bin, args = "") {
|
|
430
|
+
if (!STABLE_HOOK_BIN_RE.test(bin) || !STABLE_HOOK_ARGUMENTS_RE.test(args)) {
|
|
431
|
+
throw new Error("invalid stable hook command");
|
|
432
|
+
}
|
|
433
|
+
const resolver = [
|
|
434
|
+
// #242 and earlier identify managed commands by this package marker. Keep
|
|
435
|
+
// it as inert data during the rolling window so an older uninstall or
|
|
436
|
+
// reinstall can remove the stable entry without invoking npm or PATH.
|
|
437
|
+
`prim_legacy_reader='-p ${PKG_NAME}@stable prim-shim.sh ${bin} '; `,
|
|
438
|
+
'prim_absolute() { case "$1" in [/]* ) ;; *) return 1 ;; esac; case "$1" in [[:space:]]*|*[[:space:]]|*[/][/]*|*[/].[/]*|*[/]..[/]*|*[/].|*[/]..|?*[/]) return 1 ;; esac; }; ',
|
|
439
|
+
'prim_config=${PRIM_CONFIG_DIR:-}; if ! prim_absolute "$prim_config"; then prim_config=${XDG_CONFIG_HOME:-}; if prim_absolute "$prim_config"; then ',
|
|
440
|
+
'case "$prim_config" in /) prim_config=/prim ;; *) prim_config="$prim_config/prim" ;; esac; ',
|
|
441
|
+
'else prim_home=${HOME:-}; prim_absolute "$prim_home" || exit 78; case "$prim_home" in /) prim_config=/.config/prim ;; *) prim_config="$prim_home/.config/prim" ;; esac; ',
|
|
442
|
+
"fi; fi; ",
|
|
443
|
+
`case "$prim_config" in /) prim_launcher=/${STABLE_HOOK_LAUNCHER_NAME} ;; *) prim_launcher="$prim_config/${STABLE_HOOK_LAUNCHER_NAME}" ;; esac; `,
|
|
444
|
+
'exec "$prim_launcher" "$@"'
|
|
445
|
+
].join("");
|
|
446
|
+
const suffix = args ? ` ${args}` : "";
|
|
447
|
+
return `/bin/sh -c ${shellQuote(resolver)} ${STABLE_HOOK_LAUNCHER_NAME} ${bin}${suffix}`;
|
|
448
|
+
}
|
|
449
|
+
function detachedHookShimCommand(bin, args = "") {
|
|
450
|
+
return `payload=$(cat); { trap '' HUP; printf '%s' "$payload" | { ${stableHookCommand(bin, args)}; }; } </dev/null >/dev/null 2>&1 &`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// src/lib/hook-runtime.ts
|
|
454
|
+
import { createHash as createHash3, randomBytes as randomBytes2 } from "crypto";
|
|
455
|
+
import {
|
|
456
|
+
constants as constants2,
|
|
457
|
+
accessSync,
|
|
458
|
+
chmodSync,
|
|
459
|
+
copyFileSync,
|
|
460
|
+
existsSync as existsSync3,
|
|
461
|
+
lstatSync,
|
|
462
|
+
mkdirSync as mkdirSync3,
|
|
463
|
+
mkdtempSync,
|
|
464
|
+
readFileSync as readFileSync4,
|
|
465
|
+
readdirSync,
|
|
466
|
+
realpathSync,
|
|
467
|
+
renameSync as renameSync2,
|
|
468
|
+
rmSync as rmSync3,
|
|
469
|
+
statSync as statSync2,
|
|
470
|
+
utimesSync,
|
|
471
|
+
writeFileSync as writeFileSync3
|
|
472
|
+
} from "fs";
|
|
473
|
+
import { delimiter, dirname as dirname4, isAbsolute as isAbsolute3, join as join6, resolve } from "path";
|
|
474
|
+
|
|
475
|
+
// src/lib/file-lock.ts
|
|
476
|
+
import { randomBytes } from "crypto";
|
|
477
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "fs";
|
|
478
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
479
|
+
var syncSleeper = new Int32Array(new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT));
|
|
480
|
+
|
|
481
|
+
// src/lib/hook-runtime.ts
|
|
482
|
+
var RELEASE_PREFIX = "release-";
|
|
483
|
+
var MAX_MANIFEST_BYTES = 64 * 1024;
|
|
484
|
+
var RELEASE_GRACE_MS = 10 * 60 * 1e3;
|
|
485
|
+
var HOOK_RUNTIME_ENTRIES = {
|
|
486
|
+
// SessionStart self-healing resolves this exact CLI entry to run
|
|
487
|
+
// `daemon ensure`; it must travel with the immutable hook release.
|
|
488
|
+
prim: "dist/index.js",
|
|
489
|
+
"prim-hook": "dist/hooks/prim-hook.js",
|
|
490
|
+
"prim-pre-commit": "dist/hooks/pre-commit.js",
|
|
491
|
+
"prim-post-commit": "dist/hooks/post-commit.js",
|
|
492
|
+
"prim-post-rewrite": "dist/hooks/post-rewrite.js",
|
|
493
|
+
"prim-pre-tool-use": "dist/hooks/pre-tool-use.js",
|
|
494
|
+
"prim-post-tool-use": "dist/hooks/post-tool-use.js",
|
|
495
|
+
"prim-session-start": "dist/hooks/session-start.js",
|
|
496
|
+
"prim-session-end": "dist/hooks/session-end.js",
|
|
497
|
+
"prim-daemon-server": "dist/daemon/server.js",
|
|
498
|
+
"prim-statusline": "dist/statusline-main.js"
|
|
499
|
+
};
|
|
500
|
+
var SOURCE_ENTRIES = Object.fromEntries(
|
|
501
|
+
Object.entries(HOOK_RUNTIME_ENTRIES).map(([bin, target]) => [
|
|
502
|
+
bin,
|
|
503
|
+
target.replace(/^dist\//u, "")
|
|
504
|
+
])
|
|
505
|
+
);
|
|
506
|
+
var STABLE_HOOK_LAUNCHER_CONTENT = `#!/bin/sh
|
|
507
|
+
set -eu
|
|
508
|
+
case "$0" in */*) prim_launcher_dir=\${0%/*} ;; *) exit 78 ;; esac
|
|
509
|
+
IFS= read -r prim_release_name < "$prim_launcher_dir/hook-runtime/current" || exit 69
|
|
510
|
+
case "$prim_release_name" in ${RELEASE_PREFIX}*) ;; *) exit 69 ;; esac
|
|
511
|
+
prim_release_id=\${prim_release_name#${RELEASE_PREFIX}}
|
|
512
|
+
case "$prim_release_id" in *[!0-9a-f]*|"") exit 69 ;; esac
|
|
513
|
+
[ "\${#prim_release_id}" -eq 64 ] || exit 69
|
|
514
|
+
prim_release="$prim_launcher_dir/hook-runtime/releases/$prim_release_name"
|
|
515
|
+
case "\${1-}" in
|
|
516
|
+
${Object.entries(HOOK_RUNTIME_ENTRIES).map(([bin, entry]) => ` ${bin}) prim_entry="$prim_release/${entry}" ;;`).join("\n")}
|
|
517
|
+
*) exit 64 ;;
|
|
518
|
+
esac
|
|
519
|
+
shift
|
|
520
|
+
IFS= read -r prim_node < "$prim_release/node" || exit 69
|
|
521
|
+
case "$prim_node" in /*) ;; *) exit 69 ;; esac
|
|
522
|
+
[ -x "$prim_node" ] && [ -f "$prim_entry" ] || exit 69
|
|
523
|
+
exec "$prim_node" "$prim_entry" "$@"
|
|
524
|
+
`;
|
|
525
|
+
|
|
526
|
+
// src/commands/claude-install.ts
|
|
527
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
|
|
528
|
+
import { homedir as homedir2 } from "os";
|
|
529
|
+
import { join as join7 } from "path";
|
|
530
|
+
|
|
531
|
+
// src/lib/git.ts
|
|
532
|
+
import { execFileSync } from "child_process";
|
|
533
|
+
import { createHash as createHash4 } from "crypto";
|
|
534
|
+
import { lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
|
|
535
|
+
import { basename, dirname as dirname5, isAbsolute as isAbsolute4, relative, resolve as resolve2, sep } from "path";
|
|
536
|
+
var GIT_TIMEOUT_MS = 1e3;
|
|
537
|
+
var GITHUB_OWNER_PATTERN = /^(?![A-Za-z0-9-]*--)[A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?$/u;
|
|
538
|
+
var GITHUB_REPOSITORY_PATTERN = /^[A-Za-z0-9._-]{1,100}$/u;
|
|
539
|
+
function gitToplevel(cwd) {
|
|
540
|
+
try {
|
|
541
|
+
return execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
542
|
+
cwd,
|
|
543
|
+
encoding: "utf-8",
|
|
544
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
545
|
+
timeout: GIT_TIMEOUT_MS
|
|
546
|
+
}).trim();
|
|
547
|
+
} catch {
|
|
548
|
+
return null;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
function gitValue(cwd, args) {
|
|
552
|
+
try {
|
|
553
|
+
const value = execFileSync("git", args, {
|
|
554
|
+
cwd,
|
|
555
|
+
encoding: "utf-8",
|
|
556
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
557
|
+
timeout: GIT_TIMEOUT_MS
|
|
558
|
+
}).trim();
|
|
559
|
+
return value || void 0;
|
|
560
|
+
} catch {
|
|
561
|
+
return void 0;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
function githubRepositoryFullName(cwd) {
|
|
565
|
+
const remote = gitValue(cwd, ["config", "--get", "remote.origin.url"]);
|
|
566
|
+
if (!remote || [...remote].some((char) => char < " " || char === "\x7F")) return null;
|
|
567
|
+
const match = /^(?:git@github\.com:([^?#]+)|(?:https|ssh):\/\/(?:[^/@]+@)?github\.com(?::\d+)?\/([^?#]+))$/i.exec(
|
|
568
|
+
remote
|
|
569
|
+
);
|
|
570
|
+
const path = match?.[1] ?? match?.[2];
|
|
571
|
+
if (!path) return null;
|
|
572
|
+
const parts = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "").split("/");
|
|
573
|
+
if (parts.length !== 2 || !GITHUB_OWNER_PATTERN.test(parts[0] ?? "") || !GITHUB_REPOSITORY_PATTERN.test(parts[1] ?? "")) {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
return `${parts[0]}/${parts[1]}`;
|
|
577
|
+
}
|
|
578
|
+
function normalizeOriginRemote(remote) {
|
|
579
|
+
const value = remote.trim();
|
|
580
|
+
if (!value) return void 0;
|
|
581
|
+
const scp = !value.includes("://") ? /^(?:[^@/\s]+@)?(?<host>[^:/\s]+):(?<path>.+)$/.exec(value)?.groups : void 0;
|
|
582
|
+
if (scp?.host && scp.path) {
|
|
583
|
+
const path = scp.path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
|
|
584
|
+
return path ? `${scp.host.toLowerCase()}/${path}` : void 0;
|
|
585
|
+
}
|
|
586
|
+
try {
|
|
587
|
+
const url = new URL(value);
|
|
588
|
+
url.username = "";
|
|
589
|
+
url.password = "";
|
|
590
|
+
url.search = "";
|
|
591
|
+
url.hash = "";
|
|
592
|
+
const path = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
|
|
593
|
+
if (url.host && path) return `${url.host.toLowerCase()}/${path}`;
|
|
594
|
+
return void 0;
|
|
595
|
+
} catch {
|
|
596
|
+
return void 0;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function opaqueRepoKey(source, value) {
|
|
600
|
+
return `repo_v1_${createHash4("sha256").update(`${source}\0${value}`).digest("hex")}`;
|
|
601
|
+
}
|
|
602
|
+
function resolveRepositoryContext(cwd = process.cwd()) {
|
|
603
|
+
const top = gitToplevel(cwd);
|
|
604
|
+
if (!top) return null;
|
|
605
|
+
let repoRoot;
|
|
606
|
+
try {
|
|
607
|
+
repoRoot = realpathSync2.native(top);
|
|
608
|
+
} catch {
|
|
609
|
+
repoRoot = resolve2(top);
|
|
610
|
+
}
|
|
611
|
+
const origin = gitValue(repoRoot, ["config", "--get", "remote.origin.url"]);
|
|
612
|
+
const repoFullName = githubRepositoryFullName(repoRoot) ?? void 0;
|
|
613
|
+
const normalizedOrigin = origin ? normalizeOriginRemote(origin) : void 0;
|
|
614
|
+
if (normalizedOrigin) {
|
|
615
|
+
return {
|
|
616
|
+
repoRoot,
|
|
617
|
+
repoKey: opaqueRepoKey("origin", normalizedOrigin),
|
|
618
|
+
identitySource: "origin",
|
|
619
|
+
repoFullName
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
const roots = gitValue(repoRoot, ["rev-list", "--max-parents=0", "HEAD"])?.split(/\s+/).filter(Boolean).sort();
|
|
623
|
+
if (roots && roots.length > 0) {
|
|
624
|
+
return {
|
|
625
|
+
repoRoot,
|
|
626
|
+
repoKey: opaqueRepoKey("root_commit", roots.join("\n")),
|
|
627
|
+
identitySource: "root_commit",
|
|
628
|
+
repoFullName
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return { repoRoot, repoFullName };
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/commands/claude-install.ts
|
|
635
|
+
var CAPTURE_BIN = "prim-hook";
|
|
636
|
+
var GATE_BIN = "prim-pre-tool-use";
|
|
637
|
+
var POST_TOOL_USE_BIN = "prim-post-tool-use";
|
|
638
|
+
var SESSION_START_BIN = "prim-session-start";
|
|
639
|
+
var SESSION_END_BIN = "prim-session-end";
|
|
640
|
+
var STATUSLINE_BIN = "prim-statusline";
|
|
641
|
+
var STATUSLINE_ARGS = "";
|
|
642
|
+
var STATUSLINE_COMMAND = stableHookCommand(STATUSLINE_BIN, STATUSLINE_ARGS);
|
|
643
|
+
var USER_SCOPE_PATH = join7(homedir2(), ".claude", "settings.json");
|
|
644
|
+
function projectRoot() {
|
|
645
|
+
return gitToplevel() ?? process.cwd();
|
|
646
|
+
}
|
|
647
|
+
var CAPTURE_EVENTS = [
|
|
648
|
+
"SessionStart",
|
|
649
|
+
"UserPromptSubmit",
|
|
650
|
+
"PreToolUse",
|
|
651
|
+
"PostToolUse",
|
|
652
|
+
"PostToolUseFailure",
|
|
653
|
+
"Stop",
|
|
654
|
+
"SessionEnd",
|
|
655
|
+
"SubagentStop"
|
|
656
|
+
];
|
|
657
|
+
function makeRegistration(event, matcher, bin, args = "") {
|
|
658
|
+
return {
|
|
659
|
+
event,
|
|
660
|
+
matcher,
|
|
661
|
+
bin,
|
|
662
|
+
command: stableHookCommand(bin, args)
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function makeDetachedRegistration(event, matcher, bin, args = "") {
|
|
666
|
+
return { event, matcher, bin, command: detachedHookShimCommand(bin, args) };
|
|
667
|
+
}
|
|
668
|
+
var REGISTRATIONS = [
|
|
669
|
+
...CAPTURE_EVENTS.map(
|
|
670
|
+
(event) => event === "SessionEnd" ? makeDetachedRegistration(event, "*", CAPTURE_BIN) : makeRegistration(event, "*", CAPTURE_BIN)
|
|
671
|
+
),
|
|
672
|
+
makeRegistration("PreToolUse", "Edit|Write|MultiEdit|NotebookEdit|Bash", GATE_BIN),
|
|
673
|
+
makeRegistration("PostToolUse", "Edit|Write|MultiEdit|NotebookEdit|Bash", POST_TOOL_USE_BIN),
|
|
674
|
+
makeRegistration(
|
|
675
|
+
"PostToolUseFailure",
|
|
676
|
+
"Edit|Write|MultiEdit|NotebookEdit|Bash",
|
|
677
|
+
POST_TOOL_USE_BIN
|
|
678
|
+
),
|
|
679
|
+
makeRegistration("SessionStart", "*", SESSION_START_BIN),
|
|
680
|
+
makeDetachedRegistration("SessionEnd", "*", SESSION_END_BIN)
|
|
681
|
+
];
|
|
682
|
+
var PRIM_PERMISSION_RULE = "Bash(npx --yes @primitive.ai/prim*)";
|
|
683
|
+
var LEGACY_PRIM_PERMISSION_RULES = [
|
|
684
|
+
"Bash(npx --yes @primitive.ai/prim@latest:*)",
|
|
685
|
+
"Bash(npx --yes @primitive.ai/prim:*)"
|
|
686
|
+
];
|
|
687
|
+
var ALL_PRIM_PERMISSION_RULES = /* @__PURE__ */ new Set([
|
|
688
|
+
PRIM_PERMISSION_RULE,
|
|
689
|
+
...LEGACY_PRIM_PERMISSION_RULES
|
|
690
|
+
]);
|
|
691
|
+
|
|
692
|
+
// src/commands/cursor-install.ts
|
|
693
|
+
var CAPTURE_BIN2 = "prim-hook";
|
|
694
|
+
var GATE_BIN2 = "prim-pre-tool-use";
|
|
695
|
+
var POST_TOOL_BIN = "prim-post-tool-use";
|
|
696
|
+
var SESSION_START_BIN2 = "prim-session-start";
|
|
697
|
+
var SESSION_END_BIN2 = "prim-session-end";
|
|
698
|
+
var STATUSLINE_BIN2 = "prim-statusline";
|
|
699
|
+
var CURSOR_EVENTS = [
|
|
700
|
+
"sessionStart",
|
|
701
|
+
"beforeSubmitPrompt",
|
|
702
|
+
"preToolUse",
|
|
703
|
+
"postToolUse",
|
|
704
|
+
"postToolUseFailure",
|
|
705
|
+
"afterAgentResponse",
|
|
706
|
+
"subagentStop",
|
|
707
|
+
"stop",
|
|
708
|
+
"sessionEnd"
|
|
709
|
+
];
|
|
710
|
+
var REGISTRATIONS2 = [
|
|
711
|
+
...CURSOR_EVENTS.map((event) => ({ event, bin: CAPTURE_BIN2 })),
|
|
712
|
+
{ event: "preToolUse", bin: GATE_BIN2, matcher: "Write|Delete|Shell", timeout: 10 },
|
|
713
|
+
{ event: "postToolUse", bin: POST_TOOL_BIN },
|
|
714
|
+
{ event: "postToolUseFailure", bin: POST_TOOL_BIN },
|
|
715
|
+
{ event: "sessionStart", bin: SESSION_START_BIN2 },
|
|
716
|
+
{ event: "sessionEnd", bin: SESSION_END_BIN2 }
|
|
717
|
+
];
|
|
718
|
+
function cursorConfigDirectory(env = process.env, home = homedir3(), platform = process.platform) {
|
|
719
|
+
const explicit = env.CURSOR_CONFIG_DIR?.trim();
|
|
720
|
+
if (explicit && isAbsolute5(explicit)) return explicit;
|
|
721
|
+
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
722
|
+
if (platform !== "darwin" && xdg && isAbsolute5(xdg)) return join8(xdg, "cursor");
|
|
723
|
+
return join8(home, ".cursor");
|
|
724
|
+
}
|
|
725
|
+
function cursorHooksPath(scope, root = projectRoot()) {
|
|
726
|
+
return scope === "user" ? join8(cursorConfigDirectory(), "hooks.json") : join8(root, ".cursor", "hooks.json");
|
|
727
|
+
}
|
|
728
|
+
function plainObject(value) {
|
|
729
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
730
|
+
}
|
|
731
|
+
function readCursorHooks(path) {
|
|
732
|
+
if (!existsSync5(path)) return {};
|
|
733
|
+
let parsed;
|
|
734
|
+
try {
|
|
735
|
+
parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
736
|
+
} catch {
|
|
737
|
+
throw new Error(`invalid Cursor hooks JSON at ${path}`);
|
|
738
|
+
}
|
|
739
|
+
if (!plainObject(parsed)) throw new Error(`Cursor hooks config must be an object: ${path}`);
|
|
740
|
+
if (parsed.version !== void 0 && parsed.version !== 1) {
|
|
741
|
+
throw new Error(`unsupported Cursor hooks version at ${path} (expected version 1)`);
|
|
742
|
+
}
|
|
743
|
+
if (parsed.hooks !== void 0) {
|
|
744
|
+
if (!plainObject(parsed.hooks)) throw new Error(`Cursor hooks must be an object: ${path}`);
|
|
745
|
+
for (const [event, entries] of Object.entries(parsed.hooks)) {
|
|
746
|
+
if (!Array.isArray(entries) || !entries.every(plainObject)) {
|
|
747
|
+
throw new Error(`Cursor hook list must contain objects: ${event} in ${path}`);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return parsed;
|
|
752
|
+
}
|
|
753
|
+
function commandFor(registration, scope) {
|
|
754
|
+
return stableHookCommand(
|
|
755
|
+
registration.bin,
|
|
756
|
+
`--agent cursor --event ${registration.event} --scope ${scope}`
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
var OWNED_COMMANDS = new Set(
|
|
760
|
+
["project", "user"].flatMap(
|
|
761
|
+
(scope) => REGISTRATIONS2.map((registration) => commandFor(registration, scope))
|
|
762
|
+
)
|
|
763
|
+
);
|
|
764
|
+
function jsoncUnsafeCommand(command) {
|
|
765
|
+
return command.replace('case "$1" in [/]* ', 'case "$1" in /*').replace("*[/][/]*", "*//*").replace("*[/].[/]*", "*/./*").replace("*[/]..[/]*", "*/../*").replace("*[/].", "*/.").replace("*[/]..", "*/..").replace("?*[/]", "?*/");
|
|
766
|
+
}
|
|
767
|
+
var JSONC_UNSAFE_OWNED_COMMANDS = new Set([...OWNED_COMMANDS].map(jsoncUnsafeCommand));
|
|
768
|
+
function hookFor(registration, scope) {
|
|
769
|
+
return {
|
|
770
|
+
command: commandFor(registration, scope),
|
|
771
|
+
...registration.matcher ? { matcher: registration.matcher } : {},
|
|
772
|
+
...registration.timeout ? { timeout: registration.timeout } : {}
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function exactHook(entry, registration, scope) {
|
|
776
|
+
return JSON.stringify(entry) === JSON.stringify(hookFor(registration, scope));
|
|
777
|
+
}
|
|
778
|
+
function hasExactCursorHandler(settings, event, bin, scope) {
|
|
779
|
+
const registration = REGISTRATIONS2.find(
|
|
780
|
+
(candidate) => candidate.event === event && candidate.bin === bin
|
|
781
|
+
);
|
|
782
|
+
return registration ? (settings.hooks?.[event] ?? []).some((entry) => exactHook(entry, registration, scope)) : false;
|
|
783
|
+
}
|
|
784
|
+
var FOOTER = {
|
|
785
|
+
type: "command",
|
|
786
|
+
command: stableHookCommand(STATUSLINE_BIN2),
|
|
787
|
+
updateIntervalMs: 1e3,
|
|
788
|
+
timeoutMs: 2e3
|
|
789
|
+
};
|
|
790
|
+
var JSONC_UNSAFE_FOOTER = {
|
|
791
|
+
...FOOTER,
|
|
792
|
+
command: jsoncUnsafeCommand(FOOTER.command)
|
|
793
|
+
};
|
|
794
|
+
|
|
795
|
+
// src/hooks/cursor-coexistence.ts
|
|
796
|
+
var CURSOR_EVENTS2 = /* @__PURE__ */ new Set([
|
|
797
|
+
"sessionStart",
|
|
798
|
+
"beforeSubmitPrompt",
|
|
799
|
+
"preToolUse",
|
|
800
|
+
"postToolUse",
|
|
801
|
+
"postToolUseFailure",
|
|
802
|
+
"afterAgentResponse",
|
|
803
|
+
"subagentStop",
|
|
804
|
+
"stop",
|
|
805
|
+
"sessionEnd"
|
|
806
|
+
]);
|
|
807
|
+
function cursorEvidence(parsed) {
|
|
808
|
+
return typeof parsed.cursor_version === "string" && parsed.cursor_version.length > 0 && typeof parsed.conversation_id === "string" && parsed.conversation_id.length > 0 && typeof parsed.generation_id === "string" && parsed.generation_id.length > 0 && Array.isArray(parsed.workspace_roots) && typeof parsed.hook_event_name === "string" && CURSOR_EVENTS2.has(parsed.hook_event_name);
|
|
809
|
+
}
|
|
810
|
+
function uniqueProjectRoot(parsed) {
|
|
811
|
+
if (!Array.isArray(parsed.workspace_roots)) return;
|
|
812
|
+
const roots = new Set(
|
|
813
|
+
parsed.workspace_roots.filter((value) => typeof value === "string" && value.length > 0).map((value) => resolveRepositoryContext(value)?.repoRoot).filter((value) => !!value)
|
|
814
|
+
);
|
|
815
|
+
return roots.size === 1 ? [...roots][0] : void 0;
|
|
816
|
+
}
|
|
817
|
+
function shouldSuppressImportedCursorHandler(parsed, bin) {
|
|
818
|
+
if (!cursorEvidence(parsed)) return false;
|
|
819
|
+
const project = uniqueProjectRoot(parsed);
|
|
820
|
+
if (project) {
|
|
821
|
+
try {
|
|
822
|
+
if (hasExactCursorHandler(
|
|
823
|
+
readCursorHooks(cursorHooksPath("project", project)),
|
|
824
|
+
parsed.hook_event_name,
|
|
825
|
+
bin,
|
|
826
|
+
"project"
|
|
827
|
+
)) {
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
} catch {
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
try {
|
|
834
|
+
return hasExactCursorHandler(
|
|
835
|
+
readCursorHooks(cursorHooksPath("user")),
|
|
836
|
+
parsed.hook_event_name,
|
|
837
|
+
bin,
|
|
838
|
+
"user"
|
|
839
|
+
);
|
|
840
|
+
} catch {
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/hooks/hook-stdin.ts
|
|
846
|
+
var DEFAULT_MAX_BYTES = 1024 * 1024;
|
|
847
|
+
function readHookStdin(timeoutMs = 1e3, maxBytes = DEFAULT_MAX_BYTES) {
|
|
848
|
+
return new Promise((resolve4, reject) => {
|
|
849
|
+
const chunks = [];
|
|
850
|
+
let bytes = 0;
|
|
851
|
+
let settled = false;
|
|
852
|
+
const finish = (error) => {
|
|
853
|
+
if (settled) return;
|
|
854
|
+
settled = true;
|
|
855
|
+
clearTimeout(timer);
|
|
856
|
+
process.stdin.removeAllListeners("data");
|
|
857
|
+
process.stdin.removeAllListeners("end");
|
|
858
|
+
process.stdin.removeAllListeners("error");
|
|
859
|
+
if (error) reject(error);
|
|
860
|
+
else resolve4(Buffer.concat(chunks, bytes).toString("utf-8"));
|
|
861
|
+
};
|
|
862
|
+
const timer = setTimeout(() => finish(new Error("stdin read timeout")), timeoutMs);
|
|
863
|
+
process.stdin.on("data", (chunk) => {
|
|
864
|
+
bytes += chunk.length;
|
|
865
|
+
if (bytes > maxBytes) {
|
|
866
|
+
finish(new Error("stdin envelope exceeds size limit"));
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
chunks.push(chunk);
|
|
870
|
+
});
|
|
871
|
+
process.stdin.on("end", () => finish());
|
|
872
|
+
process.stdin.on("error", (error) => finish(error));
|
|
873
|
+
});
|
|
874
|
+
}
|
|
875
|
+
|
|
398
876
|
// src/hooks/normalize.ts
|
|
399
|
-
import { createHash as
|
|
877
|
+
import { createHash as createHash5 } from "crypto";
|
|
878
|
+
import { realpathSync as realpathSync3 } from "fs";
|
|
879
|
+
import { isAbsolute as isAbsolute6, resolve as resolve3 } from "path";
|
|
400
880
|
var HERMES_EVENT_MAP = {
|
|
401
881
|
on_session_start: "SessionStart",
|
|
402
882
|
on_session_end: "SessionEnd",
|
|
@@ -406,7 +886,125 @@ var HERMES_EVENT_MAP = {
|
|
|
406
886
|
post_tool_call: "PostToolUse",
|
|
407
887
|
subagent_stop: "SubagentStop"
|
|
408
888
|
};
|
|
889
|
+
var CURSOR_EVENT_MAP = {
|
|
890
|
+
sessionStart: "SessionStart",
|
|
891
|
+
beforeSubmitPrompt: "UserPromptSubmit",
|
|
892
|
+
preToolUse: "PreToolUse",
|
|
893
|
+
postToolUse: "PostToolUse",
|
|
894
|
+
postToolUseFailure: "PostToolUseFailure",
|
|
895
|
+
afterAgentResponse: "AssistantResponse",
|
|
896
|
+
subagentStop: "SubagentStop",
|
|
897
|
+
stop: "Stop",
|
|
898
|
+
sessionEnd: "SessionEnd"
|
|
899
|
+
};
|
|
900
|
+
var CURSOR_TOOL_EVENTS = /* @__PURE__ */ new Set(["PreToolUse", "PostToolUse", "PostToolUseFailure"]);
|
|
901
|
+
var SAFE_CORRELATION_ID_RE = /^[A-Za-z0-9._:-]{1,128}$/u;
|
|
902
|
+
var CURSOR_LOCAL_ONLY_FIELDS = /* @__PURE__ */ new Set([
|
|
903
|
+
"conversation_id",
|
|
904
|
+
"generation_id",
|
|
905
|
+
"user_email",
|
|
906
|
+
"account_email",
|
|
907
|
+
"transcript_path",
|
|
908
|
+
"transcript",
|
|
909
|
+
"thought",
|
|
910
|
+
"thoughts",
|
|
911
|
+
"thinking",
|
|
912
|
+
"tool_output"
|
|
913
|
+
]);
|
|
914
|
+
function requiredId(value, field) {
|
|
915
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
916
|
+
throw new TypeError(`Cursor hook envelope is missing ${field}`);
|
|
917
|
+
}
|
|
918
|
+
return value;
|
|
919
|
+
}
|
|
920
|
+
function cursorToolUseId(value) {
|
|
921
|
+
if (SAFE_CORRELATION_ID_RE.test(value)) return value;
|
|
922
|
+
return `cursor:tool:v1:${createHash5("sha256").update(value).digest("hex")}`;
|
|
923
|
+
}
|
|
924
|
+
function workspaceCandidates(value) {
|
|
925
|
+
if (!Array.isArray(value)) return [];
|
|
926
|
+
const roots = value.filter((item) => typeof item === "string" && item.length > 0);
|
|
927
|
+
if (roots.some((root) => !isAbsolute6(root))) {
|
|
928
|
+
throw new TypeError("Cursor workspace roots must be absolute");
|
|
929
|
+
}
|
|
930
|
+
return roots;
|
|
931
|
+
}
|
|
932
|
+
function pathIdentity(path) {
|
|
933
|
+
try {
|
|
934
|
+
return realpathSync3.native(path);
|
|
935
|
+
} catch {
|
|
936
|
+
return resolve3(path);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
function resolveCursorCwd(parsed) {
|
|
940
|
+
const roots = workspaceCandidates(parsed.workspace_roots).map(pathIdentity);
|
|
941
|
+
const rootRepositories = roots.map((root) => resolveRepositoryContext(root)?.repoRoot);
|
|
942
|
+
const repositoryRoots = new Set(rootRepositories.filter((root) => !!root));
|
|
943
|
+
if (repositoryRoots.size > 1) {
|
|
944
|
+
throw new TypeError("Cursor hook envelope spans multiple repositories");
|
|
945
|
+
}
|
|
946
|
+
if (roots.length > 1 && rootRepositories.some((root) => root === void 0)) {
|
|
947
|
+
throw new TypeError("Cursor hook envelope has ambiguous workspace roots");
|
|
948
|
+
}
|
|
949
|
+
const expectedRepo = [...repositoryRoots][0];
|
|
950
|
+
const rawCwd = typeof parsed.cwd === "string" && parsed.cwd.length > 0 ? parsed.cwd : void 0;
|
|
951
|
+
if (rawCwd && !isAbsolute6(rawCwd)) {
|
|
952
|
+
throw new TypeError("Cursor hook cwd must be absolute");
|
|
953
|
+
}
|
|
954
|
+
const envelopeCwd = rawCwd ? pathIdentity(rawCwd) : void 0;
|
|
955
|
+
const input = typeof parsed.tool_input === "object" && parsed.tool_input !== null && !Array.isArray(parsed.tool_input) ? parsed.tool_input : void 0;
|
|
956
|
+
const rawToolCwd = parsed.tool_name === "Shell" && typeof input?.working_directory === "string" ? input.working_directory : void 0;
|
|
957
|
+
const base = envelopeCwd ?? (roots.length === 1 ? roots[0] : expectedRepo);
|
|
958
|
+
if (rawToolCwd && !isAbsolute6(rawToolCwd) && !base) {
|
|
959
|
+
throw new TypeError("relative Cursor tool cwd has no workspace base");
|
|
960
|
+
}
|
|
961
|
+
const toolCwd = rawToolCwd ? pathIdentity(isAbsolute6(rawToolCwd) ? rawToolCwd : resolve3(base, rawToolCwd)) : void 0;
|
|
962
|
+
const selected = toolCwd ?? envelopeCwd ?? (roots.length === 1 ? roots[0] : expectedRepo);
|
|
963
|
+
if (!selected) {
|
|
964
|
+
throw new TypeError("Cursor hook envelope has no unambiguous workspace");
|
|
965
|
+
}
|
|
966
|
+
const selectedRepo = resolveRepositoryContext(selected)?.repoRoot;
|
|
967
|
+
if (expectedRepo && selectedRepo !== expectedRepo) {
|
|
968
|
+
throw new TypeError("Cursor hook cwd conflicts with workspace repository");
|
|
969
|
+
}
|
|
970
|
+
return selected;
|
|
971
|
+
}
|
|
972
|
+
function normalizeCursorEnvelope(parsed) {
|
|
973
|
+
const rawEvent = requiredId(parsed.hook_event_name, "hook_event_name");
|
|
974
|
+
const event = CURSOR_EVENT_MAP[rawEvent];
|
|
975
|
+
if (!event) throw new TypeError(`unsupported Cursor hook event: ${rawEvent}`);
|
|
976
|
+
const sessionId = requiredId(parsed.conversation_id, "conversation_id");
|
|
977
|
+
const turnId = requiredId(parsed.generation_id, "generation_id");
|
|
978
|
+
if (parsed.session_id !== void 0 && parsed.session_id !== sessionId) {
|
|
979
|
+
throw new TypeError("Cursor conversation_id conflicts with session_id");
|
|
980
|
+
}
|
|
981
|
+
if (parsed.turn_id !== void 0 && parsed.turn_id !== turnId) {
|
|
982
|
+
throw new TypeError("Cursor generation_id conflicts with turn_id");
|
|
983
|
+
}
|
|
984
|
+
const rawToolUseId = CURSOR_TOOL_EVENTS.has(event) ? requiredId(parsed.tool_use_id, "tool_use_id") : void 0;
|
|
985
|
+
const normalized = {
|
|
986
|
+
...Object.fromEntries(
|
|
987
|
+
Object.entries(parsed).filter(([key]) => !CURSOR_LOCAL_ONLY_FIELDS.has(key))
|
|
988
|
+
),
|
|
989
|
+
hook_event_name: event,
|
|
990
|
+
session_id: sessionId,
|
|
991
|
+
turn_id: turnId,
|
|
992
|
+
cwd: resolveCursorCwd(parsed),
|
|
993
|
+
...rawToolUseId === void 0 ? {} : { tool_use_id: cursorToolUseId(rawToolUseId) }
|
|
994
|
+
};
|
|
995
|
+
if (typeof parsed.tool_output === "string") {
|
|
996
|
+
try {
|
|
997
|
+
normalized.tool_response = JSON.parse(parsed.tool_output);
|
|
998
|
+
} catch {
|
|
999
|
+
normalized.tool_response = parsed.tool_output;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return normalized;
|
|
1003
|
+
}
|
|
409
1004
|
function normalizeEnvelope(parsed, agent) {
|
|
1005
|
+
if (agent === "cursor") {
|
|
1006
|
+
return normalizeCursorEnvelope(parsed);
|
|
1007
|
+
}
|
|
410
1008
|
if (agent === "codex") {
|
|
411
1009
|
const event = parsed.hook_event_name;
|
|
412
1010
|
if ((event === "PreToolUse" || event === "PostToolUse") && (typeof parsed.tool_use_id !== "string" || parsed.tool_use_id.length === 0)) {
|
|
@@ -418,7 +1016,7 @@ function normalizeEnvelope(parsed, agent) {
|
|
|
418
1016
|
]);
|
|
419
1017
|
return {
|
|
420
1018
|
...parsed,
|
|
421
|
-
tool_use_id: `codex:fallback:v1:${
|
|
1019
|
+
tool_use_id: `codex:fallback:v1:${createHash5("sha256").update(stableToolIdentity).digest("hex")}`
|
|
422
1020
|
};
|
|
423
1021
|
}
|
|
424
1022
|
return parsed;
|
|
@@ -436,23 +1034,6 @@ function normalizeEnvelope(parsed, agent) {
|
|
|
436
1034
|
// src/hooks/session-end.ts
|
|
437
1035
|
var STDIN_TIMEOUT_MS = 1e3;
|
|
438
1036
|
var DAEMON_TIMEOUT_MS = 250;
|
|
439
|
-
function readStdin() {
|
|
440
|
-
return new Promise((resolve, reject) => {
|
|
441
|
-
const chunks = [];
|
|
442
|
-
const timer = setTimeout(() => {
|
|
443
|
-
reject(new Error("stdin read timeout"));
|
|
444
|
-
}, STDIN_TIMEOUT_MS);
|
|
445
|
-
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
446
|
-
process.stdin.on("end", () => {
|
|
447
|
-
clearTimeout(timer);
|
|
448
|
-
resolve(Buffer.concat(chunks).toString("utf-8"));
|
|
449
|
-
});
|
|
450
|
-
process.stdin.on("error", (err) => {
|
|
451
|
-
clearTimeout(timer);
|
|
452
|
-
reject(err);
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
1037
|
function emit() {
|
|
457
1038
|
process.stdout.write("{}\n");
|
|
458
1039
|
}
|
|
@@ -460,17 +1041,19 @@ async function main() {
|
|
|
460
1041
|
const agent = parseAgent(process.argv);
|
|
461
1042
|
let raw;
|
|
462
1043
|
try {
|
|
463
|
-
raw = await
|
|
1044
|
+
raw = await readHookStdin(STDIN_TIMEOUT_MS);
|
|
464
1045
|
} catch {
|
|
465
1046
|
emit();
|
|
466
1047
|
return;
|
|
467
1048
|
}
|
|
468
1049
|
let envelope;
|
|
469
1050
|
try {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
1051
|
+
const incoming = JSON.parse(raw);
|
|
1052
|
+
if (agent !== "cursor" && shouldSuppressImportedCursorHandler(incoming, "prim-session-end")) {
|
|
1053
|
+
emit();
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
envelope = normalizeEnvelope(incoming, agent);
|
|
474
1057
|
} catch {
|
|
475
1058
|
emit();
|
|
476
1059
|
return;
|