@cydm/magic-shell-agent-node 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.
- package/dist/adapters/pty-adapter.d.ts +18 -0
- package/dist/adapters/pty-adapter.js +99 -0
- package/dist/adapters/registry.d.ts +28 -0
- package/dist/adapters/registry.js +64 -0
- package/dist/adapters/rpc-adapter.d.ts +19 -0
- package/dist/adapters/rpc-adapter.js +182 -0
- package/dist/adapters/stdio-adapter.d.ts +17 -0
- package/dist/adapters/stdio-adapter.js +107 -0
- package/dist/adapters/types.d.ts +17 -0
- package/dist/adapters/types.js +2 -0
- package/dist/claude-exec.d.ts +11 -0
- package/dist/claude-exec.js +54 -0
- package/dist/claude-worker.d.ts +12 -0
- package/dist/claude-worker.js +163 -0
- package/dist/codex-exec.d.ts +12 -0
- package/dist/codex-exec.js +84 -0
- package/dist/codex-worker.d.ts +12 -0
- package/dist/codex-worker.js +179 -0
- package/dist/directory-browser.d.ts +3 -0
- package/dist/directory-browser.js +48 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/local-direct-server.d.ts +38 -0
- package/dist/local-direct-server.js +266 -0
- package/dist/node-conversation.d.ts +21 -0
- package/dist/node-conversation.js +28 -0
- package/dist/node-intent.d.ts +2 -0
- package/dist/node-intent.js +40 -0
- package/dist/node-reply.d.ts +30 -0
- package/dist/node-reply.js +77 -0
- package/dist/node.d.ts +132 -0
- package/dist/node.js +1954 -0
- package/dist/pie-session-control.d.ts +21 -0
- package/dist/pie-session-control.js +28 -0
- package/dist/plugin-loader.d.ts +19 -0
- package/dist/plugin-loader.js +144 -0
- package/dist/plugins/pie.json +7 -0
- package/dist/primary-agent-bridge.d.ts +69 -0
- package/dist/primary-agent-bridge.js +282 -0
- package/dist/session-manager.d.ts +66 -0
- package/dist/session-manager.js +197 -0
- package/dist/terminal-metadata.d.ts +7 -0
- package/dist/terminal-metadata.js +52 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/dist/worker-control.d.ts +15 -0
- package/dist/worker-control.js +89 -0
- package/dist/worker-narration.d.ts +25 -0
- package/dist/worker-narration.js +90 -0
- package/dist/worker-output.d.ts +6 -0
- package/dist/worker-output.js +72 -0
- package/dist/worker-registry.d.ts +45 -0
- package/dist/worker-registry.js +501 -0
- package/dist/worker-runtime.d.ts +18 -0
- package/dist/worker-runtime.js +69 -0
- package/dist/ws-client.d.ts +68 -0
- package/dist/ws-client.js +193 -0
- package/package.json +38 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { stripAnsi } from "./worker-output.js";
|
|
2
|
+
function normalizeWhitespace(value) {
|
|
3
|
+
return stripAnsi(value || "")
|
|
4
|
+
.replace(/\r/g, "\n")
|
|
5
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
6
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
7
|
+
.trim();
|
|
8
|
+
}
|
|
9
|
+
function normalizeComparable(value) {
|
|
10
|
+
return normalizeWhitespace(value)
|
|
11
|
+
.replace(/\s+/g, " ")
|
|
12
|
+
.trim()
|
|
13
|
+
.toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
function isMostlyDecoration(line) {
|
|
16
|
+
return /^[\u2500-\u257f\u2580-\u259f\s]+$/u.test(line)
|
|
17
|
+
|| /^[`~!@#$%^&*()_+=\-[\]{};:'",.<>/?\\|]+$/.test(line);
|
|
18
|
+
}
|
|
19
|
+
function isClaudeNoiseLine(line) {
|
|
20
|
+
const normalized = line.trim();
|
|
21
|
+
if (!normalized)
|
|
22
|
+
return true;
|
|
23
|
+
if (normalized.length < 2)
|
|
24
|
+
return true;
|
|
25
|
+
if (normalized === "rupt")
|
|
26
|
+
return true;
|
|
27
|
+
if (isMostlyDecoration(normalized))
|
|
28
|
+
return true;
|
|
29
|
+
if (/^\?+\s*for shortcuts$/i.test(normalized))
|
|
30
|
+
return true;
|
|
31
|
+
if (/^(esc|ctrl\+c|enter)\b/i.test(normalized))
|
|
32
|
+
return true;
|
|
33
|
+
if (/^claude code$/i.test(normalized))
|
|
34
|
+
return true;
|
|
35
|
+
if (/^claude$/i.test(normalized))
|
|
36
|
+
return true;
|
|
37
|
+
if (/^╭.*claude\s*code/i.test(normalized))
|
|
38
|
+
return true;
|
|
39
|
+
if (/^╰[─-]+╯$/u.test(normalized))
|
|
40
|
+
return true;
|
|
41
|
+
if (/^model:/i.test(normalized))
|
|
42
|
+
return true;
|
|
43
|
+
if (/^cwd:/i.test(normalized))
|
|
44
|
+
return true;
|
|
45
|
+
if (/^status:/i.test(normalized))
|
|
46
|
+
return true;
|
|
47
|
+
if (/^opus now defaults/i.test(normalized))
|
|
48
|
+
return true;
|
|
49
|
+
if (/^↑\s*opus now defaults/i.test(normalized))
|
|
50
|
+
return true;
|
|
51
|
+
if (/^[│┌┐└┘├┤┬┴┼].*/u.test(normalized))
|
|
52
|
+
return true;
|
|
53
|
+
if (/^[❯›»>]\s*/u.test(normalized))
|
|
54
|
+
return true;
|
|
55
|
+
if (/^[◐◑◒◓].*/u.test(normalized))
|
|
56
|
+
return true;
|
|
57
|
+
if (/^[✢✣✤✥✦✧✩✪✫✬✭✮✯✰✱✲✳✴✵✶✷✸✹✺✻✼✽].*/u.test(normalized))
|
|
58
|
+
return true;
|
|
59
|
+
if (/^⏵⏵\s*bypass permissions on/i.test(normalized))
|
|
60
|
+
return true;
|
|
61
|
+
if (/(shift\+tab to cycle|esc to interrupt)/i.test(normalized))
|
|
62
|
+
return true;
|
|
63
|
+
if (/\/effort\b/i.test(normalized))
|
|
64
|
+
return true;
|
|
65
|
+
if (/(percolating|thinking|pondering|brainstorming)/i.test(normalized))
|
|
66
|
+
return true;
|
|
67
|
+
if (/^▋|^▐|^▝|^▘|^█|^▛|^▜|^▌/u.test(normalized))
|
|
68
|
+
return true;
|
|
69
|
+
if (/^(thinking|working|ready|waiting)$/i.test(normalized))
|
|
70
|
+
return true;
|
|
71
|
+
if (/^(claude-|gpt-|qwen-|kimi-|local-|company-)/i.test(normalized))
|
|
72
|
+
return true;
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
function removeInputEcho(lines, input) {
|
|
76
|
+
const comparableInput = normalizeComparable(input);
|
|
77
|
+
if (!comparableInput)
|
|
78
|
+
return lines;
|
|
79
|
+
return lines.filter((line) => {
|
|
80
|
+
const comparable = normalizeComparable(line);
|
|
81
|
+
if (!comparable)
|
|
82
|
+
return false;
|
|
83
|
+
if (comparable === comparableInput)
|
|
84
|
+
return false;
|
|
85
|
+
if (comparableInput.includes(comparable) || comparable.includes(comparableInput))
|
|
86
|
+
return false;
|
|
87
|
+
return true;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export function isClaudeCodeWorker(agentType) {
|
|
91
|
+
return agentType === "claude-code";
|
|
92
|
+
}
|
|
93
|
+
export function claudeNeedsTrustConfirmation(bufferedOutput) {
|
|
94
|
+
const cleaned = normalizeWhitespace(bufferedOutput);
|
|
95
|
+
if (!cleaned)
|
|
96
|
+
return false;
|
|
97
|
+
return /quick safety check/i.test(cleaned)
|
|
98
|
+
|| /accessing workspace:/i.test(cleaned)
|
|
99
|
+
|| /yes,\s*i trust this folder/i.test(cleaned)
|
|
100
|
+
|| /enter to confirm/i.test(cleaned);
|
|
101
|
+
}
|
|
102
|
+
export function formatClaudeTurnInput(message) {
|
|
103
|
+
if (!message)
|
|
104
|
+
return "\r";
|
|
105
|
+
if (message.endsWith("\r") || message.endsWith("\n"))
|
|
106
|
+
return message;
|
|
107
|
+
return `${message}\r`;
|
|
108
|
+
}
|
|
109
|
+
export function summarizeClaudeWorkerOutput(bufferedOutput, input = "") {
|
|
110
|
+
const cleaned = normalizeWhitespace(bufferedOutput);
|
|
111
|
+
if (!cleaned)
|
|
112
|
+
return "";
|
|
113
|
+
const lines = removeInputEcho(cleaned
|
|
114
|
+
.split("\n")
|
|
115
|
+
.map((line) => line.trim())
|
|
116
|
+
.filter((line) => !isClaudeNoiseLine(line)), input);
|
|
117
|
+
if (!lines.length)
|
|
118
|
+
return "";
|
|
119
|
+
return Array.from(new Set(lines)).slice(-4).join("\n").slice(-600);
|
|
120
|
+
}
|
|
121
|
+
export function getLastClaudeWorkerMessage(bufferedOutput, input = "") {
|
|
122
|
+
const summary = summarizeClaudeWorkerOutput(bufferedOutput, input);
|
|
123
|
+
if (!summary)
|
|
124
|
+
return "";
|
|
125
|
+
const lines = summary.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
126
|
+
return lines[lines.length - 1] || "";
|
|
127
|
+
}
|
|
128
|
+
export function getFreshClaudeTurnResult(beforeOutput, afterOutput, input) {
|
|
129
|
+
const delta = afterOutput.startsWith(beforeOutput) ? afterOutput.slice(beforeOutput.length) : afterOutput;
|
|
130
|
+
const deltaSummary = summarizeClaudeWorkerOutput(delta, input);
|
|
131
|
+
if (deltaSummary) {
|
|
132
|
+
const deltaLines = deltaSummary.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
133
|
+
return {
|
|
134
|
+
message: deltaLines[deltaLines.length - 1] || "",
|
|
135
|
+
summary: deltaSummary,
|
|
136
|
+
changed: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const wholeSummary = summarizeClaudeWorkerOutput(afterOutput, input);
|
|
140
|
+
if (!wholeSummary) {
|
|
141
|
+
return { message: "", summary: "", changed: false };
|
|
142
|
+
}
|
|
143
|
+
const beforeSummary = summarizeClaudeWorkerOutput(beforeOutput, input);
|
|
144
|
+
if (wholeSummary === beforeSummary) {
|
|
145
|
+
return { message: "", summary: wholeSummary, changed: false };
|
|
146
|
+
}
|
|
147
|
+
const wholeLines = wholeSummary.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
148
|
+
return {
|
|
149
|
+
message: wholeLines[wholeLines.length - 1] || "",
|
|
150
|
+
summary: wholeSummary,
|
|
151
|
+
changed: true,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
export function isClaudeReadyForTask(bufferedOutput) {
|
|
155
|
+
const cleaned = normalizeWhitespace(bufferedOutput);
|
|
156
|
+
if (!cleaned)
|
|
157
|
+
return false;
|
|
158
|
+
if (claudeNeedsTrustConfirmation(cleaned))
|
|
159
|
+
return false;
|
|
160
|
+
return /for shortcuts/i.test(cleaned)
|
|
161
|
+
|| /claude code/i.test(cleaned)
|
|
162
|
+
|| cleaned.length > 80;
|
|
163
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CodexExecOptions {
|
|
2
|
+
cwd: string;
|
|
3
|
+
message: string;
|
|
4
|
+
timeoutMs: number;
|
|
5
|
+
}
|
|
6
|
+
export interface CodexExecResult {
|
|
7
|
+
message: string;
|
|
8
|
+
summary: string;
|
|
9
|
+
rawOutput: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function extractCodexExecResult(rawOutput: string): CodexExecResult;
|
|
12
|
+
export declare function runCodexExec(options: CodexExecOptions): Promise<CodexExecResult>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return !!value && typeof value === "object";
|
|
4
|
+
}
|
|
5
|
+
export function extractCodexExecResult(rawOutput) {
|
|
6
|
+
const messages = [];
|
|
7
|
+
for (const line of rawOutput.split("\n")) {
|
|
8
|
+
const trimmed = line.trim();
|
|
9
|
+
if (!trimmed.startsWith("{"))
|
|
10
|
+
continue;
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(trimmed);
|
|
13
|
+
if (parsed.type === "item.completed"
|
|
14
|
+
&& isRecord(parsed.item)
|
|
15
|
+
&& parsed.item.type === "agent_message"
|
|
16
|
+
&& typeof parsed.item.text === "string"
|
|
17
|
+
&& parsed.item.text.trim()) {
|
|
18
|
+
messages.push(parsed.item.text.trim());
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const uniqueMessages = Array.from(new Set(messages));
|
|
26
|
+
const summary = uniqueMessages.join("\n").trim();
|
|
27
|
+
return {
|
|
28
|
+
message: uniqueMessages[uniqueMessages.length - 1] || "",
|
|
29
|
+
summary,
|
|
30
|
+
rawOutput,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export async function runCodexExec(options) {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
const child = spawn("codex", [
|
|
36
|
+
"exec",
|
|
37
|
+
"--json",
|
|
38
|
+
"--skip-git-repo-check",
|
|
39
|
+
"--dangerously-bypass-approvals-and-sandbox",
|
|
40
|
+
"-C",
|
|
41
|
+
options.cwd,
|
|
42
|
+
options.message,
|
|
43
|
+
], {
|
|
44
|
+
cwd: options.cwd,
|
|
45
|
+
env: process.env,
|
|
46
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
47
|
+
});
|
|
48
|
+
let stdout = "";
|
|
49
|
+
let stderr = "";
|
|
50
|
+
let settled = false;
|
|
51
|
+
const finish = (handler) => {
|
|
52
|
+
if (settled)
|
|
53
|
+
return;
|
|
54
|
+
settled = true;
|
|
55
|
+
clearTimeout(timeoutHandle);
|
|
56
|
+
handler();
|
|
57
|
+
};
|
|
58
|
+
child.stdout.on("data", (chunk) => {
|
|
59
|
+
stdout += chunk.toString();
|
|
60
|
+
});
|
|
61
|
+
child.stderr.on("data", (chunk) => {
|
|
62
|
+
stderr += chunk.toString();
|
|
63
|
+
});
|
|
64
|
+
child.on("error", (error) => {
|
|
65
|
+
finish(() => reject(error));
|
|
66
|
+
});
|
|
67
|
+
child.on("close", (code) => {
|
|
68
|
+
finish(() => {
|
|
69
|
+
const rawOutput = [stdout, stderr].filter(Boolean).join("");
|
|
70
|
+
if (code !== 0) {
|
|
71
|
+
reject(new Error(rawOutput.trim() || `codex exec exited with code ${code}`));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
resolve(extractCodexExecResult(rawOutput));
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
const timeoutHandle = setTimeout(() => {
|
|
78
|
+
child.kill("SIGTERM");
|
|
79
|
+
const error = new Error(`codex exec timed out after ${options.timeoutMs}ms`);
|
|
80
|
+
error.code = "CODEX_EXEC_TIMEOUT";
|
|
81
|
+
finish(() => reject(error));
|
|
82
|
+
}, Math.max(options.timeoutMs, 250));
|
|
83
|
+
});
|
|
84
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface CodexTurnResult {
|
|
2
|
+
message: string;
|
|
3
|
+
summary: string;
|
|
4
|
+
changed: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare function isCodexWorker(agentType: string | undefined): boolean;
|
|
7
|
+
export declare function codexNeedsTrustConfirmation(bufferedOutput: string): boolean;
|
|
8
|
+
export declare function isCodexReadyForTask(bufferedOutput: string): boolean;
|
|
9
|
+
export declare function formatCodexTurnInput(message: string): string;
|
|
10
|
+
export declare function summarizeCodexWorkerOutput(bufferedOutput: string, input?: string): string;
|
|
11
|
+
export declare function getLastCodexWorkerMessage(bufferedOutput: string, input?: string): string;
|
|
12
|
+
export declare function getFreshCodexTurnResult(beforeOutput: string, afterOutput: string, input: string): CodexTurnResult;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { stripAnsi } from "./worker-output.js";
|
|
2
|
+
function normalizeWhitespace(value) {
|
|
3
|
+
return stripAnsi(value || "")
|
|
4
|
+
.replace(/\r/g, "\n")
|
|
5
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
6
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
7
|
+
.trim();
|
|
8
|
+
}
|
|
9
|
+
function normalizeComparable(value) {
|
|
10
|
+
return normalizeWhitespace(value)
|
|
11
|
+
.replace(/\s+/g, " ")
|
|
12
|
+
.trim()
|
|
13
|
+
.toLowerCase();
|
|
14
|
+
}
|
|
15
|
+
function isCodexNoiseLine(line) {
|
|
16
|
+
const normalized = line.trim();
|
|
17
|
+
if (!normalized)
|
|
18
|
+
return true;
|
|
19
|
+
if (normalized.length < 2)
|
|
20
|
+
return true;
|
|
21
|
+
if (/^\d+\.?$/i.test(normalized))
|
|
22
|
+
return true;
|
|
23
|
+
if (/^v?\d+\.\d+\.\d+$/i.test(normalized))
|
|
24
|
+
return true;
|
|
25
|
+
if (/openai codex/i.test(normalized))
|
|
26
|
+
return true;
|
|
27
|
+
if (/^>_\s*openai codex/i.test(normalized))
|
|
28
|
+
return true;
|
|
29
|
+
if (/^model:\s*/i.test(normalized))
|
|
30
|
+
return true;
|
|
31
|
+
if (/^directory:\s*/i.test(normalized))
|
|
32
|
+
return true;
|
|
33
|
+
if (/^tip:\s*/i.test(normalized))
|
|
34
|
+
return true;
|
|
35
|
+
if (/^press enter to continue$/i.test(normalized))
|
|
36
|
+
return true;
|
|
37
|
+
if (/^do you trust the contents of this directory\?/i.test(normalized))
|
|
38
|
+
return true;
|
|
39
|
+
if (/^working with untrusted contents/i.test(normalized))
|
|
40
|
+
return true;
|
|
41
|
+
if (/prompt injection/i.test(normalized))
|
|
42
|
+
return true;
|
|
43
|
+
if (/^1\.\s*yes,\s*continue/i.test(normalized))
|
|
44
|
+
return true;
|
|
45
|
+
if (/^2\.\s*no,\s*quit/i.test(normalized))
|
|
46
|
+
return true;
|
|
47
|
+
if (/^find and fix a bug in @filename$/i.test(normalized))
|
|
48
|
+
return true;
|
|
49
|
+
if (/^gpt-[\w.-]+\s+.+% left/i.test(normalized))
|
|
50
|
+
return true;
|
|
51
|
+
if (/^\d+% left\b/i.test(normalized))
|
|
52
|
+
return true;
|
|
53
|
+
if (/^working \(\d+s/i.test(normalized))
|
|
54
|
+
return true;
|
|
55
|
+
if (/^(esc to interrupt|press enter to continue)$/i.test(normalized))
|
|
56
|
+
return true;
|
|
57
|
+
if (/^(you are in|continue anyway\? \[y\/n\]:)/i.test(normalized))
|
|
58
|
+
return true;
|
|
59
|
+
if (/^https?:\/\//i.test(normalized))
|
|
60
|
+
return true;
|
|
61
|
+
if (/^[╭╰│─]+$/u.test(normalized))
|
|
62
|
+
return true;
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
function removeInputEcho(lines, input) {
|
|
66
|
+
const comparableInput = normalizeComparable(input);
|
|
67
|
+
if (!comparableInput)
|
|
68
|
+
return lines;
|
|
69
|
+
return lines.filter((line) => {
|
|
70
|
+
const comparable = normalizeComparable(line);
|
|
71
|
+
if (!comparable)
|
|
72
|
+
return false;
|
|
73
|
+
if (comparable === comparableInput)
|
|
74
|
+
return false;
|
|
75
|
+
if (comparableInput.includes(comparable) || comparable.includes(comparableInput))
|
|
76
|
+
return false;
|
|
77
|
+
return true;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function cleanCodexLine(line) {
|
|
81
|
+
return line
|
|
82
|
+
.replace(/^[•●◦▪▸▹►▻]\s*/u, "")
|
|
83
|
+
.replace(/^[›>]\s*/u, "")
|
|
84
|
+
.trim();
|
|
85
|
+
}
|
|
86
|
+
function stripInlineCodexNoise(value) {
|
|
87
|
+
return value
|
|
88
|
+
.replace(/│\s*model:[^│]*│/gi, " ")
|
|
89
|
+
.replace(/│\s*directory:[^│]*│/gi, " ")
|
|
90
|
+
.replace(/[╭╰│─]+/gu, " ")
|
|
91
|
+
.replace(/working\s*\(\d+s\s*•\s*esc to interrupt\)/gi, " ")
|
|
92
|
+
.replace(/gpt-[\w.-]+\s+medium\s+·\s+\d+% left\s+·\s+[^\n]+/gi, " ")
|
|
93
|
+
.replace(/[›>]\s*implement \{feature\}/gi, " ")
|
|
94
|
+
.replace(/find and fix a bug in @filename/gi, " ")
|
|
95
|
+
.replace(/[•◦]\d+/g, " ")
|
|
96
|
+
.replace(/W[◦•]?(?:W|o|r|k|i|n|g|\d|•|◦)+/g, " ")
|
|
97
|
+
.replace(/\s{2,}/g, " ")
|
|
98
|
+
.trim();
|
|
99
|
+
}
|
|
100
|
+
function extractSentenceCandidates(line) {
|
|
101
|
+
const normalized = stripInlineCodexNoise(line);
|
|
102
|
+
if (!normalized)
|
|
103
|
+
return [];
|
|
104
|
+
const matches = normalized.match(/[^.!?。!?\n]+[.!?。!?]/g);
|
|
105
|
+
if (matches?.length) {
|
|
106
|
+
return [matches.map((item) => item.trim()).filter(Boolean).join(" ").trim()].filter(Boolean);
|
|
107
|
+
}
|
|
108
|
+
return [normalized];
|
|
109
|
+
}
|
|
110
|
+
export function isCodexWorker(agentType) {
|
|
111
|
+
return agentType === "codex";
|
|
112
|
+
}
|
|
113
|
+
export function codexNeedsTrustConfirmation(bufferedOutput) {
|
|
114
|
+
const cleaned = normalizeWhitespace(bufferedOutput);
|
|
115
|
+
if (!cleaned)
|
|
116
|
+
return false;
|
|
117
|
+
return /do you trust the contents of this directory\?/i.test(cleaned)
|
|
118
|
+
|| /press enter to continue/i.test(cleaned);
|
|
119
|
+
}
|
|
120
|
+
export function isCodexReadyForTask(bufferedOutput) {
|
|
121
|
+
const cleaned = normalizeWhitespace(bufferedOutput);
|
|
122
|
+
if (!cleaned)
|
|
123
|
+
return false;
|
|
124
|
+
return /openai codex/i.test(cleaned)
|
|
125
|
+
|| /find and fix a bug in @filename/i.test(cleaned)
|
|
126
|
+
|| /gpt-[\w.-]+.+% left/i.test(cleaned);
|
|
127
|
+
}
|
|
128
|
+
export function formatCodexTurnInput(message) {
|
|
129
|
+
if (!message)
|
|
130
|
+
return "\r";
|
|
131
|
+
if (message.endsWith("\r") || message.endsWith("\n"))
|
|
132
|
+
return message;
|
|
133
|
+
return `${message}\r`;
|
|
134
|
+
}
|
|
135
|
+
export function summarizeCodexWorkerOutput(bufferedOutput, input = "") {
|
|
136
|
+
const cleaned = normalizeWhitespace(bufferedOutput);
|
|
137
|
+
if (!cleaned)
|
|
138
|
+
return "";
|
|
139
|
+
const lines = removeInputEcho(cleaned
|
|
140
|
+
.split("\n")
|
|
141
|
+
.flatMap((line) => extractSentenceCandidates(cleanCodexLine(line)))
|
|
142
|
+
.filter((line) => !isCodexNoiseLine(line)), input);
|
|
143
|
+
if (!lines.length)
|
|
144
|
+
return "";
|
|
145
|
+
return Array.from(new Set(lines)).slice(-4).join("\n").slice(-600);
|
|
146
|
+
}
|
|
147
|
+
export function getLastCodexWorkerMessage(bufferedOutput, input = "") {
|
|
148
|
+
const summary = summarizeCodexWorkerOutput(bufferedOutput, input);
|
|
149
|
+
if (!summary)
|
|
150
|
+
return "";
|
|
151
|
+
const lines = summary.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
152
|
+
return lines[lines.length - 1] || "";
|
|
153
|
+
}
|
|
154
|
+
export function getFreshCodexTurnResult(beforeOutput, afterOutput, input) {
|
|
155
|
+
const delta = afterOutput.startsWith(beforeOutput) ? afterOutput.slice(beforeOutput.length) : afterOutput;
|
|
156
|
+
const deltaSummary = summarizeCodexWorkerOutput(delta, input);
|
|
157
|
+
if (deltaSummary) {
|
|
158
|
+
const deltaLines = deltaSummary.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
159
|
+
return {
|
|
160
|
+
message: deltaLines[deltaLines.length - 1] || "",
|
|
161
|
+
summary: deltaSummary,
|
|
162
|
+
changed: true,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const wholeSummary = summarizeCodexWorkerOutput(afterOutput, input);
|
|
166
|
+
if (!wholeSummary) {
|
|
167
|
+
return { message: "", summary: "", changed: false };
|
|
168
|
+
}
|
|
169
|
+
const beforeSummary = summarizeCodexWorkerOutput(beforeOutput, input);
|
|
170
|
+
if (wholeSummary === beforeSummary) {
|
|
171
|
+
return { message: "", summary: wholeSummary, changed: false };
|
|
172
|
+
}
|
|
173
|
+
const wholeLines = wholeSummary.split("\n").map((line) => line.trim()).filter(Boolean);
|
|
174
|
+
return {
|
|
175
|
+
message: wholeLines[wholeLines.length - 1] || "",
|
|
176
|
+
summary: wholeSummary,
|
|
177
|
+
changed: true,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { access, readdir } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export async function buildDirectoryList(requestedPath) {
|
|
5
|
+
const normalizedPath = path.resolve(requestedPath || os.homedir());
|
|
6
|
+
try {
|
|
7
|
+
const entries = await readdir(normalizedPath, { withFileTypes: true });
|
|
8
|
+
const directories = entries
|
|
9
|
+
.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
|
|
10
|
+
.map((entry) => ({
|
|
11
|
+
name: entry.name,
|
|
12
|
+
path: path.join(normalizedPath, entry.name),
|
|
13
|
+
}))
|
|
14
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
15
|
+
const parentPath = path.dirname(normalizedPath);
|
|
16
|
+
const repoRoot = await findRepoRoot(normalizedPath);
|
|
17
|
+
return {
|
|
18
|
+
type: "dir_list",
|
|
19
|
+
path: normalizedPath,
|
|
20
|
+
parentPath: parentPath === normalizedPath ? null : parentPath,
|
|
21
|
+
repoRoot,
|
|
22
|
+
entries: directories,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
return {
|
|
27
|
+
type: "error",
|
|
28
|
+
error: err instanceof Error ? err.message : String(err),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function findRepoRoot(startPath) {
|
|
33
|
+
let currentPath = startPath;
|
|
34
|
+
while (true) {
|
|
35
|
+
try {
|
|
36
|
+
await access(path.join(currentPath, ".git"));
|
|
37
|
+
return currentPath;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// keep walking up
|
|
41
|
+
}
|
|
42
|
+
const parentPath = path.dirname(currentPath);
|
|
43
|
+
if (parentPath === currentPath) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
currentPath = parentPath;
|
|
47
|
+
}
|
|
48
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ClientMessage, ServerMessage } from "@cydm/magic-shell-protocol";
|
|
2
|
+
export interface LocalDirectServerOptions {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
nodeId: string;
|
|
6
|
+
password: string;
|
|
7
|
+
}
|
|
8
|
+
export interface LocalDirectServerDelegate {
|
|
9
|
+
handleIncomingMessage(message: ClientMessage | ServerMessage): Promise<void>;
|
|
10
|
+
handleSubscriptionChange(sessionId: string, attachedBrowserCount: number): Promise<void> | void;
|
|
11
|
+
}
|
|
12
|
+
export declare class LocalDirectServer {
|
|
13
|
+
private readonly options;
|
|
14
|
+
private readonly delegate;
|
|
15
|
+
private httpServer;
|
|
16
|
+
private wsServer;
|
|
17
|
+
private connections;
|
|
18
|
+
private sessionToConnections;
|
|
19
|
+
private workbenchRoot;
|
|
20
|
+
constructor(options: LocalDirectServerOptions, delegate: LocalDirectServerDelegate);
|
|
21
|
+
start(): Promise<{
|
|
22
|
+
host: string;
|
|
23
|
+
port: number;
|
|
24
|
+
webUrl: string;
|
|
25
|
+
wsUrl: string;
|
|
26
|
+
}>;
|
|
27
|
+
stop(): Promise<void>;
|
|
28
|
+
handleNodeMessage(message: ServerMessage): void;
|
|
29
|
+
private handleHttpRequest;
|
|
30
|
+
private handleSocketConnection;
|
|
31
|
+
private handleBrowserMessage;
|
|
32
|
+
private attachConnectionToSession;
|
|
33
|
+
private detachConnectionFromSession;
|
|
34
|
+
private cleanupConnection;
|
|
35
|
+
private sendToSession;
|
|
36
|
+
private broadcast;
|
|
37
|
+
private send;
|
|
38
|
+
}
|