@dyyz1993/create-agent 2.0.1 → 2.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/package.json +1 -1
- package/src/commands/create.ts +31 -31
- package/src/commands/workspace.ts +112 -105
- package/src/lib/copy.ts +1 -0
- package/templates/agent/electron/main.js +46 -0
- package/templates/agent/electron/preload.js +5 -0
- package/templates/agent/electron-builder.json +40 -0
- package/templates/agent/eslint.config.mjs +2 -0
- package/templates/agent/package.json +60 -2
- package/templates/agent/src/mainview/App.tsx +29 -26
- package/templates/agent/src/mainview/components/chat/ChatPanel.tsx +88 -88
- package/templates/agent/src/mainview/components/file-preview/VirtualizedCodeView.tsx +105 -81
- package/templates/agent/src/mainview/components/search/SearchPanel.tsx +427 -378
- package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +3 -3
- package/templates/agent/src/mainview/hooks/use-input-history.ts +70 -61
- package/templates/agent/src/mainview/lib/api-client.ts +1 -4
- package/templates/agent/src/mainview/main.tsx +4 -10
- package/templates/agent/src/mainview/stores/use-feed-store.ts +107 -107
- package/templates/agent/src/mainview/utils/drop-handler.ts +114 -115
- package/templates/agent/src/server-config.ts +1 -1
- package/templates/agent/src/server.ts +1 -2
- package/templates/agent/src/shared/handlers/chat.ts +5 -5
- package/templates/agent/src/shared/handlers/debug.ts +5 -1
- package/templates/agent/src/shared/handlers/git.ts +286 -243
- package/templates/agent/src/shared/http-routes.ts +1 -1
- package/templates/agent/src/shared/lib/bash-security.ts +43 -43
- package/templates/agent/tsconfig.ipc.json +5 -1
- package/templates/agent/tsconfig.json +3 -1
- package/templates/chat/package.json +3 -0
- package/templates/chat/src/mainview/hooks/use-input-history.ts +70 -61
- package/templates/chat/src/mainview/lib/api-client.ts +2 -5
- package/templates/chat/src/mainview/main.tsx +10 -7
- package/templates/chat/src/server-config.ts +1 -1
- package/templates/chat/src/server.ts +1 -2
- package/templates/chat/src/shared/handlers/chat.ts +5 -5
- package/templates/chat/src/shared/handlers/debug.ts +5 -1
- package/templates/chat/src/shared/http-routes.ts +1 -1
- package/templates/chat/tsconfig.ipc.json +5 -1
- package/templates/chat/tsconfig.json +12 -2
- package/templates/general/package.json +3 -0
- package/templates/general/src/mainview/components/file-preview/VirtualizedCodeView.tsx +101 -81
- package/templates/general/src/mainview/components/search/SearchPanel.tsx +429 -378
- package/templates/general/src/mainview/hooks/use-input-history.ts +70 -61
- package/templates/general/src/mainview/lib/api-client.ts +2 -5
- package/templates/general/src/mainview/main.tsx +10 -7
- package/templates/general/src/mainview/stores/use-feed-store.ts +107 -107
- package/templates/general/src/mainview/utils/drop-handler.ts +114 -115
- package/templates/general/src/server-config.ts +1 -1
- package/templates/general/src/server.ts +1 -2
- package/templates/general/src/shared/handlers/chat.ts +5 -5
- package/templates/general/src/shared/handlers/debug.ts +5 -1
- package/templates/general/src/shared/handlers/git.ts +286 -243
- package/templates/general/src/shared/http-routes.ts +1 -1
- package/templates/general/tsconfig.ipc.json +5 -1
- package/templates/general/tsconfig.json +12 -2
- package/templates/shared/components/ErrorBoundary.tsx +50 -49
- package/templates/shared/http-routes.ts +210 -190
|
@@ -1,64 +1,64 @@
|
|
|
1
1
|
export interface CommandPolicy {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
enabled: boolean;
|
|
3
|
+
blockedPatterns: RegExp[];
|
|
4
|
+
allowedCommands: string[] | null;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
let policy: CommandPolicy = {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
enabled: true,
|
|
9
|
+
blockedPatterns: [
|
|
10
|
+
/rm\s+-rf\s+(.*\s)?\/($|\s)/,
|
|
11
|
+
/rm\s+-rf\s+--no-preserve-root/,
|
|
12
|
+
/mkfs/,
|
|
13
|
+
/dd\s+if=/,
|
|
14
|
+
/>\s*\/dev\//,
|
|
15
|
+
/:()\s*\{.*\|.*&\s*\}/,
|
|
16
|
+
/shutdown/,
|
|
17
|
+
/reboot/,
|
|
18
|
+
],
|
|
19
|
+
allowedCommands: null,
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
export function setCommandPolicy(p: CommandPolicy): void {
|
|
23
|
-
|
|
23
|
+
policy = p;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export function isCommandAllowed(command: string): boolean {
|
|
27
|
-
|
|
27
|
+
if (!policy.enabled) return false;
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const trimmed = command.trim();
|
|
30
|
+
if (!trimmed) return false;
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
for (const pattern of policy.blockedPatterns) {
|
|
33
|
+
if (pattern.test(trimmed)) return false;
|
|
34
|
+
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
if (policy.allowedCommands) {
|
|
37
|
+
const baseCommand = trimmed.split(/\s+/)[0]!;
|
|
38
|
+
return policy.allowedCommands.some(
|
|
39
|
+
(allowed) => baseCommand === allowed || baseCommand.startsWith(allowed)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
return true;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
export function validateCommand(command: string): string {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
if (!policy.enabled) {
|
|
48
|
+
throw new Error("Bash execution is disabled");
|
|
49
|
+
}
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
const trimmed = command.trim();
|
|
52
|
+
if (!trimmed) {
|
|
53
|
+
throw new Error("Command cannot be empty");
|
|
54
|
+
}
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
56
|
+
if (!isCommandAllowed(trimmed)) {
|
|
57
|
+
if (policy.allowedCommands) {
|
|
58
|
+
throw new Error(`Command not in whitelist: "${trimmed}"`);
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Command blocked for safety: "${trimmed}"`);
|
|
61
|
+
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
return trimmed;
|
|
64
64
|
}
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"noUnusedLocals": false,
|
|
5
5
|
"noUnusedParameters": false,
|
|
6
|
-
"skipLibCheck": true
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"noImplicitReturns": true,
|
|
9
|
+
"noFallthroughCasesInSwitch": true,
|
|
10
|
+
"strict": true
|
|
7
11
|
},
|
|
8
12
|
"include": ["src/bun", "src/gateway/ipc-transport.ts", "src/shared"],
|
|
9
13
|
"exclude": ["node_modules", "dist", "build", "**/__tests__/**", "**/*.test.ts", "**/*.test.tsx"]
|
|
@@ -4,81 +4,90 @@ const HISTORY_KEY = "pi-input-history";
|
|
|
4
4
|
const MAX_ITEMS = 10;
|
|
5
5
|
|
|
6
6
|
function getStorageKey(sessionId: string): string {
|
|
7
|
-
|
|
7
|
+
return `${HISTORY_KEY}:${sessionId}`;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
function readHistory(sessionId: string): string[] {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
try {
|
|
12
|
+
const raw = localStorage.getItem(getStorageKey(sessionId));
|
|
13
|
+
if (!raw) return [];
|
|
14
|
+
const parsed = JSON.parse(raw);
|
|
15
|
+
if (Array.isArray(parsed)) return parsed.slice(0, MAX_ITEMS);
|
|
16
|
+
} catch {
|
|
17
|
+
/* ignore */
|
|
18
|
+
}
|
|
19
|
+
return [];
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
function writeHistory(sessionId: string, items: string[]) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem(getStorageKey(sessionId), JSON.stringify(items.slice(0, MAX_ITEMS)));
|
|
25
|
+
} catch {
|
|
26
|
+
/* ignore */
|
|
27
|
+
}
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export function useInputHistory(sessionId: string) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
const historyRef = useRef<string[]>(readHistory(sessionId));
|
|
32
|
+
const indexRef = useRef(-1);
|
|
33
|
+
const [, forceUpdate] = useState(0);
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
const hasPrev = historyRef.current.length > 0 && indexRef.current < historyRef.current.length - 1;
|
|
36
|
+
const hasNext = indexRef.current > 0;
|
|
33
37
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
38
|
+
const saveToHistory = useCallback(
|
|
39
|
+
(text: string) => {
|
|
40
|
+
const trimmed = text.trim();
|
|
41
|
+
if (!trimmed) return;
|
|
42
|
+
const h = historyRef.current;
|
|
43
|
+
const filtered = h.filter((item) => item !== trimmed);
|
|
44
|
+
const updated = [trimmed, ...filtered].slice(0, MAX_ITEMS);
|
|
45
|
+
historyRef.current = updated;
|
|
46
|
+
writeHistory(sessionId, updated);
|
|
47
|
+
indexRef.current = -1;
|
|
48
|
+
forceUpdate((n) => n + 1);
|
|
49
|
+
},
|
|
50
|
+
[sessionId]
|
|
51
|
+
);
|
|
45
52
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
const navigatePrev = useCallback((): string | null => {
|
|
54
|
+
const h = historyRef.current;
|
|
55
|
+
if (h.length === 0) return null;
|
|
56
|
+
const nextIdx = Math.min(indexRef.current + 1, h.length - 1);
|
|
57
|
+
indexRef.current = nextIdx;
|
|
58
|
+
forceUpdate((n) => n + 1);
|
|
59
|
+
return h[nextIdx] ?? null;
|
|
60
|
+
}, []);
|
|
54
61
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const navigateNext = useCallback((): string | null => {
|
|
63
|
+
const h = historyRef.current;
|
|
64
|
+
if (h.length === 0) return null;
|
|
65
|
+
const nextIdx = indexRef.current - 1;
|
|
66
|
+
if (nextIdx < 0) {
|
|
67
|
+
indexRef.current = -1;
|
|
68
|
+
forceUpdate((n) => n + 1);
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
indexRef.current = nextIdx;
|
|
72
|
+
forceUpdate((n) => n + 1);
|
|
73
|
+
return h[nextIdx] ?? null;
|
|
74
|
+
}, []);
|
|
68
75
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
const clearHistory = useCallback(() => {
|
|
77
|
+
historyRef.current = [];
|
|
78
|
+
indexRef.current = -1;
|
|
79
|
+
try {
|
|
80
|
+
localStorage.removeItem(getStorageKey(sessionId));
|
|
81
|
+
} catch {
|
|
82
|
+
/* ignore */
|
|
83
|
+
}
|
|
84
|
+
forceUpdate((n) => n + 1);
|
|
85
|
+
}, [sessionId]);
|
|
77
86
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
87
|
+
const resetIndex = useCallback(() => {
|
|
88
|
+
indexRef.current = -1;
|
|
89
|
+
forceUpdate((n) => n + 1);
|
|
90
|
+
}, []);
|
|
82
91
|
|
|
83
|
-
|
|
92
|
+
return { saveToHistory, navigatePrev, navigateNext, clearHistory, resetIndex, hasPrev, hasNext };
|
|
84
93
|
}
|
|
@@ -42,12 +42,9 @@ class APIClientImpl {
|
|
|
42
42
|
|
|
43
43
|
const ipcTransport = new IPCTransport();
|
|
44
44
|
this._transport = "ipc";
|
|
45
|
-
this._baseUrl = null;
|
|
45
|
+
this._baseUrl = null;
|
|
46
46
|
this.client = createTypedClient<RPCMethods, RPCEvents>(ipcTransport);
|
|
47
47
|
this.setupElectrobunBridge(ipcTransport);
|
|
48
|
-
if (import.meta.env.DEV) {
|
|
49
|
-
console.warn("[APIClient] Desktop (IPC) initialized synchronously");
|
|
50
|
-
}
|
|
51
48
|
}
|
|
52
49
|
|
|
53
50
|
/**
|
|
@@ -82,7 +79,7 @@ class APIClientImpl {
|
|
|
82
79
|
url: wsUrl,
|
|
83
80
|
authToken: AUTH_TOKEN,
|
|
84
81
|
reconnect: true,
|
|
85
|
-
});
|
|
82
|
+
} as never);
|
|
86
83
|
await this.wsTransport.connect();
|
|
87
84
|
this.client = createTypedClient<RPCMethods, RPCEvents>(this.wsTransport);
|
|
88
85
|
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
import "./lib/i18n";
|
|
2
2
|
import { StrictMode } from "react";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
3
4
|
import { createRoot } from "react-dom/client";
|
|
4
5
|
import { apiClient } from "./lib/api-client";
|
|
5
|
-
import { ErrorBoundary } from "@shared/components/ErrorBoundary";
|
|
6
|
+
import { ErrorBoundary as _EB } from "@shared/components/ErrorBoundary";
|
|
6
7
|
import "./index.css";
|
|
7
8
|
import App from "./App";
|
|
8
9
|
|
|
10
|
+
const ErrorBoundary = _EB as unknown as React.FC<{ children: ReactNode }>;
|
|
11
|
+
|
|
9
12
|
const isElectrobun = typeof window !== "undefined" && !!window.__electrobunBunBridge;
|
|
10
13
|
|
|
11
14
|
if (isElectrobun) {
|
|
12
|
-
|
|
15
|
+
apiClient.initSyncForDesktop();
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
createRoot(document.getElementById("root")!).render(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
<StrictMode>
|
|
20
|
+
<ErrorBoundary>
|
|
21
|
+
<App />
|
|
22
|
+
</ErrorBoundary>
|
|
23
|
+
</StrictMode>
|
|
21
24
|
);
|
|
@@ -22,7 +22,7 @@ export function parseEnvInt(
|
|
|
22
22
|
if (value === undefined || value === "") return defaultValue;
|
|
23
23
|
const parsed = parseInt(value, 10);
|
|
24
24
|
if (isNaN(parsed) || parsed < min || parsed > max) {
|
|
25
|
-
|
|
25
|
+
process.stderr.write(`[config] Invalid ${key}: "${value}", using default: ${defaultValue}\n`);
|
|
26
26
|
return defaultValue;
|
|
27
27
|
}
|
|
28
28
|
return parsed;
|
|
@@ -62,8 +62,7 @@ async function start() {
|
|
|
62
62
|
log.info(`WebSocket: ws://localhost:${port}/ws (auth required)`);
|
|
63
63
|
log.info(`Available RPC methods: ${discoverMethodNames().join(", ")}`);
|
|
64
64
|
log.info("File endpoints: GET /file/{path}, GET /info/{path}");
|
|
65
|
-
|
|
66
|
-
console.log("\n" + formatRegistryForOutput() + "\n");
|
|
65
|
+
log.info(formatRegistryForOutput());
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
start().catch((err) => {
|
|
@@ -69,7 +69,7 @@ export function generateReply(input: string): string {
|
|
|
69
69
|
"Hello! Great to see you. What would you like to know?",
|
|
70
70
|
"Hi! I'm your desktop assistant. Ask me anything!",
|
|
71
71
|
];
|
|
72
|
-
return greetings[Math.floor(Math.random() * greetings.length)]
|
|
72
|
+
return greetings[Math.floor(Math.random() * greetings.length)]!;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
if (
|
|
@@ -90,9 +90,9 @@ export function generateReply(input: string): string {
|
|
|
90
90
|
/(?:what(?:'s| is)\s+)?(\d+(?:\.\d+)?)\s*([+\-*/x×÷^])\s*(\d+(?:\.\d+)?)/
|
|
91
91
|
);
|
|
92
92
|
if (mathMatch) {
|
|
93
|
-
const a = parseFloat(mathMatch[1]);
|
|
94
|
-
const op = mathMatch[2]
|
|
95
|
-
const b = parseFloat(mathMatch[3]);
|
|
93
|
+
const a = parseFloat(mathMatch[1]!);
|
|
94
|
+
const op = mathMatch[2]!;
|
|
95
|
+
const b = parseFloat(mathMatch[3]!);
|
|
96
96
|
let result: number;
|
|
97
97
|
switch (op) {
|
|
98
98
|
case "+":
|
|
@@ -166,7 +166,7 @@ export function generateReply(input: string): string {
|
|
|
166
166
|
"I wish I could help with that! For now I can answer questions about time, math, files, and git. Type **help** to see what I can do.",
|
|
167
167
|
"Hmm, I'm not sure about that one. But I *can* do math, tell you the time, and talk about files and git. Give it a shot!",
|
|
168
168
|
];
|
|
169
|
-
return defaults[Math.floor(Math.random() * defaults.length)]
|
|
169
|
+
return defaults[Math.floor(Math.random() * defaults.length)]!;
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
export function register(server: RPCServer, options: HandlerOptions): void {
|
|
@@ -3,6 +3,10 @@ import type { HandlerOptions } from "../rpc-schema";
|
|
|
3
3
|
|
|
4
4
|
export function register(server: RPCServer, _options: HandlerOptions): void {
|
|
5
5
|
server.register("debug.subscriptions", async () => {
|
|
6
|
-
return {
|
|
6
|
+
return {
|
|
7
|
+
subscriptions: (
|
|
8
|
+
server as unknown as { getActiveSubscriptions(): unknown }
|
|
9
|
+
).getActiveSubscriptions(),
|
|
10
|
+
};
|
|
7
11
|
});
|
|
8
12
|
}
|
|
@@ -178,7 +178,7 @@ async function handleFileContent(
|
|
|
178
178
|
const range = req.headers["range"];
|
|
179
179
|
if (range) {
|
|
180
180
|
const parts = range.replace(/bytes=/, "").split("-");
|
|
181
|
-
const start = parseInt(parts[0]
|
|
181
|
+
const start = parseInt(parts[0]!, 10);
|
|
182
182
|
const end = parts[1] ? parseInt(parts[1], 10) : s.size - 1;
|
|
183
183
|
const chunkSize = end - start + 1;
|
|
184
184
|
|
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
"compilerOptions": {
|
|
4
4
|
"noUnusedLocals": false,
|
|
5
5
|
"noUnusedParameters": false,
|
|
6
|
-
"skipLibCheck": true
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"noUncheckedIndexedAccess": true,
|
|
8
|
+
"noImplicitReturns": true,
|
|
9
|
+
"noFallthroughCasesInSwitch": true,
|
|
10
|
+
"strict": true
|
|
7
11
|
},
|
|
8
12
|
"include": ["src/bun", "src/gateway/ipc-transport.ts", "src/shared"],
|
|
9
13
|
"exclude": ["node_modules", "dist", "build", "**/__tests__/**", "**/*.test.ts", "**/*.test.tsx"]
|
|
@@ -19,8 +19,18 @@
|
|
|
19
19
|
"paths": {
|
|
20
20
|
"@dyyz1993/rpc-core": ["../packages/rpc-core/src/index.ts"],
|
|
21
21
|
"@shared/*": ["../shared/*"]
|
|
22
|
-
}
|
|
22
|
+
},
|
|
23
|
+
"noUncheckedIndexedAccess": true,
|
|
24
|
+
"noImplicitReturns": true
|
|
23
25
|
},
|
|
24
26
|
"include": ["src", "../shared"],
|
|
25
|
-
"exclude": [
|
|
27
|
+
"exclude": [
|
|
28
|
+
"node_modules",
|
|
29
|
+
"dist",
|
|
30
|
+
"build",
|
|
31
|
+
"../../package/dist",
|
|
32
|
+
"**/__tests__/**",
|
|
33
|
+
"**/*.test.ts",
|
|
34
|
+
"**/*.test.tsx"
|
|
35
|
+
]
|
|
26
36
|
}
|