codeksei 0.1.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.
Files changed (80) hide show
  1. package/LICENSE +661 -0
  2. package/README.en.md +215 -0
  3. package/README.md +259 -0
  4. package/bin/codeksei.js +10 -0
  5. package/bin/cyberboss.js +11 -0
  6. package/package.json +86 -0
  7. package/scripts/install-background-tasks.ps1 +135 -0
  8. package/scripts/open_shared_wechat_thread.sh +94 -0
  9. package/scripts/open_wechat_thread.sh +117 -0
  10. package/scripts/shared-common.js +791 -0
  11. package/scripts/shared-open.js +46 -0
  12. package/scripts/shared-start.js +41 -0
  13. package/scripts/shared-status.js +74 -0
  14. package/scripts/shared-supervisor.js +141 -0
  15. package/scripts/shared-task-runner.ps1 +87 -0
  16. package/scripts/shared-watchdog.js +290 -0
  17. package/scripts/show_shared_status.sh +53 -0
  18. package/scripts/start_shared_app_server.sh +65 -0
  19. package/scripts/start_shared_wechat.sh +108 -0
  20. package/scripts/timeline-screenshot.sh +15 -0
  21. package/scripts/uninstall-background-tasks.ps1 +23 -0
  22. package/src/adapters/channel/weixin/account-store.js +135 -0
  23. package/src/adapters/channel/weixin/api-v2.js +258 -0
  24. package/src/adapters/channel/weixin/api.js +180 -0
  25. package/src/adapters/channel/weixin/context-token-store.js +84 -0
  26. package/src/adapters/channel/weixin/index.js +605 -0
  27. package/src/adapters/channel/weixin/legacy.js +567 -0
  28. package/src/adapters/channel/weixin/login-common.js +63 -0
  29. package/src/adapters/channel/weixin/login-legacy.js +124 -0
  30. package/src/adapters/channel/weixin/login-v2.js +186 -0
  31. package/src/adapters/channel/weixin/media-mime.js +22 -0
  32. package/src/adapters/channel/weixin/media-receive.js +370 -0
  33. package/src/adapters/channel/weixin/media-send.js +331 -0
  34. package/src/adapters/channel/weixin/message-utils-v2.js +282 -0
  35. package/src/adapters/channel/weixin/message-utils.js +199 -0
  36. package/src/adapters/channel/weixin/protocol.js +77 -0
  37. package/src/adapters/channel/weixin/redact.js +41 -0
  38. package/src/adapters/channel/weixin/reminder-queue-store.js +101 -0
  39. package/src/adapters/channel/weixin/sync-buffer-store.js +35 -0
  40. package/src/adapters/runtime/codex/events.js +252 -0
  41. package/src/adapters/runtime/codex/index.js +502 -0
  42. package/src/adapters/runtime/codex/message-utils.js +141 -0
  43. package/src/adapters/runtime/codex/model-catalog.js +106 -0
  44. package/src/adapters/runtime/codex/protocol-leak-monitor.js +75 -0
  45. package/src/adapters/runtime/codex/rpc-client.js +443 -0
  46. package/src/adapters/runtime/codex/session-store.js +376 -0
  47. package/src/app/channel-send-file-cli.js +57 -0
  48. package/src/app/diary-write-cli.js +620 -0
  49. package/src/app/note-auto-cli.js +201 -0
  50. package/src/app/note-sync-cli.js +130 -0
  51. package/src/app/project-radar-cli.js +165 -0
  52. package/src/app/reminder-write-cli.js +210 -0
  53. package/src/app/review-cli.js +134 -0
  54. package/src/app/system-checkin-poller.js +100 -0
  55. package/src/app/system-send-cli.js +129 -0
  56. package/src/app/timeline-event-cli.js +273 -0
  57. package/src/app/timeline-screenshot-cli.js +109 -0
  58. package/src/core/app.js +1810 -0
  59. package/src/core/branding.js +167 -0
  60. package/src/core/command-registry.js +609 -0
  61. package/src/core/config.js +84 -0
  62. package/src/core/default-targets.js +163 -0
  63. package/src/core/durable-note-schema.js +325 -0
  64. package/src/core/instructions-template.js +31 -0
  65. package/src/core/note-sync.js +433 -0
  66. package/src/core/project-radar.js +402 -0
  67. package/src/core/review-semantic.js +524 -0
  68. package/src/core/review.js +1081 -0
  69. package/src/core/shared-bridge-heartbeat.js +140 -0
  70. package/src/core/stream-delivery.js +990 -0
  71. package/src/core/system-message-dispatcher.js +68 -0
  72. package/src/core/system-message-queue-store.js +128 -0
  73. package/src/core/thread-state-store.js +135 -0
  74. package/src/core/timeline-screenshot-queue-store.js +134 -0
  75. package/src/core/workspace-alias.js +163 -0
  76. package/src/core/workspace-bootstrap.js +338 -0
  77. package/src/index.js +270 -0
  78. package/src/integrations/timeline/index.js +191 -0
  79. package/templates/weixin-instructions.md +53 -0
  80. package/templates/weixin-operations.md +69 -0
@@ -0,0 +1,68 @@
1
+ class SystemMessageDispatcher {
2
+ constructor({ queueStore, config, accountId }) {
3
+ this.queueStore = queueStore;
4
+ this.config = config;
5
+ this.accountId = accountId;
6
+ }
7
+
8
+ hasPending() {
9
+ return this.queueStore.hasPendingForAccount(this.accountId);
10
+ }
11
+
12
+ drainPending() {
13
+ return this.queueStore.drainForAccount(this.accountId);
14
+ }
15
+
16
+ requeue(message) {
17
+ return this.queueStore.enqueue(message);
18
+ }
19
+
20
+ resolveWorkspaceRoot(message) {
21
+ return normalizeText(message?.workspaceRoot) || normalizeText(this.config.workspaceRoot);
22
+ }
23
+
24
+ buildPreparedMessage(message, contextToken = "") {
25
+ return {
26
+ provider: "system",
27
+ workspaceId: this.config.workspaceId,
28
+ accountId: this.accountId,
29
+ chatId: message.senderId,
30
+ threadKey: `system:${message.senderId}`,
31
+ senderId: message.senderId,
32
+ messageId: message.id,
33
+ text: buildSystemInboundText(message?.text, this.config),
34
+ attachments: [],
35
+ command: "message",
36
+ contextToken,
37
+ receivedAt: normalizeIsoTime(message?.createdAt) || new Date().toISOString(),
38
+ workspaceRoot: this.resolveWorkspaceRoot(message),
39
+ };
40
+ }
41
+ }
42
+
43
+ function buildSystemInboundText(text, config = {}) {
44
+ const body = normalizeText(text);
45
+ const userName = normalizeText(config?.userName) || "用户";
46
+ if (!body) {
47
+ return `System trigger.\nThis message is not visible to ${userName}.`;
48
+ }
49
+ return `System trigger.\nThis message is not visible to ${userName}.\n${body}`;
50
+ }
51
+
52
+ function normalizeIsoTime(value) {
53
+ const normalized = normalizeText(value);
54
+ if (!normalized) {
55
+ return "";
56
+ }
57
+ const parsed = Date.parse(normalized);
58
+ if (!Number.isFinite(parsed)) {
59
+ return "";
60
+ }
61
+ return new Date(parsed).toISOString();
62
+ }
63
+
64
+ function normalizeText(value) {
65
+ return typeof value === "string" ? value.trim() : "";
66
+ }
67
+
68
+ module.exports = { SystemMessageDispatcher };
@@ -0,0 +1,128 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ class SystemMessageQueueStore {
5
+ constructor({ filePath }) {
6
+ this.filePath = filePath;
7
+ this.state = { messages: [] };
8
+ this.ensureParentDirectory();
9
+ this.load();
10
+ }
11
+
12
+ ensureParentDirectory() {
13
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
14
+ }
15
+
16
+ load() {
17
+ try {
18
+ const raw = fs.readFileSync(this.filePath, "utf8");
19
+ const parsed = JSON.parse(raw);
20
+ const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
21
+ this.state = {
22
+ messages: messages
23
+ .map(normalizeSystemMessage)
24
+ .filter(Boolean)
25
+ .sort(compareSystemMessages),
26
+ };
27
+ } catch {
28
+ this.state = { messages: [] };
29
+ }
30
+ }
31
+
32
+ save() {
33
+ fs.writeFileSync(this.filePath, JSON.stringify(this.state, null, 2));
34
+ }
35
+
36
+ enqueue(message) {
37
+ this.load();
38
+ const normalized = normalizeSystemMessage(message);
39
+ if (!normalized) {
40
+ throw new Error("invalid system message");
41
+ }
42
+ this.state.messages.push(normalized);
43
+ this.state.messages.sort(compareSystemMessages);
44
+ this.save();
45
+ return normalized;
46
+ }
47
+
48
+ drainForAccount(accountId) {
49
+ this.load();
50
+ const normalizedAccountId = normalizeText(accountId);
51
+ const drained = [];
52
+ const pending = [];
53
+
54
+ for (const message of this.state.messages) {
55
+ if (message.accountId === normalizedAccountId) {
56
+ drained.push(message);
57
+ } else {
58
+ pending.push(message);
59
+ }
60
+ }
61
+
62
+ if (drained.length) {
63
+ this.state.messages = pending;
64
+ this.save();
65
+ }
66
+
67
+ return drained;
68
+ }
69
+
70
+ hasPendingForAccount(accountId) {
71
+ this.load();
72
+ const normalizedAccountId = normalizeText(accountId);
73
+ return this.state.messages.some((message) => message.accountId === normalizedAccountId);
74
+ }
75
+ }
76
+
77
+ function normalizeSystemMessage(message) {
78
+ if (!message || typeof message !== "object") {
79
+ return null;
80
+ }
81
+
82
+ const id = normalizeText(message.id);
83
+ const accountId = normalizeText(message.accountId);
84
+ const senderId = normalizeText(message.senderId);
85
+ const workspaceRoot = normalizeText(message.workspaceRoot);
86
+ const text = normalizeText(message.text);
87
+ const createdAt = normalizeIsoTime(message.createdAt);
88
+
89
+ if (!id || !accountId || !senderId || !workspaceRoot || !text) {
90
+ return null;
91
+ }
92
+
93
+ return {
94
+ id,
95
+ accountId,
96
+ senderId,
97
+ workspaceRoot,
98
+ text,
99
+ createdAt: createdAt || new Date().toISOString(),
100
+ };
101
+ }
102
+
103
+ function normalizeIsoTime(value) {
104
+ const normalized = normalizeText(value);
105
+ if (!normalized) {
106
+ return "";
107
+ }
108
+ const parsed = Date.parse(normalized);
109
+ if (!Number.isFinite(parsed)) {
110
+ return "";
111
+ }
112
+ return new Date(parsed).toISOString();
113
+ }
114
+
115
+ function compareSystemMessages(left, right) {
116
+ const leftTime = Date.parse(left?.createdAt || "") || 0;
117
+ const rightTime = Date.parse(right?.createdAt || "") || 0;
118
+ if (leftTime !== rightTime) {
119
+ return leftTime - rightTime;
120
+ }
121
+ return String(left?.id || "").localeCompare(String(right?.id || ""));
122
+ }
123
+
124
+ function normalizeText(value) {
125
+ return typeof value === "string" ? value.trim() : "";
126
+ }
127
+
128
+ module.exports = { SystemMessageQueueStore };
@@ -0,0 +1,135 @@
1
+ class ThreadStateStore {
2
+ constructor() {
3
+ this.stateByThreadId = new Map();
4
+ this.latestUsage = null;
5
+ }
6
+
7
+ applyRuntimeEvent(event) {
8
+ if (event?.type === "runtime.usage.updated") {
9
+ this.latestUsage = {
10
+ ...event.payload,
11
+ updatedAt: new Date().toISOString(),
12
+ };
13
+ return;
14
+ }
15
+ if (!event || !event.payload || !event.payload.threadId) {
16
+ return;
17
+ }
18
+
19
+ const threadId = event.payload.threadId;
20
+ const current = this.stateByThreadId.get(threadId) || createEmptyThreadState(threadId);
21
+ const next = {
22
+ ...current,
23
+ updatedAt: new Date().toISOString(),
24
+ };
25
+
26
+ switch (event.type) {
27
+ case "runtime.turn.started":
28
+ next.status = "running";
29
+ next.turnId = event.payload.turnId || next.turnId;
30
+ next.lastError = "";
31
+ break;
32
+ case "runtime.reply.delta":
33
+ next.status = "running";
34
+ next.turnId = event.payload.turnId || next.turnId;
35
+ next.lastReplyText = event.payload.text || next.lastReplyText;
36
+ break;
37
+ case "runtime.reply.completed":
38
+ next.status = "running";
39
+ next.turnId = event.payload.turnId || next.turnId;
40
+ next.lastReplyText = event.payload.text || next.lastReplyText;
41
+ break;
42
+ case "runtime.approval.requested":
43
+ next.status = "waiting_approval";
44
+ next.pendingApproval = {
45
+ requestId: event.payload.requestId ?? null,
46
+ reason: event.payload.reason || "",
47
+ command: event.payload.command || "",
48
+ commandTokens: Array.isArray(event.payload.commandTokens) ? event.payload.commandTokens : [],
49
+ };
50
+ break;
51
+ case "runtime.turn.completed":
52
+ next.status = "idle";
53
+ next.turnId = event.payload.turnId || next.turnId;
54
+ next.pendingApproval = null;
55
+ break;
56
+ case "runtime.turn.failed":
57
+ next.status = "failed";
58
+ next.turnId = event.payload.turnId || next.turnId;
59
+ next.lastError = event.payload.text || "执行失败";
60
+ next.pendingApproval = null;
61
+ break;
62
+ default:
63
+ break;
64
+ }
65
+
66
+ this.stateByThreadId.set(threadId, next);
67
+ }
68
+
69
+ getThreadState(threadId) {
70
+ return this.stateByThreadId.get(threadId) || null;
71
+ }
72
+
73
+ resolveApproval(threadId, status = "running") {
74
+ const current = this.stateByThreadId.get(threadId);
75
+ if (!current) {
76
+ return null;
77
+ }
78
+ const next = {
79
+ ...current,
80
+ status,
81
+ pendingApproval: null,
82
+ updatedAt: new Date().toISOString(),
83
+ };
84
+ this.stateByThreadId.set(threadId, next);
85
+ return next;
86
+ }
87
+
88
+ markTurnFailed(threadId, turnId, message = "执行失败") {
89
+ const normalizedThreadId = normalizeText(threadId);
90
+ const normalizedTurnId = normalizeText(turnId);
91
+ if (!normalizedThreadId) {
92
+ return null;
93
+ }
94
+ const current = this.stateByThreadId.get(normalizedThreadId) || createEmptyThreadState(normalizedThreadId);
95
+ if (normalizedTurnId && normalizeText(current.turnId) && normalizeText(current.turnId) !== normalizedTurnId) {
96
+ return current;
97
+ }
98
+ const next = {
99
+ ...current,
100
+ status: "failed",
101
+ turnId: normalizedTurnId || current.turnId,
102
+ lastError: normalizeText(message) || "执行失败",
103
+ pendingApproval: null,
104
+ updatedAt: new Date().toISOString(),
105
+ };
106
+ this.stateByThreadId.set(normalizedThreadId, next);
107
+ return next;
108
+ }
109
+
110
+ snapshot() {
111
+ return Array.from(this.stateByThreadId.values()).map((entry) => ({ ...entry }));
112
+ }
113
+
114
+ getLatestUsage() {
115
+ return this.latestUsage ? { ...this.latestUsage } : null;
116
+ }
117
+ }
118
+
119
+ function createEmptyThreadState(threadId) {
120
+ return {
121
+ threadId,
122
+ turnId: "",
123
+ status: "idle",
124
+ lastReplyText: "",
125
+ lastError: "",
126
+ pendingApproval: null,
127
+ updatedAt: new Date().toISOString(),
128
+ };
129
+ }
130
+
131
+ function normalizeText(value) {
132
+ return typeof value === "string" ? value.trim() : "";
133
+ }
134
+
135
+ module.exports = { ThreadStateStore };
@@ -0,0 +1,134 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ class TimelineScreenshotQueueStore {
5
+ constructor({ filePath }) {
6
+ this.filePath = filePath;
7
+ this.state = { jobs: [] };
8
+ this.ensureParentDirectory();
9
+ this.load();
10
+ }
11
+
12
+ ensureParentDirectory() {
13
+ fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
14
+ }
15
+
16
+ load() {
17
+ try {
18
+ const raw = fs.readFileSync(this.filePath, "utf8");
19
+ const parsed = JSON.parse(raw);
20
+ const jobs = Array.isArray(parsed?.jobs) ? parsed.jobs : [];
21
+ this.state = {
22
+ jobs: jobs
23
+ .map(normalizeTimelineScreenshotJob)
24
+ .filter(Boolean)
25
+ .sort(compareTimelineScreenshotJobs),
26
+ };
27
+ } catch {
28
+ this.state = { jobs: [] };
29
+ }
30
+ }
31
+
32
+ save() {
33
+ fs.writeFileSync(this.filePath, JSON.stringify(this.state, null, 2));
34
+ }
35
+
36
+ enqueue(job) {
37
+ this.load();
38
+ const normalized = normalizeTimelineScreenshotJob(job);
39
+ if (!normalized) {
40
+ throw new Error("invalid timeline screenshot job");
41
+ }
42
+ this.state.jobs.push(normalized);
43
+ this.state.jobs.sort(compareTimelineScreenshotJobs);
44
+ this.save();
45
+ return normalized;
46
+ }
47
+
48
+ drainForAccount(accountId) {
49
+ this.load();
50
+ const normalizedAccountId = normalizeText(accountId);
51
+ const drained = [];
52
+ const pending = [];
53
+
54
+ for (const job of this.state.jobs) {
55
+ if (job.accountId === normalizedAccountId) {
56
+ drained.push(job);
57
+ } else {
58
+ pending.push(job);
59
+ }
60
+ }
61
+
62
+ if (drained.length) {
63
+ this.state.jobs = pending;
64
+ this.save();
65
+ }
66
+
67
+ return drained;
68
+ }
69
+
70
+ hasPendingForAccount(accountId) {
71
+ this.load();
72
+ const normalizedAccountId = normalizeText(accountId);
73
+ return this.state.jobs.some((job) => job.accountId === normalizedAccountId);
74
+ }
75
+ }
76
+
77
+ function normalizeTimelineScreenshotJob(job) {
78
+ if (!job || typeof job !== "object") {
79
+ return null;
80
+ }
81
+
82
+ const id = normalizeText(job.id);
83
+ const accountId = normalizeText(job.accountId);
84
+ const senderId = normalizeText(job.senderId);
85
+ const outputFile = normalizeText(job.outputFile);
86
+ const createdAt = normalizeIsoTime(job.createdAt);
87
+ const args = normalizeArgs(job.args);
88
+
89
+ if (!id || !accountId || !senderId) {
90
+ return null;
91
+ }
92
+
93
+ return {
94
+ id,
95
+ accountId,
96
+ senderId,
97
+ outputFile,
98
+ args,
99
+ createdAt: createdAt || new Date().toISOString(),
100
+ };
101
+ }
102
+
103
+ function normalizeArgs(args) {
104
+ return Array.isArray(args)
105
+ ? args.map((value) => normalizeText(value)).filter(Boolean)
106
+ : [];
107
+ }
108
+
109
+ function normalizeIsoTime(value) {
110
+ const normalized = normalizeText(value);
111
+ if (!normalized) {
112
+ return "";
113
+ }
114
+ const parsed = Date.parse(normalized);
115
+ if (!Number.isFinite(parsed)) {
116
+ return "";
117
+ }
118
+ return new Date(parsed).toISOString();
119
+ }
120
+
121
+ function compareTimelineScreenshotJobs(left, right) {
122
+ const leftTime = Date.parse(left?.createdAt || "") || 0;
123
+ const rightTime = Date.parse(right?.createdAt || "") || 0;
124
+ if (leftTime !== rightTime) {
125
+ return leftTime - rightTime;
126
+ }
127
+ return String(left?.id || "").localeCompare(String(right?.id || ""));
128
+ }
129
+
130
+ function normalizeText(value) {
131
+ return typeof value === "string" ? value.trim() : "";
132
+ }
133
+
134
+ module.exports = { TimelineScreenshotQueueStore };
@@ -0,0 +1,163 @@
1
+ const fs = require("fs");
2
+ const os = require("os");
3
+ const path = require("path");
4
+
5
+ const DEFAULT_ALIAS_MANIFEST = path.join(os.homedir(), ".codex", "windows-ascii-alias", "aliases.json");
6
+
7
+ function resolveCodexWorkspaceRoot(workspaceRoot, options = {}) {
8
+ const normalized = normalizeWorkspaceRoot(workspaceRoot);
9
+ if (!normalized) {
10
+ return "";
11
+ }
12
+ if (isAscii(normalized)) {
13
+ return normalized;
14
+ }
15
+
16
+ const mapping = resolveAliasMappingForPath(normalized, options);
17
+ if (!mapping) {
18
+ return normalized;
19
+ }
20
+
21
+ const converted = convertRootedPath(normalized, mapping.fromRoot, mapping.toRoot);
22
+ if (!converted) {
23
+ return normalized;
24
+ }
25
+ // Alias roots are only a compatibility shim for Codex transport quirks. If
26
+ // the manifest drifts or the junction disappears, prefer the real workspace
27
+ // root over a synthetic cwd that would otherwise trigger os error 267.
28
+ return isReadableDirectory(converted) ? converted : normalized;
29
+ }
30
+
31
+ function resolveAliasMappingForPath(workspaceRoot, options = {}) {
32
+ const normalized = normalizeWorkspaceRoot(workspaceRoot);
33
+ if (!normalized) {
34
+ return null;
35
+ }
36
+
37
+ const mappings = loadAliasMappings(options);
38
+ let bestMatch = null;
39
+ let bestLength = -1;
40
+
41
+ for (const mapping of mappings) {
42
+ for (const candidate of [
43
+ { kind: "target", root: mapping.targetPath, toRoot: mapping.aliasPath },
44
+ { kind: "alias", root: mapping.aliasPath, toRoot: mapping.aliasPath },
45
+ ]) {
46
+ if (!isPathWithinRoot(normalized, candidate.root)) {
47
+ continue;
48
+ }
49
+ if (candidate.root.length <= bestLength) {
50
+ continue;
51
+ }
52
+ bestMatch = {
53
+ slug: mapping.slug,
54
+ kind: candidate.kind,
55
+ fromRoot: candidate.root,
56
+ toRoot: candidate.toRoot,
57
+ };
58
+ bestLength = candidate.root.length;
59
+ }
60
+ }
61
+
62
+ return bestMatch;
63
+ }
64
+
65
+ function loadAliasMappings(options = {}) {
66
+ const manifestPath = typeof options.manifestPath === "string" && options.manifestPath.trim()
67
+ ? options.manifestPath.trim()
68
+ : DEFAULT_ALIAS_MANIFEST;
69
+ try {
70
+ const raw = fs.readFileSync(manifestPath, "utf8");
71
+ const parsed = JSON.parse(raw);
72
+ const mappings = Array.isArray(parsed?.mappings) ? parsed.mappings : [];
73
+ return mappings
74
+ .map((mapping) => ({
75
+ slug: normalizeText(mapping?.slug),
76
+ targetPath: normalizeWorkspaceRoot(mapping?.target_path),
77
+ aliasPath: normalizeWorkspaceRoot(mapping?.alias_path),
78
+ }))
79
+ .filter((mapping) => mapping.slug && mapping.targetPath && mapping.aliasPath);
80
+ } catch {
81
+ return [];
82
+ }
83
+ }
84
+
85
+ function convertRootedPath(pathValue, fromRoot, toRoot) {
86
+ const normalizedPath = normalizeWorkspaceRoot(pathValue);
87
+ const normalizedFromRoot = normalizeWorkspaceRoot(fromRoot);
88
+ const normalizedToRoot = normalizeWorkspaceRoot(toRoot);
89
+ if (!normalizedPath || !normalizedFromRoot || !normalizedToRoot) {
90
+ return "";
91
+ }
92
+ if (!isPathWithinRoot(normalizedPath, normalizedFromRoot)) {
93
+ return "";
94
+ }
95
+ if (normalizedPath === normalizedFromRoot) {
96
+ return normalizedToRoot;
97
+ }
98
+ return `${normalizedToRoot}${normalizedPath.slice(normalizedFromRoot.length)}`;
99
+ }
100
+
101
+ function isPathWithinRoot(pathValue, rootValue) {
102
+ const normalizedPath = normalizeWorkspaceRoot(pathValue);
103
+ const normalizedRoot = normalizeWorkspaceRoot(rootValue);
104
+ if (!normalizedPath || !normalizedRoot) {
105
+ return false;
106
+ }
107
+ if (normalizedPath === normalizedRoot) {
108
+ return true;
109
+ }
110
+ if (normalizedPath.length <= normalizedRoot.length) {
111
+ return false;
112
+ }
113
+ if (!normalizedPath.toLowerCase().startsWith(normalizedRoot.toLowerCase())) {
114
+ return false;
115
+ }
116
+ const nextChar = normalizedPath[normalizedRoot.length];
117
+ return nextChar === "/" || nextChar === "\\";
118
+ }
119
+
120
+ function isReadableDirectory(pathValue) {
121
+ const normalized = normalizeWorkspaceRoot(pathValue);
122
+ if (!normalized) {
123
+ return false;
124
+ }
125
+ try {
126
+ return fs.statSync(path.resolve(normalized)).isDirectory();
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+
132
+ function normalizeWorkspaceRoot(value) {
133
+ const normalized = normalizeText(value).replace(/\\/g, "/");
134
+ if (!normalized) {
135
+ return "";
136
+ }
137
+ if (normalized.length > 3) {
138
+ return normalized.replace(/\/+$/g, "");
139
+ }
140
+ return normalized;
141
+ }
142
+
143
+ function isAscii(value) {
144
+ for (const char of String(value || "")) {
145
+ if (char.charCodeAt(0) > 127) {
146
+ return false;
147
+ }
148
+ }
149
+ return true;
150
+ }
151
+
152
+ function normalizeText(value) {
153
+ return typeof value === "string" ? value.trim() : "";
154
+ }
155
+
156
+ module.exports = {
157
+ DEFAULT_ALIAS_MANIFEST,
158
+ convertRootedPath,
159
+ loadAliasMappings,
160
+ normalizeWorkspaceRoot,
161
+ resolveAliasMappingForPath,
162
+ resolveCodexWorkspaceRoot,
163
+ };