@codemem/opencode-plugin 0.41.0 → 0.43.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/.opencode/plugins/codemem.js +207 -62
- package/package.json +2 -2
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
|
|
17
17
|
const TRUTHY_VALUES = ["1", "true", "yes"];
|
|
18
18
|
const DISABLED_VALUES = ["0", "false", "off"];
|
|
19
|
-
const PINNED_BACKEND_VERSION = "0.
|
|
19
|
+
const PINNED_BACKEND_VERSION = "0.43.0";
|
|
20
20
|
const COMPAT_CHECK_DELAY_MS = 1500;
|
|
21
21
|
const COMPAT_CHECK_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
22
22
|
const MAX_UPDATE_STATUS_BYTES = 16 * 1024;
|
|
@@ -623,6 +623,31 @@ const parsePackMetrics = (stdout) => {
|
|
|
623
623
|
}
|
|
624
624
|
};
|
|
625
625
|
|
|
626
|
+
const buildViewerIdentityTarget = (env = process.env, cwd = process.cwd()) => {
|
|
627
|
+
const normalizeIdentityPath = (value) => {
|
|
628
|
+
const trimmed = String(value || "").trim();
|
|
629
|
+
if (!trimmed) return null;
|
|
630
|
+
const expanded = trimmed.startsWith("~/")
|
|
631
|
+
? join(env.HOME?.trim() || homedir(), trimmed.slice(2))
|
|
632
|
+
: trimmed;
|
|
633
|
+
return resolve(cwd, expanded);
|
|
634
|
+
};
|
|
635
|
+
return {
|
|
636
|
+
device_id: env.CODEMEM_DEVICE_ID?.trim() || null,
|
|
637
|
+
actor_id_present: Object.hasOwn(env, "CODEMEM_ACTOR_ID"),
|
|
638
|
+
actor_id: env.CODEMEM_ACTOR_ID?.trim() || null,
|
|
639
|
+
config_path: normalizeIdentityPath(env.CODEMEM_CONFIG),
|
|
640
|
+
runtime_root: normalizeIdentityPath(env.CODEMEM_RUNTIME_ROOT),
|
|
641
|
+
workspace_id: env.CODEMEM_WORKSPACE_ID?.trim() || null,
|
|
642
|
+
home_dir: normalizeIdentityPath(env.HOME || homedir()),
|
|
643
|
+
pack_compression: env.CODEMEM_PACK_COMPRESSION?.trim() || null,
|
|
644
|
+
embedding_disabled: ["1", "true", "yes"].includes(
|
|
645
|
+
String(env.CODEMEM_EMBEDDING_DISABLED || "").toLowerCase(),
|
|
646
|
+
),
|
|
647
|
+
embedding_model: env.CODEMEM_EMBEDDING_MODEL || "Xenova/bge-small-en-v1.5",
|
|
648
|
+
};
|
|
649
|
+
};
|
|
650
|
+
|
|
626
651
|
const parsePackOutput = (result) => {
|
|
627
652
|
const succeeded = result?.exitCode === 0;
|
|
628
653
|
let payload = null;
|
|
@@ -709,8 +734,67 @@ const isViewerContractUnsupportedPayload = (payload) =>
|
|
|
709
734
|
const isViewerInvalidRequestPayload = (payload) =>
|
|
710
735
|
isRecord(payload)
|
|
711
736
|
&& isRecord(payload.error)
|
|
712
|
-
&& payload.error.code === "invalid_request"
|
|
713
|
-
|
|
737
|
+
&& payload.error.code === "invalid_request";
|
|
738
|
+
|
|
739
|
+
// Dependency-free port of @codemem/core prompt-transport semantics. Keep the
|
|
740
|
+
// range and classifier parity pinned by the plugin injection tests.
|
|
741
|
+
const PROMPT_TRANSPORT_PROTOCOL_RANGE = Object.freeze({
|
|
742
|
+
minSupportedProtocolVersion: 1,
|
|
743
|
+
protocolVersion: 1,
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const normalizePromptTransportProtocolRange = (
|
|
747
|
+
protocolVersion,
|
|
748
|
+
minSupportedProtocolVersion = undefined,
|
|
749
|
+
) => {
|
|
750
|
+
if (!Number.isSafeInteger(protocolVersion) || protocolVersion < 1) return null;
|
|
751
|
+
const minimum = minSupportedProtocolVersion === undefined
|
|
752
|
+
? protocolVersion
|
|
753
|
+
: minSupportedProtocolVersion;
|
|
754
|
+
if (!Number.isSafeInteger(minimum) || minimum < 1 || minimum > protocolVersion) return null;
|
|
755
|
+
return { minSupportedProtocolVersion: minimum, protocolVersion };
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
const arePromptTransportProtocolRangesCompatible = (left, right) =>
|
|
759
|
+
left.minSupportedProtocolVersion <= right.protocolVersion
|
|
760
|
+
&& right.minSupportedProtocolVersion <= left.protocolVersion;
|
|
761
|
+
|
|
762
|
+
const classifyPromptTransportFailure = ({ kind, compatibleProfile = false }) => {
|
|
763
|
+
if (kind === "database_mismatch" || kind === "runtime_identity_mismatch") {
|
|
764
|
+
return "local_fallback";
|
|
765
|
+
}
|
|
766
|
+
if (kind === "invalid_request") {
|
|
767
|
+
return compatibleProfile ? "terminal" : "fallback";
|
|
768
|
+
}
|
|
769
|
+
if (kind === "policy_failure" || kind === "authorization_failure") {
|
|
770
|
+
return "terminal";
|
|
771
|
+
}
|
|
772
|
+
if (kind === "viewer_contract_unsupported") {
|
|
773
|
+
return compatibleProfile ? "terminal" : "fallback";
|
|
774
|
+
}
|
|
775
|
+
return "fallback";
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
const isViewerPolicyOrAuthFailurePayload = (payload) => {
|
|
779
|
+
const code = isRecord(payload) && isRecord(payload.error)
|
|
780
|
+
? payload.error.code
|
|
781
|
+
: isRecord(payload)
|
|
782
|
+
? payload.error
|
|
783
|
+
: null;
|
|
784
|
+
return typeof code === "string" && [
|
|
785
|
+
"authorization_failed",
|
|
786
|
+
"forbidden",
|
|
787
|
+
"policy_denied",
|
|
788
|
+
"policy_disabled",
|
|
789
|
+
"unauthorized",
|
|
790
|
+
].includes(code);
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
const viewerFailureClassification = (cause, disposition) => ({
|
|
794
|
+
cause,
|
|
795
|
+
disposition,
|
|
796
|
+
retryable: disposition !== "terminal",
|
|
797
|
+
});
|
|
714
798
|
|
|
715
799
|
const classifyViewerHttpFailure = ({
|
|
716
800
|
operation,
|
|
@@ -718,31 +802,60 @@ const classifyViewerHttpFailure = ({
|
|
|
718
802
|
error = null,
|
|
719
803
|
malformed = false,
|
|
720
804
|
body = null,
|
|
805
|
+
compatibleProfile = false,
|
|
721
806
|
}) => {
|
|
722
807
|
if (malformed) {
|
|
723
|
-
return
|
|
808
|
+
return viewerFailureClassification(
|
|
809
|
+
`${operation} returned malformed success`,
|
|
810
|
+
classifyPromptTransportFailure({ kind: "malformed_response" }),
|
|
811
|
+
);
|
|
724
812
|
}
|
|
725
|
-
if (
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
813
|
+
if (isViewerDbMismatchPayload(body)) {
|
|
814
|
+
return viewerFailureClassification(
|
|
815
|
+
`${operation} viewer database mismatch`,
|
|
816
|
+
classifyPromptTransportFailure({ kind: "database_mismatch" }),
|
|
817
|
+
);
|
|
818
|
+
}
|
|
819
|
+
if (isViewerIdentityMismatchPayload(body)) {
|
|
820
|
+
return viewerFailureClassification(
|
|
821
|
+
`${operation} viewer runtime identity mismatch`,
|
|
822
|
+
classifyPromptTransportFailure({ kind: "runtime_identity_mismatch" }),
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
if (isViewerContractUnsupportedPayload(body)) {
|
|
826
|
+
return viewerFailureClassification(
|
|
827
|
+
`${operation} viewer contract unsupported`,
|
|
828
|
+
classifyPromptTransportFailure({
|
|
829
|
+
kind: "viewer_contract_unsupported",
|
|
830
|
+
compatibleProfile,
|
|
831
|
+
}),
|
|
832
|
+
);
|
|
731
833
|
}
|
|
732
834
|
if (operation === "prompt-pack-ledger" && isValidLedgerFailureHttpPayload(body)) {
|
|
733
|
-
return
|
|
835
|
+
return viewerFailureClassification(`${operation} request rejected (${status})`, "terminal");
|
|
734
836
|
}
|
|
735
|
-
if (
|
|
736
|
-
return
|
|
837
|
+
if (status === 401 || status === 403 || isViewerPolicyOrAuthFailurePayload(body)) {
|
|
838
|
+
return viewerFailureClassification(
|
|
839
|
+
`${operation} policy or authorization failure (${status})`,
|
|
840
|
+
classifyPromptTransportFailure({
|
|
841
|
+
kind: status === 401 || status === 403 ? "authorization_failure" : "policy_failure",
|
|
842
|
+
}),
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
if (isViewerInvalidRequestPayload(body)) {
|
|
846
|
+
return viewerFailureClassification(
|
|
847
|
+
`${operation} request rejected (${status})`,
|
|
848
|
+
classifyPromptTransportFailure({ kind: "invalid_request", compatibleProfile }),
|
|
849
|
+
);
|
|
737
850
|
}
|
|
738
851
|
if (status === 404 || status === 405) {
|
|
739
|
-
return
|
|
852
|
+
return viewerFailureClassification(`${operation} endpoint unavailable (${status})`, "fallback");
|
|
740
853
|
}
|
|
741
854
|
if (Number.isInteger(status) && status >= 500) {
|
|
742
|
-
return
|
|
855
|
+
return viewerFailureClassification(`${operation} server failure (${status})`, "fallback");
|
|
743
856
|
}
|
|
744
857
|
if (Number.isInteger(status)) {
|
|
745
|
-
return
|
|
858
|
+
return viewerFailureClassification(`${operation} unexpected response (${status})`, "fallback");
|
|
746
859
|
}
|
|
747
860
|
const diagnostic = [
|
|
748
861
|
error?.name,
|
|
@@ -752,10 +865,10 @@ const classifyViewerHttpFailure = ({
|
|
|
752
865
|
error,
|
|
753
866
|
].filter(Boolean).join(" ");
|
|
754
867
|
const timedOut = /AbortError|TimeoutError|timeout|timed out|ETIMEDOUT/i.test(diagnostic);
|
|
755
|
-
return
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
868
|
+
return viewerFailureClassification(
|
|
869
|
+
timedOut ? `${operation} request timeout` : `${operation} connection failed`,
|
|
870
|
+
"fallback",
|
|
871
|
+
);
|
|
759
872
|
};
|
|
760
873
|
|
|
761
874
|
const buildPackHttpBody = ({
|
|
@@ -1675,28 +1788,7 @@ export const CodememPlugin = async ({
|
|
|
1675
1788
|
cwd,
|
|
1676
1789
|
expandedViewerDbPath || join(homedir(), ".codemem", "mem.sqlite"),
|
|
1677
1790
|
);
|
|
1678
|
-
const
|
|
1679
|
-
const trimmed = String(value || "").trim();
|
|
1680
|
-
if (!trimmed) return null;
|
|
1681
|
-
const expanded = trimmed.startsWith("~/")
|
|
1682
|
-
? join(process.env.HOME?.trim() || homedir(), trimmed.slice(2))
|
|
1683
|
-
: trimmed;
|
|
1684
|
-
return resolve(cwd, expanded);
|
|
1685
|
-
};
|
|
1686
|
-
const promptPackIdentityTarget = {
|
|
1687
|
-
device_id: process.env.CODEMEM_DEVICE_ID?.trim() || null,
|
|
1688
|
-
actor_id_present: Object.hasOwn(process.env, "CODEMEM_ACTOR_ID"),
|
|
1689
|
-
actor_id: process.env.CODEMEM_ACTOR_ID?.trim() || null,
|
|
1690
|
-
config_path: normalizeIdentityPath(process.env.CODEMEM_CONFIG),
|
|
1691
|
-
runtime_root: normalizeIdentityPath(process.env.CODEMEM_RUNTIME_ROOT),
|
|
1692
|
-
workspace_id: process.env.CODEMEM_WORKSPACE_ID?.trim() || null,
|
|
1693
|
-
home_dir: normalizeIdentityPath(process.env.HOME || homedir()),
|
|
1694
|
-
pack_compression: process.env.CODEMEM_PACK_COMPRESSION?.trim() || null,
|
|
1695
|
-
embedding_disabled: ["1", "true", "yes"].includes(
|
|
1696
|
-
String(process.env.CODEMEM_EMBEDDING_DISABLED || "").toLowerCase(),
|
|
1697
|
-
),
|
|
1698
|
-
embedding_model: process.env.CODEMEM_EMBEDDING_MODEL || "Xenova/bge-small-en-v1.5",
|
|
1699
|
-
};
|
|
1791
|
+
const promptPackIdentityTarget = buildViewerIdentityTarget(process.env, cwd);
|
|
1700
1792
|
const viewerConfigPath = process.env.CODEMEM_CONFIG || "";
|
|
1701
1793
|
// A malformed value (e.g. "abc", "", "-1") falls back to the 20s default
|
|
1702
1794
|
// instead of NaN, which would silently disable the timeout. An explicit "0"
|
|
@@ -1921,10 +2013,27 @@ export const CodememPlugin = async ({
|
|
|
1921
2013
|
const postResp = await fetch(rawEventsUrl, {
|
|
1922
2014
|
method: "POST",
|
|
1923
2015
|
headers: { "Content-Type": "application/json" },
|
|
1924
|
-
body: JSON.stringify(
|
|
2016
|
+
body: JSON.stringify({
|
|
2017
|
+
...body,
|
|
2018
|
+
db_path: promptPackDbPath,
|
|
2019
|
+
identity_target: promptPackIdentityTarget,
|
|
2020
|
+
}),
|
|
1925
2021
|
});
|
|
1926
2022
|
if (!postResp.ok) {
|
|
1927
|
-
|
|
2023
|
+
let responseBody = null;
|
|
2024
|
+
if (postResp.status === 409) {
|
|
2025
|
+
try {
|
|
2026
|
+
responseBody = await postResp.json();
|
|
2027
|
+
} catch {
|
|
2028
|
+
// Generic failure handling below remains the safe fallback.
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
const targetMismatch = isViewerDbMismatchPayload(responseBody)
|
|
2032
|
+
|| isViewerIdentityMismatchPayload(responseBody)
|
|
2033
|
+
|| isViewerContractUnsupportedPayload(responseBody);
|
|
2034
|
+
const postError = new Error(`raw-events post failed (${postResp.status})`);
|
|
2035
|
+
postError.viewerTargetMismatch = targetMismatch;
|
|
2036
|
+
throw postError;
|
|
1928
2037
|
}
|
|
1929
2038
|
streamUnavailableUntil = 0;
|
|
1930
2039
|
streamErrorNoted = false;
|
|
@@ -1947,6 +2056,7 @@ export const CodememPlugin = async ({
|
|
|
1947
2056
|
type,
|
|
1948
2057
|
viewerHost,
|
|
1949
2058
|
viewerPort,
|
|
2059
|
+
viewerTargetMismatch: err?.viewerTargetMismatch === true,
|
|
1950
2060
|
error: String(err),
|
|
1951
2061
|
},
|
|
1952
2062
|
});
|
|
@@ -2386,19 +2496,19 @@ export const CodememPlugin = async ({
|
|
|
2386
2496
|
if (!viewerEnabled) {
|
|
2387
2497
|
return {
|
|
2388
2498
|
ok: false,
|
|
2389
|
-
classification:
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2499
|
+
classification: viewerFailureClassification(
|
|
2500
|
+
`${operation} viewer transport disabled`,
|
|
2501
|
+
"fallback",
|
|
2502
|
+
),
|
|
2393
2503
|
};
|
|
2394
2504
|
}
|
|
2395
2505
|
if (Date.now() < promptPackTransportUnavailableUntil) {
|
|
2396
2506
|
return {
|
|
2397
2507
|
ok: false,
|
|
2398
|
-
classification:
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2508
|
+
classification: viewerFailureClassification(
|
|
2509
|
+
`${operation} viewer transport in backoff`,
|
|
2510
|
+
"fallback",
|
|
2511
|
+
),
|
|
2402
2512
|
};
|
|
2403
2513
|
}
|
|
2404
2514
|
|
|
@@ -2426,20 +2536,49 @@ export const CodememPlugin = async ({
|
|
|
2426
2536
|
}
|
|
2427
2537
|
return { ok: false, classification };
|
|
2428
2538
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2539
|
+
const viewerProtocolRange = isRecord(profileBody)
|
|
2540
|
+
? normalizePromptTransportProtocolRange(
|
|
2541
|
+
profileBody.protocol_version,
|
|
2542
|
+
profileBody.min_supported_protocol_version,
|
|
2543
|
+
)
|
|
2544
|
+
: null;
|
|
2545
|
+
let profileFailure = null;
|
|
2546
|
+
if (!isRecord(profileBody) || profileBody.service !== "codemem-viewer") {
|
|
2547
|
+
profileFailure = viewerFailureClassification(
|
|
2548
|
+
`${operation} viewer profile malformed`,
|
|
2549
|
+
classifyPromptTransportFailure({ kind: "profile_malformed" }),
|
|
2550
|
+
);
|
|
2551
|
+
} else if (!viewerProtocolRange) {
|
|
2552
|
+
profileFailure = viewerFailureClassification(
|
|
2553
|
+
`${operation} viewer protocol range malformed`,
|
|
2554
|
+
classifyPromptTransportFailure({ kind: "profile_malformed" }),
|
|
2555
|
+
);
|
|
2556
|
+
} else if (!arePromptTransportProtocolRangesCompatible(
|
|
2557
|
+
PROMPT_TRANSPORT_PROTOCOL_RANGE,
|
|
2558
|
+
viewerProtocolRange,
|
|
2559
|
+
)) {
|
|
2560
|
+
profileFailure = viewerFailureClassification(
|
|
2561
|
+
`${operation} viewer protocol range unsupported`,
|
|
2562
|
+
classifyPromptTransportFailure({ kind: "protocol_range_mismatch" }),
|
|
2563
|
+
);
|
|
2564
|
+
} else if (profileBody.db_path !== promptPackDbPath) {
|
|
2565
|
+
profileFailure = viewerFailureClassification(
|
|
2566
|
+
`${operation} viewer database mismatch`,
|
|
2567
|
+
classifyPromptTransportFailure({ kind: "database_mismatch" }),
|
|
2568
|
+
);
|
|
2569
|
+
} else if (
|
|
2570
|
+
canonicalJson(profileBody.identity_target) !== canonicalJson(promptPackIdentityTarget)
|
|
2435
2571
|
) {
|
|
2572
|
+
profileFailure = viewerFailureClassification(
|
|
2573
|
+
`${operation} viewer runtime identity mismatch`,
|
|
2574
|
+
classifyPromptTransportFailure({ kind: "runtime_identity_mismatch" }),
|
|
2575
|
+
);
|
|
2576
|
+
}
|
|
2577
|
+
if (profileFailure) {
|
|
2436
2578
|
promptPackTransportUnavailableUntil = Date.now() + Math.max(1000, rawEventsBackoffMs);
|
|
2437
2579
|
return {
|
|
2438
2580
|
ok: false,
|
|
2439
|
-
classification:
|
|
2440
|
-
cause: `${operation} viewer profile handshake mismatch`,
|
|
2441
|
-
retryable: true,
|
|
2442
|
-
},
|
|
2581
|
+
classification: profileFailure,
|
|
2443
2582
|
};
|
|
2444
2583
|
}
|
|
2445
2584
|
response = await fetch(url, {
|
|
@@ -2467,6 +2606,7 @@ export const CodememPlugin = async ({
|
|
|
2467
2606
|
operation,
|
|
2468
2607
|
status: response.status,
|
|
2469
2608
|
body,
|
|
2609
|
+
compatibleProfile: true,
|
|
2470
2610
|
});
|
|
2471
2611
|
if (classification.retryable) {
|
|
2472
2612
|
promptPackTransportUnavailableUntil =
|
|
@@ -3894,12 +4034,17 @@ export const __testUtils = {
|
|
|
3894
4034
|
redactPackCommand,
|
|
3895
4035
|
rejectsInternalLedgerFlag,
|
|
3896
4036
|
classifyFallbackCommandResult,
|
|
4037
|
+
PROMPT_TRANSPORT_PROTOCOL_RANGE,
|
|
4038
|
+
normalizePromptTransportProtocolRange,
|
|
4039
|
+
arePromptTransportProtocolRangesCompatible,
|
|
4040
|
+
classifyPromptTransportFailure,
|
|
3897
4041
|
classifyViewerHttpFailure,
|
|
3898
4042
|
isValidPackHttpPayload,
|
|
3899
4043
|
isValidLedgerHttpPayload,
|
|
3900
4044
|
buildPackHttpBody,
|
|
3901
4045
|
parsePackText,
|
|
3902
4046
|
parsePackMetrics,
|
|
4047
|
+
buildViewerIdentityTarget,
|
|
3903
4048
|
resolveInjectSurface,
|
|
3904
4049
|
applyInjectedContextToOutput,
|
|
3905
4050
|
applyInjectedContextToMessages,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codemem/opencode-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"description": "CodeMem plugin for OpenCode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
".opencode/lib/"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@opencode-ai/plugin": "1.18.
|
|
21
|
+
"@opencode-ai/plugin": "1.18.21"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|