@takimcizgisi/ajan 0.2.5 → 0.3.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.

Potentially problematic release.


This version of @takimcizgisi/ajan might be problematic. Click here for more details.

Files changed (71) hide show
  1. package/LICENSE +227 -674
  2. package/README.md +243 -305
  3. package/ajan-logo.txt +2 -0
  4. package/dist/config/configManager.js +122 -19
  5. package/dist/config/types.d.ts +37 -0
  6. package/dist/core/agent.d.ts +1 -1
  7. package/dist/core/agent.js +25 -4
  8. package/dist/discord/presence.d.ts +65 -0
  9. package/dist/discord/presence.js +401 -0
  10. package/dist/engine/llama.d.ts +5 -0
  11. package/dist/engine/llama.js +19 -5
  12. package/dist/engine/modelManager.d.ts +0 -1
  13. package/dist/engine/modelManager.js +0 -8
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +1 -0
  16. package/dist/service.js +0 -1
  17. package/dist/tools/playground.d.ts +4 -0
  18. package/dist/tools/playground.js +125 -0
  19. package/dist/tools/registry.d.ts +0 -1
  20. package/dist/tools/registry.js +5 -4
  21. package/dist/tools/taskComplete.js +16 -4
  22. package/dist/tools/terminal.js +12 -12
  23. package/dist/utils/paths.d.ts +1 -0
  24. package/dist/utils/paths.js +29 -11
  25. package/package.json +78 -106
  26. package/scripts/build-vscode.mjs +28 -0
  27. package/scripts/ensure-tgz.mjs +27 -0
  28. package/scripts/install-vscode.mjs +236 -0
  29. package/scripts/release.mjs +19 -0
  30. package/scripts/smoke-new-tools.ts +79 -0
  31. package/scripts/smoke-tools.ts +201 -0
  32. package/src/vscode/LICENSE +674 -0
  33. package/src/vscode/README.md +54 -0
  34. package/src/vscode/media/assets/Anton.ttf +0 -0
  35. package/src/vscode/media/chat.js +634 -0
  36. package/src/vscode/media/style.css +767 -0
  37. package/src/vscode/out/extension.js +598 -0
  38. package/src/vscode/package.json +243 -0
  39. package/dist/cli/index.d.ts +0 -2
  40. package/dist/cli/index.js +0 -741
  41. package/dist/cli/tui.d.ts +0 -145
  42. package/dist/cli/tui.js +0 -1003
  43. package/dist/electron/main.d.ts +0 -1
  44. package/dist/electron/main.js +0 -364
  45. package/dist/electron/preload.d.ts +0 -1
  46. package/dist/electron/preload.js +0 -67
  47. package/dist/utils/clipboard.d.ts +0 -2
  48. package/dist/utils/clipboard.js +0 -40
  49. package/dist/utils/lock.d.ts +0 -12
  50. package/dist/utils/lock.js +0 -68
  51. package/dist/utils/sacGuard.d.ts +0 -4
  52. package/dist/utils/sacGuard.js +0 -47
  53. package/dist/utils/spinner.d.ts +0 -10
  54. package/dist/utils/spinner.js +0 -32
  55. package/src/gui/assets/image/logo.svg +0 -1
  56. package/src/gui/assets/image/opencode.svg +0 -18
  57. package/src/gui/css/style.css +0 -430
  58. package/src/gui/html/chat.html +0 -34
  59. package/src/gui/html/downloads.html +0 -11
  60. package/src/gui/html/hakkinda.html +0 -26
  61. package/src/gui/html/index.html +0 -82
  62. package/src/gui/html/projects.html +0 -12
  63. package/src/gui/html/settings.html +0 -4
  64. package/src/gui/js/app.js +0 -197
  65. package/src/gui/js/chat.js +0 -434
  66. package/src/gui/js/downloads.js +0 -172
  67. package/src/gui/js/hakkinda.js +0 -16
  68. package/src/gui/js/projects.js +0 -113
  69. package/src/gui/js/settings.js +0 -177
  70. /package/src/{gui/assets/image → vscode/media/assets}/AJAN_LOGO.svg +0 -0
  71. /package/src/{gui/assets/image → vscode/media/assets}/ajan.png +0 -0
@@ -0,0 +1,401 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Client } from "@xhayper/discord-rpc";
3
+ import { logger } from "../utils/paths.js";
4
+ const PHASE_TIME_KEY = {
5
+ idle: 0,
6
+ chat: 0,
7
+ thinking: 0,
8
+ loading: 0,
9
+ error: 0
10
+ };
11
+ /**
12
+ * AJAN etkinliklerini Discord Rich Presence'a (profildeki "Oynuyor" alanı)
13
+ * dönüştürür. Discord masaüstü uygulaması çalışıyorken IPC üzerinden bağlanır.
14
+ *
15
+ * Kullanım: config.json içinde `discord.enabled = true` ve `discord.clientId`
16
+ * ayarlanmalıdır. Client ID, https://discord.com/developers/applications
17
+ * adresinden alınır; görseller Rich Presence > Art Assets sekmesine yüklenir.
18
+ */
19
+ export class DiscordPresence {
20
+ client = null;
21
+ config;
22
+ phaseTime = { ...PHASE_TIME_KEY };
23
+ sessionStartedAt = null;
24
+ sessionTitle = "";
25
+ currentTool = null;
26
+ currentStep = 0;
27
+ maxSteps = 0;
28
+ lastError = null;
29
+ thinkingText = "";
30
+ lastToolCallAt = 0;
31
+ loadingPercent = null;
32
+ partyId = null;
33
+ joinSecret = null;
34
+ spectateSecret = null;
35
+ connected = false;
36
+ disposed = false;
37
+ reconnectTimer = null;
38
+ heartbeat = null;
39
+ reconnectAttempt = 0;
40
+ refreshTimer = null;
41
+ lastErrorLogAt = 0;
42
+ lastErrorLogMsg = "";
43
+ constructor(config) {
44
+ this.config = {
45
+ enabled: config.enabled ?? false,
46
+ clientId: config.clientId ?? "",
47
+ transport: config.transport ?? "ipc",
48
+ showToolCalls: config.showToolCalls ?? true,
49
+ showThinking: config.showThinking ?? false,
50
+ largeImageKey: config.largeImageKey ?? "ajan-logo",
51
+ largeImageText: config.largeImageText ?? "AJAN AI",
52
+ smallImageKeys: config.smallImageKeys ?? {}
53
+ };
54
+ this.markPhase("idle");
55
+ }
56
+ get isEnabled() {
57
+ return this.config.enabled === true && !!this.config.clientId;
58
+ }
59
+ get isConnected() {
60
+ return this.connected;
61
+ }
62
+ /** Discord IPC'ye bağlanır; başarısız olursa sessizce devam eder. */
63
+ async connect() {
64
+ if (!this.isEnabled || this.disposed)
65
+ return false;
66
+ if (this.connected)
67
+ return true;
68
+ try {
69
+ this.client = new Client({
70
+ clientId: this.config.clientId,
71
+ transport: { type: this.config.transport ?? "websocket" }
72
+ });
73
+ this.client.on("connected", () => {
74
+ this.connected = true;
75
+ logger.info("Discord Rich Presence bağlandı.");
76
+ });
77
+ this.client.on("ready", () => {
78
+ this.connected = true;
79
+ void this.scheduleRefresh();
80
+ });
81
+ this.client.on("disconnected", () => {
82
+ this.connected = false;
83
+ logger.debug("Discord Rich Presence bağlantısı koptu.");
84
+ void this.reconnect();
85
+ });
86
+ await this.client.connect();
87
+ // Kullanıcı oturumu (RPC HELLO/READY) hazır olana kadar bekle; ancak
88
+ // o zaman `client.user` set olur ve aktivite gönderilebilir.
89
+ // 5 sn içinde READY gelmezse askıda kalmadan devam et.
90
+ if (!this.client.user) {
91
+ await Promise.race([
92
+ new Promise((resolve) => this.client.once("ready", () => resolve())),
93
+ new Promise((resolve) => setTimeout(resolve, 5000))
94
+ ]);
95
+ }
96
+ this.connected = true;
97
+ await this.flushRefresh();
98
+ this.startHeartbeat();
99
+ return this.connected;
100
+ }
101
+ catch (err) {
102
+ this.connected = false;
103
+ logger.debug(`Discord Rich Presence bağlanamadı: ${err.message}`);
104
+ void this.reconnect();
105
+ return false;
106
+ }
107
+ }
108
+ async disconnect() {
109
+ this.disposed = true;
110
+ this.stopTimers();
111
+ if (this.client) {
112
+ try {
113
+ await this.client.destroy();
114
+ }
115
+ catch {
116
+ /* yoksay */
117
+ }
118
+ this.client = null;
119
+ }
120
+ this.connected = false;
121
+ }
122
+ /** Bağlantı koptuğunda üstel gecikmeyle yeniden bağlanmayı dener. */
123
+ async reconnect() {
124
+ if (this.disposed)
125
+ return;
126
+ if (this.reconnectTimer)
127
+ clearTimeout(this.reconnectTimer);
128
+ const delay = Math.min(30_000, 2000 * 2 ** this.reconnectAttempt);
129
+ this.reconnectTimer = setTimeout(() => {
130
+ void (async () => {
131
+ if (this.disposed)
132
+ return;
133
+ try {
134
+ await this.client?.destroy();
135
+ }
136
+ catch {
137
+ /* yoksay */
138
+ }
139
+ this.client = null;
140
+ this.connected = false;
141
+ const ok = await this.connect();
142
+ if (ok) {
143
+ this.reconnectAttempt = 0;
144
+ }
145
+ else {
146
+ void this.reconnect();
147
+ }
148
+ })();
149
+ }, delay);
150
+ }
151
+ /** Activity'yi Discord ile canlı tutmak için düzenli tazeleme. */
152
+ startHeartbeat() {
153
+ if (this.heartbeat)
154
+ return;
155
+ this.heartbeat = setInterval(() => {
156
+ if (this.connected && this.client?.user && !this.disposed) {
157
+ void this.scheduleRefresh();
158
+ }
159
+ }, 45_000);
160
+ this.heartbeat.unref?.();
161
+ }
162
+ stopTimers() {
163
+ if (this.reconnectTimer) {
164
+ clearTimeout(this.reconnectTimer);
165
+ this.reconnectTimer = null;
166
+ }
167
+ if (this.heartbeat) {
168
+ clearInterval(this.heartbeat);
169
+ this.heartbeat = null;
170
+ }
171
+ if (this.refreshTimer) {
172
+ clearTimeout(this.refreshTimer);
173
+ this.refreshTimer = null;
174
+ }
175
+ }
176
+ // ── Durum güncellemeleri ──
177
+ onSessionStart(title) {
178
+ this.sessionStartedAt = Date.now();
179
+ this.sessionTitle = title.trim() || "Yeni Sohbet";
180
+ this.thinkingText = "";
181
+ this.currentTool = null;
182
+ this.currentStep = 0;
183
+ this.maxSteps = 0;
184
+ this.lastError = null;
185
+ // Oyun daveti/katılımı için her oturuma özel party anahtarları üret.
186
+ this.partyId = randomUUID();
187
+ this.joinSecret = randomUUID();
188
+ this.spectateSecret = randomUUID();
189
+ this.markPhase("chat");
190
+ void this.scheduleRefresh();
191
+ }
192
+ onSessionEnd() {
193
+ this.sessionStartedAt = null;
194
+ this.sessionTitle = "";
195
+ this.thinkingText = "";
196
+ this.currentTool = null;
197
+ this.currentStep = 0;
198
+ this.maxSteps = 0;
199
+ this.lastError = null;
200
+ this.partyId = null;
201
+ this.joinSecret = null;
202
+ this.spectateSecret = null;
203
+ this.markPhase("idle");
204
+ void this.scheduleRefresh();
205
+ }
206
+ onThinking(text) {
207
+ if (!this.config.showThinking || this.disposed)
208
+ return;
209
+ this.thinkingText = text.slice(0, 120);
210
+ this.markPhase("thinking");
211
+ void this.scheduleRefresh();
212
+ }
213
+ onToolCall(name) {
214
+ if (this.disposed)
215
+ return;
216
+ this.currentTool = name;
217
+ if (this.config.showThinking)
218
+ this.thinkingText = "";
219
+ this.markPhase("chat");
220
+ this.lastToolCallAt = Date.now();
221
+ void this.scheduleRefresh();
222
+ }
223
+ onToolResult() {
224
+ if (this.disposed)
225
+ return;
226
+ this.currentTool = null;
227
+ // Araç sonucunda düşünce moduna dönme; bir sonraki tur beklentisi.
228
+ void this.scheduleRefresh();
229
+ }
230
+ onStepStart(step, maxSteps) {
231
+ if (this.disposed)
232
+ return;
233
+ this.currentStep = step;
234
+ this.maxSteps = maxSteps;
235
+ this.markPhase("chat");
236
+ void this.scheduleRefresh();
237
+ }
238
+ onComplete() {
239
+ if (this.disposed)
240
+ return;
241
+ this.currentTool = null;
242
+ this.thinkingText = "";
243
+ this.markPhase("chat");
244
+ void this.scheduleRefresh();
245
+ }
246
+ onError(message) {
247
+ if (this.disposed)
248
+ return;
249
+ this.lastError = message.slice(0, 120);
250
+ this.markPhase("error");
251
+ void this.scheduleRefresh();
252
+ }
253
+ onModelLoadProgress(pct) {
254
+ if (this.disposed)
255
+ return;
256
+ this.loadingPercent = pct;
257
+ this.markPhase("loading");
258
+ void this.scheduleRefresh();
259
+ }
260
+ onModelLoadComplete() {
261
+ if (this.disposed)
262
+ return;
263
+ this.loadingPercent = null;
264
+ this.markPhase("idle");
265
+ void this.scheduleRefresh();
266
+ }
267
+ onTextChunk(text) {
268
+ if (this.disposed || !this.config.showThinking)
269
+ return;
270
+ // Akış halindeki son metni durumda kısa tutarız.
271
+ this.thinkingText = text.replace(/\s+/g, " ").slice(0, 120);
272
+ this.markPhase("chat");
273
+ void this.scheduleRefresh();
274
+ }
275
+ // ── Etkinlik üretimi ──
276
+ markPhase(phase) {
277
+ this.phaseTime[phase] = Date.now();
278
+ }
279
+ buildActivity() {
280
+ const cfg = this.config;
281
+ const small = cfg.smallImageKeys ?? {};
282
+ const base = {
283
+ type: 0, // Playing
284
+ name: "AJAN AI",
285
+ largeImageKey: cfg.largeImageKey,
286
+ largeImageText: cfg.largeImageText ?? "AJAN AI"
287
+ };
288
+ if (this.sessionStartedAt) {
289
+ base.startTimestamp = this.sessionStartedAt;
290
+ }
291
+ // Oyun daveti / katılım arayüzü: oturum aktifken party + join/spectate.
292
+ if (this.partyId && this.joinSecret) {
293
+ base.partyId = this.partyId;
294
+ base.partySize = 1;
295
+ base.partyMax = 4;
296
+ base.joinSecret = this.joinSecret;
297
+ base.spectateSecret = this.spectateSecret ?? undefined;
298
+ }
299
+ // GitHub benzeri buton (config'de url doluysa göster)
300
+ const btn = cfg.buttons ?? {};
301
+ if (btn.url && btn.url.trim()) {
302
+ base.buttons = [
303
+ {
304
+ label: btn.label?.trim() || "GitHub",
305
+ url: btn.url.trim()
306
+ }
307
+ ];
308
+ }
309
+ // Durum metnini hazırla
310
+ let details;
311
+ let state;
312
+ let smallImageKey;
313
+ let smallImageText;
314
+ if (this.loadingPercent != null) {
315
+ details = "Model yükleniyor";
316
+ state = `%${Math.round(this.loadingPercent)}`;
317
+ smallImageKey = small.loading;
318
+ smallImageText = "Model yükleniyor";
319
+ }
320
+ else if (this.lastError) {
321
+ details = "Hata oluştu";
322
+ state = this.lastError;
323
+ smallImageKey = small.error;
324
+ smallImageText = "Hata";
325
+ }
326
+ else if (this.thinkingText && this.config.showThinking) {
327
+ details = "Düşünüyor...";
328
+ state = this.thinkingText;
329
+ smallImageKey = small.task;
330
+ smallImageText = "Düşünüyor";
331
+ }
332
+ else if (this.currentTool) {
333
+ details = "Görev yürütülüyor";
334
+ const showTool = cfg.showToolCalls !== false;
335
+ state = showTool
336
+ ? this.currentStep > 0
337
+ ? `Adım ${this.currentStep}/${this.maxSteps} · ${this.currentTool}`
338
+ : this.currentTool
339
+ : undefined;
340
+ smallImageKey = small.task;
341
+ smallImageText = "Araç kullanılıyor";
342
+ }
343
+ else if (this.sessionTitle) {
344
+ details = "Sohbet modunda";
345
+ state = this.sessionTitle;
346
+ smallImageKey = small.chat;
347
+ smallImageText = "Oturum açık";
348
+ }
349
+ else {
350
+ details = "Oturum bekleniyor";
351
+ state = undefined;
352
+ smallImageKey = small.idle;
353
+ smallImageText = "Hazır";
354
+ }
355
+ if (details)
356
+ base.details = details;
357
+ if (state)
358
+ base.state = state;
359
+ if (smallImageKey)
360
+ base.smallImageKey = smallImageKey;
361
+ if (smallImageText)
362
+ base.smallImageText = smallImageText;
363
+ return base;
364
+ }
365
+ /** Son durum değişikliğini 250ms'de bir tek sefere birleştirir (fırtına önleyici). */
366
+ scheduleRefresh() {
367
+ if (this.disposed)
368
+ return;
369
+ if (this.refreshTimer)
370
+ return;
371
+ this.refreshTimer = setTimeout(() => {
372
+ this.refreshTimer = null;
373
+ void this.flushRefresh();
374
+ }, 250);
375
+ }
376
+ /** Aktiviteyi Discord'a gönderir; hata durumunu (tekrarı kısıtlanmış şekilde) yutar. */
377
+ async flushRefresh() {
378
+ if (this.disposed)
379
+ return;
380
+ if (!this.connected || !this.client?.user)
381
+ return;
382
+ try {
383
+ await this.client.user.setActivity(this.buildActivity());
384
+ }
385
+ catch (err) {
386
+ const msg = err.message;
387
+ const now = Date.now();
388
+ if (now - this.lastErrorLogAt > 5000 || msg !== this.lastErrorLogMsg) {
389
+ this.lastErrorLogAt = now;
390
+ this.lastErrorLogMsg = msg;
391
+ logger.debug(`Discord aktivite güncellenemedi: ${msg}`);
392
+ }
393
+ }
394
+ }
395
+ /** Açık oturum yokken "boşta" durumunu göster. */
396
+ async showIdle() {
397
+ this.markPhase("idle");
398
+ await this.flushRefresh();
399
+ }
400
+ }
401
+ //# sourceMappingURL=presence.js.map
@@ -21,6 +21,11 @@ export declare class LlamaEngine {
21
21
  private readonly onProgress?;
22
22
  private readonly signal?;
23
23
  constructor(options: LlamaEngineOptions);
24
+ /**
25
+ * config.gpu değerini node-llama-cpp'in kabul ettiği arka uca çevirir.
26
+ * Önceden otomotik dışındaki seçimler (cuda/vulkan/metal) sessizce yok sayılıyordu;
27
+ * config'de açıkça seçilen arka uç artık motora iletilir.
28
+ */
24
29
  private get gpuOption();
25
30
  static detectSupportedGpus(): Promise<string[]>;
26
31
  init(): Promise<void>;
@@ -20,12 +20,26 @@ export class LlamaEngine {
20
20
  this.onProgress = options.onProgress;
21
21
  this.signal = options.signal;
22
22
  }
23
+ /**
24
+ * config.gpu değerini node-llama-cpp'in kabul ettiği arka uca çevirir.
25
+ * Önceden otomotik dışındaki seçimler (cuda/vulkan/metal) sessizce yok sayılıyordu;
26
+ * config'de açıkça seçilen arka uç artık motora iletilir.
27
+ */
23
28
  get gpuOption() {
24
- if (!this.gpuBackend)
25
- return undefined;
26
- if (this.gpuBackend === "auto")
27
- return { type: "auto" };
28
- return undefined;
29
+ switch (this.gpuBackend) {
30
+ case "cuda":
31
+ case "cuda-llama":
32
+ return "cuda";
33
+ case "vulkan":
34
+ case "vulkan-llama":
35
+ return "vulkan";
36
+ case "metal":
37
+ return "metal";
38
+ case "auto":
39
+ default:
40
+ // webgpu vb. bu node-llama-cpp sürümünde desteklenmiyor; auto'ya düş.
41
+ return "auto";
42
+ }
29
43
  }
30
44
  static async detectSupportedGpus() {
31
45
  try {
@@ -8,7 +8,6 @@ export type DownloadProgress = {
8
8
  };
9
9
  export type DownloadOptions = {
10
10
  onProgress?: (progress: DownloadProgress) => void;
11
- showProgress?: boolean;
12
11
  signal?: AbortSignal;
13
12
  };
14
13
  export declare function formatBytes(bytes: number): string;
@@ -45,17 +45,9 @@ export async function downloadModel(model, options = {}) {
45
45
  showCliProgress: false,
46
46
  onProgress: (status) => {
47
47
  options.onProgress?.(status);
48
- if (options.showProgress) {
49
- const pct = status.totalSize > 0 ? Math.round((status.downloadedSize / status.totalSize) * 100) : 0;
50
- const speed = `${formatBytes(status.averageSpeed)}/sn`;
51
- const eta = formatTime(status.estimatedTimeLeft);
52
- process.stdout.write(`\r [indirme] %${pct} · ${formatBytes(status.downloadedSize)}/${formatBytes(status.totalSize)} · ${speed} · kalan ${eta} `);
53
- }
54
48
  }
55
49
  });
56
50
  const downloadedPath = await downloader.download({ signal: options.signal });
57
- if (options.showProgress)
58
- process.stdout.write("\n");
59
51
  logger.info(`Model indirildi: ${downloadedPath}`);
60
52
  return downloadedPath;
61
53
  }
package/dist/index.d.ts CHANGED
@@ -7,3 +7,4 @@ export { createCoreTools, buildSessionFunctions } from "./tools/registry.js";
7
7
  export { parseUnifiedDiff } from "./tools/patch.js";
8
8
  export type { AjanConfig, ModelDefinition, RemoteModelsRegistry, SessionConfig, AgentTool, ToolHandler, ToolExecutionContext, ToolExecutionResult, ChatWrapperName, GpuBackend } from "./config/types.js";
9
9
  export { getDataDir, getModelsDir, getConfigPath, getLogDir, setLogLevel, logger } from "./utils/paths.js";
10
+ export { DiscordPresence, type PresencePhase } from "./discord/presence.js";
package/dist/index.js CHANGED
@@ -6,4 +6,5 @@ export { DEFAULT_CONFIG, loadConfig, saveConfig, getModelsRegistry, getModelById
6
6
  export { createCoreTools, buildSessionFunctions } from "./tools/registry.js";
7
7
  export { parseUnifiedDiff } from "./tools/patch.js";
8
8
  export { getDataDir, getModelsDir, getConfigPath, getLogDir, setLogLevel, logger } from "./utils/paths.js";
9
+ export { DiscordPresence } from "./discord/presence.js";
9
10
  //# sourceMappingURL=index.js.map
package/dist/service.js CHANGED
@@ -164,7 +164,6 @@ export class AjanService {
164
164
  try {
165
165
  const { downloadModel } = await import("./engine/modelManager.js");
166
166
  await downloadModel(model, {
167
- showProgress: true,
168
167
  onProgress: (progress) => {
169
168
  const pct = progress.totalSize > 0
170
169
  ? Math.round((progress.downloadedSize / progress.totalSize) * 100)
@@ -0,0 +1,4 @@
1
+ import type { AgentTool } from "../config/types.js";
2
+ export declare const openPlaygroundTool: AgentTool;
3
+ export declare const setTodosTool: AgentTool;
4
+ export declare const getTodosTool: AgentTool;
@@ -0,0 +1,125 @@
1
+ import { mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, rmSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { limitOutput } from "./utils.js";
4
+ /** Kullanıcının açık klasöründe, projenin içindeki AJAN test klasörü: <cwd>/.ajan */
5
+ function playgroundPath(cwd) {
6
+ const dir = join(cwd, ".ajan");
7
+ mkdirSync(dir, { recursive: true });
8
+ return dir;
9
+ }
10
+ function todosPath(cwd) {
11
+ return join(playgroundPath(cwd), "todos.json");
12
+ }
13
+ function loadTodos(cwd) {
14
+ try {
15
+ if (existsSync(todosPath(cwd))) {
16
+ const parsed = JSON.parse(readFileSync(todosPath(cwd), "utf8"));
17
+ if (Array.isArray(parsed))
18
+ return parsed;
19
+ }
20
+ }
21
+ catch {
22
+ /* bozuk dosya: sıfırla */
23
+ }
24
+ return [];
25
+ }
26
+ function clearDir(dir) {
27
+ let count = 0;
28
+ for (const name of readdirSync(dir)) {
29
+ rmSync(join(dir, name), { recursive: true, force: true });
30
+ count++;
31
+ }
32
+ return count;
33
+ }
34
+ export const openPlaygroundTool = {
35
+ name: "open_playground",
36
+ description: "Açık klasörün içindeki AJAN TEST KLASÖRÜNÜ (<cwd>/.ajan) hazırlar. " +
37
+ "Deneme/test/prototip yapmak istediğinde önce bunu çağır. " +
38
+ "reset=false iken masaarkadaşları korur; reset=true ise klasörü bomboş hale getirir. " +
39
+ "Kullanıcının gerçek proje dosyalarına kesinlikle dokunmaz; tüm denemeler bu klasörde yapılır.",
40
+ parameters: {
41
+ type: "object",
42
+ properties: {
43
+ reset: {
44
+ type: "boolean",
45
+ description: "true ise .ajan içeriğini komple temizler (varsayılan: false)"
46
+ },
47
+ note: { type: "string", description: "Ne yapacağın hakkında kısa bir not (opsiyonel)" }
48
+ },
49
+ required: []
50
+ },
51
+ handler: async (params, ctx) => {
52
+ const dir = playgroundPath(ctx.cwd);
53
+ const before = readdirSync(dir).length;
54
+ let cleared = 0;
55
+ if (params.reset)
56
+ cleared = clearDir(dir);
57
+ const after = readdirSync(dir).length;
58
+ const lines = [
59
+ `.ajan test klasörü hazır: ${dir}`,
60
+ `Önceki içerik: ${before} öğe; sonraki içerik: ${after} öğe${cleared > 0 ? ` (${cleared} temizlendi)` : ""}`
61
+ ];
62
+ if (params.note)
63
+ lines.push(`Not: ${params.note}`);
64
+ lines.push("KURAL: tüm denemeleri, geçici dosyaları ve riskli testleri BURADA yap; asıl projeye dokunma.");
65
+ return { ok: true, output: lines.join("\n") };
66
+ }
67
+ };
68
+ export const setTodosTool = {
69
+ name: "set_todos",
70
+ description: "Görev programını (.ajan/todos.json) günceller. " +
71
+ "Bir göreve başlarken önce kısa bir TODO listesi planla ve bu araçla kaydet. " +
72
+ "done=false adım halen beklemede, done=true tamamlandı demektir.",
73
+ parameters: {
74
+ type: "object",
75
+ properties: {
76
+ todos: {
77
+ type: "array",
78
+ description: "Yapılacaklar listesi",
79
+ items: {
80
+ type: "object",
81
+ properties: {
82
+ text: { type: "string", description: "Adım açıklaması" },
83
+ done: { type: "boolean", description: "Tamamlandı mı (varsayılan false)" }
84
+ },
85
+ required: ["text"]
86
+ }
87
+ }
88
+ },
89
+ required: ["todos"]
90
+ },
91
+ handler: async (params, ctx) => {
92
+ const raw = params.todos;
93
+ if (!raw || raw.length === 0)
94
+ return { ok: false, output: "todos listesi boş olamaz." };
95
+ const items = raw.map((t) => ({
96
+ text: String(t.text ?? "").trim(),
97
+ done: t.done === true
98
+ }));
99
+ if (items.some((t) => !t.text))
100
+ return { ok: false, output: "Her TODO maddesi için text gerekli." };
101
+ writeFileSync(todosPath(ctx.cwd), JSON.stringify(items, null, 2), "utf8");
102
+ const lines = items.map((t, i) => `${t.done ? "[x]" : "[ ]"} ${i + 1}. ${t.text}`);
103
+ return {
104
+ ok: true,
105
+ output: `TODO listesi kaydedildi (${items.length} madde): ${todosPath(ctx.cwd)}\n${lines.join("\n")}`
106
+ };
107
+ }
108
+ };
109
+ export const getTodosTool = {
110
+ name: "get_todos",
111
+ description: "Görev programını (.ajan/todos.json) okur. Kayıtlı TODO varsa döndürür, yoksa boş döner.",
112
+ parameters: {
113
+ type: "object",
114
+ properties: {}
115
+ },
116
+ handler: async (_params, ctx) => {
117
+ const items = loadTodos(ctx.cwd);
118
+ if (items.length === 0) {
119
+ return { ok: true, output: "Kayıtlı TODO yok. Göreve başlarken set_todos ile plan oluştur." };
120
+ }
121
+ const lines = items.map((t, i) => `${t.done ? "[x]" : "[ ]"} ${i + 1}. ${t.text}`);
122
+ return { ok: true, output: limitOutput(`Görev programı (${items.length} madde):\n${lines.join("\n")}`, ctx.config.maxToolOutput) };
123
+ }
124
+ };
125
+ //# sourceMappingURL=playground.js.map
@@ -5,6 +5,5 @@ export type ToolEvents = {
5
5
  onToolResult?: (name: string, ok: boolean, output: string) => void;
6
6
  };
7
7
  export declare function createCoreTools(ctx: ToolExecutionContext): AgentTool[];
8
- export declare function toolNames(): string[];
9
8
  export declare function buildSessionFunctions(tools: AgentTool[], ctx: ToolExecutionContext, events?: ToolEvents): ChatSessionModelFunctions;
10
9
  export type { ToolExecutionSignal };
@@ -7,6 +7,7 @@ import { moveFileTool, deleteFileTool } from "./filesys.js";
7
7
  import { readDataTool, unzipFileTool } from "./data.js";
8
8
  import { noteAddTool, noteReadTool } from "./memory.js";
9
9
  import { taskCompleteTool } from "./taskComplete.js";
10
+ import { openPlaygroundTool, setTodosTool, getTodosTool } from "./playground.js";
10
11
  import { logger } from "../utils/paths.js";
11
12
  export function createCoreTools(ctx) {
12
13
  return [
@@ -26,12 +27,12 @@ export function createCoreTools(ctx) {
26
27
  fetchUrlTool,
27
28
  noteAddTool,
28
29
  noteReadTool,
29
- taskCompleteTool
30
+ taskCompleteTool,
31
+ openPlaygroundTool,
32
+ setTodosTool,
33
+ getTodosTool
30
34
  ];
31
35
  }
32
- export function toolNames() {
33
- return createCoreTools({ cwd: process.cwd(), config: { modelId: "x" } }).map((t) => t.name);
34
- }
35
36
  export function buildSessionFunctions(tools, ctx, events = {}) {
36
37
  const result = {};
37
38
  for (const tool of tools) {