@memorax/memorax-code 0.1.10 → 0.1.12
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 +7 -4
- package/bin/memorax-code-setup.mjs +136 -34
- package/bin/memorax-code-trae.mjs +4 -0
- package/docs/configuration.md +111 -54
- package/docs/troubleshooting.md +50 -8
- package/lib/memorax-code-backend/dist/clients/claude/transcript-turn.js +2 -2
- package/lib/memorax-code-backend/dist/clients/codex/rollout-turn.js +1 -1
- package/lib/memorax-code-backend/dist/clients/trae/lifecycle.js +53 -0
- package/lib/memorax-code-backend/dist/clients/trae/memory-hook-runtime.js +302 -0
- package/lib/memorax-code-backend/dist/clients/trae/turn-id.js +16 -0
- package/lib/memorax-code-backend/dist/config/memorax-code.js +14 -1
- package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +22 -4
- package/lib/memorax-code-backend/dist/lifecycle/active-clients.js +3 -0
- package/lib/memorax-code-backend/dist/lifecycle/client-plugin-removal.js +14 -2
- package/lib/memorax-code-backend/dist/lifecycle/client-selection.js +5 -3
- package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +62 -9
- package/lib/memorax-code-backend/dist/memory/hook-command.js +34 -2
- package/lib/memorax-code-backend/dist/memory/reminder-trace-recorder.js +5 -1
- package/lib/memorax-code-backend/dist/memory/service.js +12 -0
- package/lib/memorax-code-backend/dist/trace/config.js +12 -1
- package/lib/memorax-code-backend/dist/trace/context.js +26 -1
- package/lib/memorax-code-backend/package.json +1 -1
- package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +3 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +16 -3
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +15 -2
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +3 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +16 -3
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +15 -2
- package/lib/memorax-code-codebuddy-adapter/.codebuddy-plugin/plugin.json +1 -1
- package/lib/memorax-code-codebuddy-adapter/hooks/runtime-hook.mjs +11 -1
- package/lib/memorax-code-codebuddy-adapter/package.json +1 -1
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/SKILL.md +3 -1
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-add.md +16 -3
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-search.md +15 -2
- package/lib/memorax-code-codebuddy-adapter/src/config.mjs +86 -23
- package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
- package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-codex-adapter/package.json +1 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +3 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +16 -3
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +15 -2
- package/lib/memorax-code-dsh-adapter/package.json +1 -1
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/SKILL.md +3 -1
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +16 -3
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +15 -2
- package/lib/memorax-code-opencode-adapter/package.json +1 -1
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/SKILL.md +3 -1
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +16 -3
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +15 -2
- package/lib/memorax-code-trae-adapter/hooks/runtime-hook.mjs +340 -0
- package/lib/memorax-code-trae-adapter/package.json +9 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/SKILL.md +87 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/agents/claude.yaml +10 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/agents/openai.yaml +7 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/defaults.json +12 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/references/memorax-add.md +101 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/references/memorax-search.md +106 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/references/personal-read.md +46 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/references/personal-write.md +120 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/references/repo-build.md +319 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/references/repo-read.md +103 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/references/repo-templates.md +390 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/references/repo-update.md +127 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/scripts/collect_all.py +579 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/scripts/detect_updates.py +919 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/scripts/github_resource_facets.py +512 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +517 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
- package/lib/memorax-code-trae-adapter/skills/memorax-code/scripts/validate_memory.py +248 -0
- package/lib/memorax-code-trae-adapter/src/adapter-paths.mjs +69 -0
- package/lib/memorax-code-trae-adapter/src/cli.mjs +59 -0
- package/lib/memorax-code-trae-adapter/src/config.mjs +630 -0
- package/lib/memorax-code-trae-adapter/src/runtime-observation.mjs +55 -0
- package/lib/resolve-codebuddy-command.mjs +15 -4
- package/lib/run-entrypoint.mjs +6 -0
- package/package.json +5 -3
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
cpSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
lstatSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
rmSync,
|
|
11
|
+
statSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from "node:fs";
|
|
14
|
+
import { dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import {
|
|
17
|
+
atomicWriteJson,
|
|
18
|
+
readAdapterState,
|
|
19
|
+
readJsonFile,
|
|
20
|
+
stringOption,
|
|
21
|
+
withJsonFileLock,
|
|
22
|
+
withJsonFileLockAsync,
|
|
23
|
+
} from "../../memorax-code-adapter-common/src/config-utils.mjs";
|
|
24
|
+
import {
|
|
25
|
+
defaultMemoraxCodeHome,
|
|
26
|
+
defaultTraeHome,
|
|
27
|
+
traeAdapterRoot,
|
|
28
|
+
traeAdapterStatePath,
|
|
29
|
+
traeHooksPath,
|
|
30
|
+
traeRuntimeRoot,
|
|
31
|
+
traeSkillPath,
|
|
32
|
+
} from "./adapter-paths.mjs";
|
|
33
|
+
import {
|
|
34
|
+
readTraeRuntimeObservation,
|
|
35
|
+
traeRuntimeObservationPath,
|
|
36
|
+
} from "./runtime-observation.mjs";
|
|
37
|
+
|
|
38
|
+
const ADAPTER_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
39
|
+
const STATE_VERSION = 1;
|
|
40
|
+
const HOOK_MARKER = "--memorax-code-trae-hook-v1";
|
|
41
|
+
const REQUIRED_EVENTS = ["SessionStart", "UserPromptSubmit", "Stop"];
|
|
42
|
+
|
|
43
|
+
export async function enableTraeAdapter(options = {}) {
|
|
44
|
+
const paths = resolvePaths(options);
|
|
45
|
+
return await withTraeLifecycleLock(paths, () => enableTraeAdapterUnlocked(paths, options));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function enableTraeAdapterUnlocked(paths, options) {
|
|
49
|
+
const previousState = readAdapterState(paths.statePath);
|
|
50
|
+
const stateProblem = validateState(previousState, paths);
|
|
51
|
+
if (stateProblem) return { ...stateProblem, action: "enable" };
|
|
52
|
+
const sourceProblem = validateSources(paths);
|
|
53
|
+
if (sourceProblem) return { ...sourceProblem, action: "enable" };
|
|
54
|
+
if (existsSync(paths.skillPath) && previousState?.skillPath !== paths.skillPath) {
|
|
55
|
+
return conflict("skill_conflict", paths, paths.skillPath);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let hookManifest;
|
|
59
|
+
try {
|
|
60
|
+
hookManifest = readHookManifest(paths.hooksPath);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return failure("hooks_invalid", paths, error);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const runtimeDigest = runtimeSourceDigest(paths);
|
|
66
|
+
const generationPath = join(paths.runtimeRoot, runtimeDigest);
|
|
67
|
+
const runtimePath = join(generationPath, "hooks", "runtime-hook.mjs");
|
|
68
|
+
const hookCommand = traeHookCommand(
|
|
69
|
+
runtimePath,
|
|
70
|
+
options.platform ?? process.platform,
|
|
71
|
+
absoluteRegularFile(options.nodePath) ?? process.execPath,
|
|
72
|
+
options.powershellPath ?? defaultWindowsPowerShellPath(),
|
|
73
|
+
);
|
|
74
|
+
const skillDigest = directoryDigest(paths.skillSourcePath);
|
|
75
|
+
const current = previousState?.runtimeDigest === runtimeDigest
|
|
76
|
+
&& previousState?.skillDigest === skillDigest
|
|
77
|
+
&& previousState?.enabled === true
|
|
78
|
+
&& existsSync(runtimePath)
|
|
79
|
+
&& directoryDigestIfPresent(paths.skillPath) === skillDigest
|
|
80
|
+
&& hooksConfigured(hookManifest, hookCommand);
|
|
81
|
+
const now = new Date().toISOString();
|
|
82
|
+
const state = {
|
|
83
|
+
version: STATE_VERSION,
|
|
84
|
+
runtime: "trae",
|
|
85
|
+
integration: "hooks",
|
|
86
|
+
enabled: true,
|
|
87
|
+
traeHome: paths.traeHome,
|
|
88
|
+
hooksPath: paths.hooksPath,
|
|
89
|
+
skillPath: paths.skillPath,
|
|
90
|
+
skillDigest,
|
|
91
|
+
runtimeRoot: paths.runtimeRoot,
|
|
92
|
+
runtimePath,
|
|
93
|
+
runtimeDigest,
|
|
94
|
+
hookCommand,
|
|
95
|
+
installedAt: stringOption(previousState?.installedAt) ?? now,
|
|
96
|
+
updatedAt: now,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
atomicWriteJson(paths.statePath, { ...state, enabled: false, installPending: true });
|
|
101
|
+
materializeRuntimeGeneration(paths, generationPath, runtimeDigest, options);
|
|
102
|
+
if (directoryDigestIfPresent(paths.skillPath) !== skillDigest) {
|
|
103
|
+
materializeDirectory(paths.skillSourcePath, paths.skillPath);
|
|
104
|
+
}
|
|
105
|
+
updateManagedHooks(paths.hooksPath, hookCommand, true);
|
|
106
|
+
atomicWriteJson(paths.statePath, state);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
return failure("install_failed", paths, error);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return await readTraeAdapterStatusUnlocked(paths, { ...options, changed: !current });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function disableTraeAdapter(options = {}) {
|
|
115
|
+
const paths = resolvePaths(options);
|
|
116
|
+
return await withTraeLifecycleLock(paths, () => disableTraeAdapterUnlocked(paths));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function disableTraeAdapterUnlocked(paths) {
|
|
120
|
+
const state = readAdapterState(paths.statePath);
|
|
121
|
+
const stateProblem = validateState(state, paths);
|
|
122
|
+
if (stateProblem) return { ...stateProblem, action: "disable" };
|
|
123
|
+
if (!state) {
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
action: "disable",
|
|
127
|
+
runtime: "trae",
|
|
128
|
+
integration: "hooks",
|
|
129
|
+
installed: false,
|
|
130
|
+
enabled: false,
|
|
131
|
+
managed: false,
|
|
132
|
+
skipped: true,
|
|
133
|
+
reason: "not_managed",
|
|
134
|
+
traeHome: paths.traeHome,
|
|
135
|
+
statePath: paths.statePath,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
updateManagedHooks(paths.hooksPath, undefined, false);
|
|
140
|
+
const disabledState = {
|
|
141
|
+
...state,
|
|
142
|
+
enabled: false,
|
|
143
|
+
disabledAt: new Date().toISOString(),
|
|
144
|
+
};
|
|
145
|
+
delete disabledState.installPending;
|
|
146
|
+
atomicWriteJson(paths.statePath, disabledState);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
return failure("disable_failed", paths, error);
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
ok: true,
|
|
152
|
+
action: "disable",
|
|
153
|
+
runtime: "trae",
|
|
154
|
+
integration: "hooks",
|
|
155
|
+
installed: existsSync(state.runtimePath) && existsSync(join(state.skillPath, "SKILL.md")),
|
|
156
|
+
enabled: false,
|
|
157
|
+
managed: true,
|
|
158
|
+
changed: state.enabled === true,
|
|
159
|
+
traeHome: paths.traeHome,
|
|
160
|
+
statePath: paths.statePath,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export async function readTraeAdapterStatus(options = {}) {
|
|
165
|
+
const paths = resolvePaths(options);
|
|
166
|
+
return await readTraeAdapterStatusUnlocked(paths, options);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function readTraeAdapterStatusUnlocked(paths, options) {
|
|
170
|
+
const state = readAdapterState(paths.statePath);
|
|
171
|
+
const stateProblem = validateState(state, paths);
|
|
172
|
+
if (stateProblem) return { ...stateProblem, action: "status" };
|
|
173
|
+
if (!state) {
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
action: "status",
|
|
177
|
+
runtime: "trae",
|
|
178
|
+
integration: "hooks",
|
|
179
|
+
installed: false,
|
|
180
|
+
enabled: false,
|
|
181
|
+
managed: false,
|
|
182
|
+
skipped: true,
|
|
183
|
+
reason: "not_managed",
|
|
184
|
+
traeHome: paths.traeHome,
|
|
185
|
+
statePath: paths.statePath,
|
|
186
|
+
traeHooks: { ok: false, configured: false, runtimeObserved: false, status: "missing" },
|
|
187
|
+
traeSkills: skillSummary(paths.skillPath, false),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
let manifest;
|
|
191
|
+
try {
|
|
192
|
+
manifest = readHookManifest(paths.hooksPath);
|
|
193
|
+
} catch {
|
|
194
|
+
manifest = undefined;
|
|
195
|
+
}
|
|
196
|
+
const runtimeCurrent = existsSync(state.runtimePath)
|
|
197
|
+
&& state.runtimePath === join(state.runtimeRoot, state.runtimeDigest, "hooks", "runtime-hook.mjs");
|
|
198
|
+
const skillCurrent = existsSync(join(state.skillPath, "SKILL.md"))
|
|
199
|
+
&& directoryDigestIfPresent(state.skillPath) === state.skillDigest;
|
|
200
|
+
const configured = Boolean(manifest && hooksConfigured(manifest, state.hookCommand));
|
|
201
|
+
const observation = await readTraeRuntimeObservation(paths.memoraxCodeHome);
|
|
202
|
+
const runtimeObserved = observation?.runtimeDigest === state.runtimeDigest
|
|
203
|
+
&& comparablePath(observation.traeHome, options.platform ?? process.platform)
|
|
204
|
+
=== comparablePath(paths.traeHome, options.platform ?? process.platform);
|
|
205
|
+
const installed = runtimeCurrent && skillCurrent;
|
|
206
|
+
const enabled = state.enabled === true && installed && configured;
|
|
207
|
+
const installPending = state.installPending === true;
|
|
208
|
+
return {
|
|
209
|
+
ok: true,
|
|
210
|
+
action: "status",
|
|
211
|
+
runtime: "trae",
|
|
212
|
+
integration: "hooks",
|
|
213
|
+
installed,
|
|
214
|
+
enabled,
|
|
215
|
+
managed: true,
|
|
216
|
+
current: installed && configured,
|
|
217
|
+
changed: options.changed,
|
|
218
|
+
traeHome: paths.traeHome,
|
|
219
|
+
statePath: paths.statePath,
|
|
220
|
+
installPath: dirname(state.runtimePath),
|
|
221
|
+
skillPath: state.skillPath,
|
|
222
|
+
traeHooks: {
|
|
223
|
+
ok: configured,
|
|
224
|
+
configured,
|
|
225
|
+
runtimeObserved,
|
|
226
|
+
status: configured ? (runtimeObserved ? "observed" : "unverified") : "invalid",
|
|
227
|
+
observationPath: traeRuntimeObservationPath(paths.memoraxCodeHome),
|
|
228
|
+
},
|
|
229
|
+
traeSkills: skillSummary(state.skillPath, skillCurrent),
|
|
230
|
+
globalHooksActivationRequired: configured && !runtimeObserved,
|
|
231
|
+
...(!enabled ? {
|
|
232
|
+
reason: installPending
|
|
233
|
+
? "install_incomplete"
|
|
234
|
+
: !installed ? "artifacts_missing" : "hooks_not_configured",
|
|
235
|
+
} : {}),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function removeTraeAdapterInstallation(options = {}) {
|
|
240
|
+
const paths = resolvePaths(options);
|
|
241
|
+
return await withTraeLifecycleLock(paths, () => removeTraeAdapterInstallationUnlocked(paths));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function removeTraeAdapterInstallationUnlocked(paths) {
|
|
245
|
+
const state = readAdapterState(paths.statePath);
|
|
246
|
+
const stateProblem = validateState(state, paths);
|
|
247
|
+
if (stateProblem) return { ...stateProblem, action: "trae-adapter-remove" };
|
|
248
|
+
if (!state) {
|
|
249
|
+
return {
|
|
250
|
+
ok: true,
|
|
251
|
+
action: "trae-adapter-remove",
|
|
252
|
+
skipped: true,
|
|
253
|
+
reason: "not_managed",
|
|
254
|
+
removed: false,
|
|
255
|
+
statePath: paths.statePath,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const disabled = disableTraeAdapterUnlocked(paths);
|
|
259
|
+
if (disabled.ok === false) return { ...disabled, action: "trae-adapter-remove" };
|
|
260
|
+
try {
|
|
261
|
+
rmSync(state.skillPath, { recursive: true, force: true });
|
|
262
|
+
rmSync(traeAdapterRoot(paths.memoraxCodeHome), { recursive: true, force: true });
|
|
263
|
+
} catch (error) {
|
|
264
|
+
return failure("remove_failed", paths, error, "trae-adapter-remove");
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
ok: true,
|
|
268
|
+
action: "trae-adapter-remove",
|
|
269
|
+
runtime: "trae",
|
|
270
|
+
integration: "hooks",
|
|
271
|
+
installed: false,
|
|
272
|
+
enabled: false,
|
|
273
|
+
managed: false,
|
|
274
|
+
removed: true,
|
|
275
|
+
traeHome: paths.traeHome,
|
|
276
|
+
statePath: paths.statePath,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function traeHookCommand(
|
|
281
|
+
runtimePath,
|
|
282
|
+
platform = process.platform,
|
|
283
|
+
nodePath = process.execPath,
|
|
284
|
+
powershellPath = defaultWindowsPowerShellPath(),
|
|
285
|
+
) {
|
|
286
|
+
if (platform !== "win32") {
|
|
287
|
+
return `${posixShellLiteral(nodePath)} ${posixShellLiteral(runtimePath)} ${HOOK_MARKER}`;
|
|
288
|
+
}
|
|
289
|
+
const script = [
|
|
290
|
+
"$ErrorActionPreference='Stop'",
|
|
291
|
+
"$utf8=[Text.UTF8Encoding]::new($false)",
|
|
292
|
+
"[Console]::InputEncoding=$utf8",
|
|
293
|
+
"[Console]::OutputEncoding=$utf8",
|
|
294
|
+
"$payload=[Console]::In.ReadToEnd()",
|
|
295
|
+
"$start=[Diagnostics.ProcessStartInfo]::new()",
|
|
296
|
+
`$start.FileName=${powershellLiteral(nodePath)}`,
|
|
297
|
+
`$start.Arguments=${powershellLiteral(`"${runtimePath}" "${HOOK_MARKER}"`)}`,
|
|
298
|
+
"$start.UseShellExecute=$false",
|
|
299
|
+
"$start.CreateNoWindow=$true",
|
|
300
|
+
"$start.RedirectStandardInput=$true",
|
|
301
|
+
"$start.RedirectStandardOutput=$true",
|
|
302
|
+
"$start.RedirectStandardError=$true",
|
|
303
|
+
"$start.StandardOutputEncoding=$utf8",
|
|
304
|
+
"$start.StandardErrorEncoding=$utf8",
|
|
305
|
+
"$process=[Diagnostics.Process]::new()",
|
|
306
|
+
"$process.StartInfo=$start",
|
|
307
|
+
"[void]$process.Start()",
|
|
308
|
+
"$stdout=$process.StandardOutput.ReadToEndAsync()",
|
|
309
|
+
"$stderr=$process.StandardError.ReadToEndAsync()",
|
|
310
|
+
"$process.StandardInput.Write($payload)",
|
|
311
|
+
"$process.StandardInput.Close()",
|
|
312
|
+
"$process.WaitForExit()",
|
|
313
|
+
"[Console]::Out.Write($stdout.GetAwaiter().GetResult())",
|
|
314
|
+
"[Console]::Error.Write($stderr.GetAwaiter().GetResult())",
|
|
315
|
+
"exit $process.ExitCode",
|
|
316
|
+
].join(";");
|
|
317
|
+
const encoded = Buffer.from(script, "utf16le").toString("base64");
|
|
318
|
+
return `${windowsExecutableToken(powershellPath)} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${encoded}`;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
export function defaultWindowsPowerShellPath(env = process.env) {
|
|
322
|
+
const systemRoot = stringOption(env.SystemRoot)
|
|
323
|
+
?? stringOption(env.SYSTEMROOT)
|
|
324
|
+
?? stringOption(env.WINDIR)
|
|
325
|
+
?? "C:\\Windows";
|
|
326
|
+
return win32.join(systemRoot, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export function defaultTraeSkillSourcePath(adapterRoot = ADAPTER_ROOT) {
|
|
330
|
+
const packaged = join(adapterRoot, "skills", "memorax-code");
|
|
331
|
+
return existsSync(join(packaged, "SKILL.md"))
|
|
332
|
+
? packaged
|
|
333
|
+
: resolve(adapterRoot, "..", "memorax-code-codex-adapter", "skills", "memorax-code");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function defaultMemoraxCodeCommand(adapterRoot = ADAPTER_ROOT) {
|
|
337
|
+
const packageRoot = resolve(adapterRoot, "..", "..");
|
|
338
|
+
return [
|
|
339
|
+
join(packageRoot, "bin", "memorax-code.mjs"),
|
|
340
|
+
join(packageRoot, "npm", "memorax-code", "bin", "memorax-code.mjs"),
|
|
341
|
+
].find((path) => existsSync(path));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function resolvePaths(options) {
|
|
345
|
+
const memoraxCodeHome = resolve(options.memoraxCodeHome ?? defaultMemoraxCodeHome());
|
|
346
|
+
const traeHome = resolve(options.traeHome ?? defaultTraeHome());
|
|
347
|
+
return {
|
|
348
|
+
memoraxCodeHome,
|
|
349
|
+
traeHome,
|
|
350
|
+
lifecycleLockTarget: resolve(
|
|
351
|
+
options.lifecycleLockTarget ?? join(memoraxCodeHome, "adapters", "trae-lifecycle"),
|
|
352
|
+
),
|
|
353
|
+
statePath: resolve(options.statePath ?? traeAdapterStatePath(memoraxCodeHome)),
|
|
354
|
+
hooksPath: resolve(options.hooksPath ?? traeHooksPath(traeHome)),
|
|
355
|
+
skillPath: resolve(options.skillPath ?? traeSkillPath(traeHome)),
|
|
356
|
+
runtimeRoot: resolve(options.runtimeRoot ?? traeRuntimeRoot(memoraxCodeHome)),
|
|
357
|
+
runtimeHookSourcePath: resolve(options.runtimeHookSourcePath ?? join(ADAPTER_ROOT, "hooks", "runtime-hook.mjs")),
|
|
358
|
+
runtimeObservationSourcePath: resolve(options.runtimeObservationSourcePath ?? join(ADAPTER_ROOT, "src", "runtime-observation.mjs")),
|
|
359
|
+
commonSourcePath: resolve(options.commonSourcePath ?? join(ADAPTER_ROOT, "..", "memorax-code-adapter-common", "src")),
|
|
360
|
+
skillSourcePath: resolve(options.skillSourcePath ?? defaultTraeSkillSourcePath()),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function withTraeLifecycleLock(paths, operation) {
|
|
365
|
+
return withJsonFileLockAsync(paths.lifecycleLockTarget, operation);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function validateState(state, paths) {
|
|
369
|
+
if (!state) return undefined;
|
|
370
|
+
if (state.unreadable === true) return { ok: false, reason: "state_invalid", statePath: paths.statePath };
|
|
371
|
+
if (state.version !== STATE_VERSION || state.runtime !== "trae" || state.integration !== "hooks") {
|
|
372
|
+
return { ok: false, reason: "state_invalid", statePath: paths.statePath };
|
|
373
|
+
}
|
|
374
|
+
const expected = {
|
|
375
|
+
traeHome: paths.traeHome,
|
|
376
|
+
hooksPath: paths.hooksPath,
|
|
377
|
+
skillPath: paths.skillPath,
|
|
378
|
+
runtimeRoot: paths.runtimeRoot,
|
|
379
|
+
};
|
|
380
|
+
if (Object.entries(expected).some(([key, value]) => state[key] !== value)
|
|
381
|
+
|| !containedPath(paths.memoraxCodeHome, state.runtimeRoot)
|
|
382
|
+
|| !containedPath(state.runtimeRoot, state.runtimePath)
|
|
383
|
+
|| !/^[a-f0-9]{64}$/.test(String(state.runtimeDigest ?? ""))
|
|
384
|
+
|| !/^[a-f0-9]{64}$/.test(String(state.skillDigest ?? ""))
|
|
385
|
+
|| typeof state.hookCommand !== "string"
|
|
386
|
+
|| !managedHookCommand(state.hookCommand)) {
|
|
387
|
+
return { ok: false, reason: "state_paths_invalid", statePath: paths.statePath };
|
|
388
|
+
}
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function validateSources(paths) {
|
|
393
|
+
for (const [name, path] of [
|
|
394
|
+
["runtime_hook", paths.runtimeHookSourcePath],
|
|
395
|
+
["runtime_observation", paths.runtimeObservationSourcePath],
|
|
396
|
+
]) {
|
|
397
|
+
if (!regularFile(path)) return { ok: false, reason: `${name}_missing`, sourcePath: path };
|
|
398
|
+
}
|
|
399
|
+
for (const [name, path] of [["common_runtime", paths.commonSourcePath], ["skill", paths.skillSourcePath]]) {
|
|
400
|
+
if (!regularDirectory(path)) return { ok: false, reason: `${name}_missing`, sourcePath: path };
|
|
401
|
+
}
|
|
402
|
+
if (!regularFile(join(paths.skillSourcePath, "SKILL.md"))) {
|
|
403
|
+
return { ok: false, reason: "skill_missing", sourcePath: paths.skillSourcePath };
|
|
404
|
+
}
|
|
405
|
+
return undefined;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function materializeRuntimeGeneration(paths, generationPath, runtimeDigest, options) {
|
|
409
|
+
if (existsSync(generationPath)) {
|
|
410
|
+
const record = readJsonFile(join(generationPath, "generation.json"));
|
|
411
|
+
if (record?.unreadable || record?.value?.runtimeDigest !== runtimeDigest
|
|
412
|
+
|| !regularFile(join(generationPath, "hooks", "runtime-hook.mjs"))) {
|
|
413
|
+
throw new Error("Trae runtime generation is invalid");
|
|
414
|
+
}
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
mkdirSync(paths.runtimeRoot, { recursive: true, mode: 0o700 });
|
|
418
|
+
const temporaryPath = join(paths.runtimeRoot, `.staging-${process.pid}-${randomUUID()}`);
|
|
419
|
+
try {
|
|
420
|
+
mkdirSync(join(temporaryPath, "hooks"), { recursive: true, mode: 0o700 });
|
|
421
|
+
mkdirSync(join(temporaryPath, "src"), { recursive: true, mode: 0o700 });
|
|
422
|
+
cpSync(paths.runtimeHookSourcePath, join(temporaryPath, "hooks", "runtime-hook.mjs"));
|
|
423
|
+
cpSync(paths.runtimeObservationSourcePath, join(temporaryPath, "src", "runtime-observation.mjs"));
|
|
424
|
+
cpSync(paths.commonSourcePath, join(temporaryPath, "memorax-code-adapter-common", "src"), { recursive: true });
|
|
425
|
+
atomicWriteJson(join(temporaryPath, "generation.json"), { version: 1, runtimeDigest });
|
|
426
|
+
const memoraxCodeCommand = stringOption(options.memoraxCodeCommand) ?? defaultMemoraxCodeCommand();
|
|
427
|
+
const npmExecPath = absoluteRegularFile(options.npmExecPath ?? process.env.npm_execpath);
|
|
428
|
+
atomicWriteJson(join(temporaryPath, ".memorax-code-package.json"), {
|
|
429
|
+
version: 1,
|
|
430
|
+
...(memoraxCodeCommand ? { memoraxCodeCommand } : {}),
|
|
431
|
+
...(npmExecPath ? { npmExecPath } : {}),
|
|
432
|
+
memoraxCodeHome: paths.memoraxCodeHome,
|
|
433
|
+
traeHome: paths.traeHome,
|
|
434
|
+
runtimeDigest,
|
|
435
|
+
});
|
|
436
|
+
renameSync(temporaryPath, generationPath);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
rmSync(temporaryPath, { recursive: true, force: true });
|
|
439
|
+
if (!existsSync(generationPath)) throw error;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function updateManagedHooks(path, command, enabled) {
|
|
444
|
+
withJsonFileLock(path, () => {
|
|
445
|
+
const manifest = readHookManifest(path);
|
|
446
|
+
for (const event of REQUIRED_EVENTS) {
|
|
447
|
+
const existing = Array.isArray(manifest.hooks[event]) ? manifest.hooks[event] : [];
|
|
448
|
+
const filtered = existing.flatMap((group) => filterManagedGroup(group));
|
|
449
|
+
if (enabled) {
|
|
450
|
+
filtered.push({ hooks: [{ type: "command", command, timeout: event === "SessionStart" ? 35 : 15 }] });
|
|
451
|
+
}
|
|
452
|
+
if (filtered.length > 0) manifest.hooks[event] = filtered;
|
|
453
|
+
else delete manifest.hooks[event];
|
|
454
|
+
}
|
|
455
|
+
atomicWriteJson(path, manifest);
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function filterManagedGroup(group) {
|
|
460
|
+
if (!isRecord(group) || !Array.isArray(group.hooks)) return [group];
|
|
461
|
+
const hooks = group.hooks.filter((hook) => !isManagedHook(hook));
|
|
462
|
+
return hooks.length > 0 ? [{ ...group, hooks }] : [];
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function readHookManifest(path) {
|
|
466
|
+
if (!existsSync(path)) return { hooks: {} };
|
|
467
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
468
|
+
if (!isRecord(parsed) || (parsed.hooks !== undefined && !isRecord(parsed.hooks))) {
|
|
469
|
+
throw new Error(`invalid Trae Hook manifest: ${path}`);
|
|
470
|
+
}
|
|
471
|
+
const manifest = { ...parsed, hooks: { ...(parsed.hooks ?? {}) } };
|
|
472
|
+
for (const event of REQUIRED_EVENTS) {
|
|
473
|
+
if (manifest.hooks[event] !== undefined && !Array.isArray(manifest.hooks[event])) {
|
|
474
|
+
throw new Error(`invalid Trae Hook event: ${event}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return manifest;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function hooksConfigured(manifest, expectedCommand) {
|
|
481
|
+
return REQUIRED_EVENTS.every((event) => {
|
|
482
|
+
const managed = (manifest.hooks[event] ?? [])
|
|
483
|
+
.flatMap((group) => Array.isArray(group?.hooks) ? group.hooks : [])
|
|
484
|
+
.filter(isManagedHook);
|
|
485
|
+
return managed.length === 1 && managed[0].type === "command" && managed[0].command === expectedCommand;
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function isManagedHook(hook) {
|
|
490
|
+
return isRecord(hook) && hook.type === "command"
|
|
491
|
+
&& typeof hook.command === "string" && managedHookCommand(hook.command);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function managedHookCommand(command) {
|
|
495
|
+
if (command.includes(HOOK_MARKER)) return true;
|
|
496
|
+
const encoded = /(?:^|\s)-EncodedCommand\s+([A-Za-z0-9+/]+={0,2})\s*$/.exec(command)?.[1];
|
|
497
|
+
if (!encoded || encoded.length % 4 !== 0) return false;
|
|
498
|
+
try {
|
|
499
|
+
return Buffer.from(encoded, "base64").toString("utf16le").includes(HOOK_MARKER);
|
|
500
|
+
} catch {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function powershellLiteral(value) {
|
|
506
|
+
return `'${String(value).replaceAll("'", "''")}'`;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function posixShellLiteral(value) {
|
|
510
|
+
return `'${String(value).replaceAll("'", "'\\''")}'`;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function windowsExecutableToken(path) {
|
|
514
|
+
const normalized = String(path).replaceAll("\\", "/");
|
|
515
|
+
return /^[A-Za-z]:\/[^\s"]+$/.test(normalized) ? normalized : "powershell.exe";
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function materializeDirectory(source, destination) {
|
|
519
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
520
|
+
const temporaryPath = `${destination}.tmp-${process.pid}-${randomUUID()}`;
|
|
521
|
+
rmSync(temporaryPath, { recursive: true, force: true });
|
|
522
|
+
try {
|
|
523
|
+
cpSync(source, temporaryPath, { recursive: true });
|
|
524
|
+
rmSync(destination, { recursive: true, force: true });
|
|
525
|
+
renameSync(temporaryPath, destination);
|
|
526
|
+
} catch (error) {
|
|
527
|
+
rmSync(temporaryPath, { recursive: true, force: true });
|
|
528
|
+
throw error;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function runtimeSourceDigest(paths) {
|
|
533
|
+
const hash = createHash("sha256");
|
|
534
|
+
hashFile(hash, paths.runtimeHookSourcePath, "hooks/runtime-hook.mjs");
|
|
535
|
+
hashFile(hash, paths.runtimeObservationSourcePath, "src/runtime-observation.mjs");
|
|
536
|
+
hashDirectory(hash, paths.commonSourcePath, "memorax-code-adapter-common/src");
|
|
537
|
+
return hash.digest("hex");
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function directoryDigest(path) {
|
|
541
|
+
const hash = createHash("sha256");
|
|
542
|
+
hashDirectory(hash, path, "");
|
|
543
|
+
return hash.digest("hex");
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function directoryDigestIfPresent(path) {
|
|
547
|
+
try {
|
|
548
|
+
return directoryDigest(path);
|
|
549
|
+
} catch {
|
|
550
|
+
return undefined;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function hashDirectory(hash, root, prefix) {
|
|
555
|
+
for (const file of regularFiles(root)) {
|
|
556
|
+
hashFile(hash, file, join(prefix, relative(root, file)).replaceAll("\\", "/"));
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function hashFile(hash, path, label) {
|
|
561
|
+
hash.update(label);
|
|
562
|
+
hash.update("\0");
|
|
563
|
+
hash.update(readFileSync(path));
|
|
564
|
+
hash.update("\0");
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function regularFiles(root) {
|
|
568
|
+
const files = [];
|
|
569
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
570
|
+
const path = join(root, entry.name);
|
|
571
|
+
const metadata = lstatSync(path);
|
|
572
|
+
if (metadata.isSymbolicLink()) throw new Error(`managed source contains a symbolic link: ${path}`);
|
|
573
|
+
if (metadata.isDirectory()) files.push(...regularFiles(path));
|
|
574
|
+
else if (metadata.isFile()) files.push(path);
|
|
575
|
+
else throw new Error(`managed source contains a non-regular entry: ${path}`);
|
|
576
|
+
}
|
|
577
|
+
return files.sort((left, right) => left.localeCompare(right));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function skillSummary(path, ok) {
|
|
581
|
+
return { ok, status: ok ? "installed" : "missing", managed: ok, memoraxCode: ok, path: join(path, "SKILL.md") };
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function failure(reason, paths, error, action = "status") {
|
|
585
|
+
return {
|
|
586
|
+
ok: false,
|
|
587
|
+
action,
|
|
588
|
+
runtime: "trae",
|
|
589
|
+
integration: "hooks",
|
|
590
|
+
installed: false,
|
|
591
|
+
enabled: false,
|
|
592
|
+
managed: existsSync(paths.statePath),
|
|
593
|
+
reason,
|
|
594
|
+
error: error instanceof Error ? error.message : String(error),
|
|
595
|
+
traeHome: paths.traeHome,
|
|
596
|
+
statePath: paths.statePath,
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function conflict(reason, paths, conflictPath) {
|
|
601
|
+
return { ...failure(reason, paths, new Error(`unmanaged Trae artifact exists: ${conflictPath}`), "enable"), conflictPath };
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
function regularFile(path) {
|
|
605
|
+
try { return statSync(path).isFile() && !lstatSync(path).isSymbolicLink(); } catch { return false; }
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function regularDirectory(path) {
|
|
609
|
+
try { return statSync(path).isDirectory() && !lstatSync(path).isSymbolicLink(); } catch { return false; }
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function absoluteRegularFile(value) {
|
|
613
|
+
const path = stringOption(value);
|
|
614
|
+
return path && isAbsolute(path) && regularFile(path) ? path : undefined;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function containedPath(boundary, path) {
|
|
618
|
+
if (typeof path !== "string") return false;
|
|
619
|
+
const child = relative(resolve(boundary), resolve(path));
|
|
620
|
+
return child !== "" && child !== ".." && !child.startsWith(`..${sep}`) && !isAbsolute(child);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function comparablePath(value, platform) {
|
|
624
|
+
const normalized = String(value ?? "").replaceAll("\\", "/").replace(/\/+$/, "");
|
|
625
|
+
return platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function isRecord(value) {
|
|
629
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
630
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function traeRuntimeObservationPath(memoraxCodeHome) {
|
|
5
|
+
return join(memoraxCodeHome, "adapters", "trae", "runtime-observed.json");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function writeTraeRuntimeObservation({ memoraxCodeHome, traeHome, runtimeDigest }) {
|
|
9
|
+
const path = traeRuntimeObservationPath(memoraxCodeHome);
|
|
10
|
+
const record = {
|
|
11
|
+
version: 1,
|
|
12
|
+
runtimeDigest,
|
|
13
|
+
traeHome,
|
|
14
|
+
observedAt: new Date().toISOString(),
|
|
15
|
+
};
|
|
16
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
17
|
+
const temporaryPath = `${path}.tmp-${process.pid}-${Date.now()}`;
|
|
18
|
+
await writeFile(temporaryPath, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600 });
|
|
19
|
+
await rename(temporaryPath, path);
|
|
20
|
+
return record;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function readTraeRuntimeObservation(memoraxCodeHome) {
|
|
24
|
+
try {
|
|
25
|
+
const value = JSON.parse(await readFile(traeRuntimeObservationPath(memoraxCodeHome), "utf8"));
|
|
26
|
+
if (!isRecord(value) || !hasExactKeys(value, ["observedAt", "runtimeDigest", "traeHome", "version"])) return undefined;
|
|
27
|
+
if (value.version !== 1
|
|
28
|
+
|| !sha256(value.runtimeDigest)
|
|
29
|
+
|| !stringValue(value.traeHome)
|
|
30
|
+
|| !validTimestamp(value.observedAt)) return undefined;
|
|
31
|
+
return value;
|
|
32
|
+
} catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isRecord(value) {
|
|
38
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hasExactKeys(value, expected) {
|
|
42
|
+
return Object.keys(value).sort().join("\n") === [...expected].sort().join("\n");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stringValue(value) {
|
|
46
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function sha256(value) {
|
|
50
|
+
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function validTimestamp(value) {
|
|
54
|
+
return typeof value === "string" && Number.isFinite(Date.parse(value));
|
|
55
|
+
}
|