natureco-cli 5.19.0 → 5.20.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/CHANGELOG.md +32 -0
- package/package.json +1 -1
- package/skills/a-b-testing/SKILL.md +34 -0
- package/skills/accessibility-audit/SKILL.md +34 -0
- package/skills/agent-memory/SKILL.md +34 -0
- package/skills/agent-orchestration/SKILL.md +34 -0
- package/skills/analytics-setup/SKILL.md +34 -0
- package/skills/api-design/SKILL.md +34 -0
- package/skills/api-versioning/SKILL.md +34 -0
- package/skills/audio-transcription/SKILL.md +34 -0
- package/skills/authentication-patterns/SKILL.md +34 -0
- package/skills/authorization-rbac/SKILL.md +34 -0
- package/skills/backup-strategy/SKILL.md +34 -0
- package/skills/bash-scripting/SKILL.md +34 -0
- package/skills/batch-api-calls/SKILL.md +34 -0
- package/skills/batch-processing/SKILL.md +34 -0
- package/skills/bulk-file-operations/SKILL.md +34 -0
- package/skills/cache-responses/SKILL.md +34 -0
- package/skills/caching-strategy/SKILL.md +34 -0
- package/skills/calendar-optimization/SKILL.md +34 -0
- package/skills/changelog-generation/SKILL.md +34 -0
- package/skills/chat-bot-design/SKILL.md +34 -0
- package/skills/chunking-strategy/SKILL.md +34 -0
- package/skills/ci-cd-pipeline/SKILL.md +34 -0
- package/skills/classification-pipeline/SKILL.md +34 -0
- package/skills/clipboard-master/SKILL.md +34 -0
- package/skills/code-explanation/SKILL.md +34 -0
- package/skills/code-generation/SKILL.md +34 -0
- package/skills/code-migration/SKILL.md +34 -0
- package/skills/code-review-checklist/SKILL.md +34 -0
- package/skills/contact-management/SKILL.md +34 -0
- package/skills/content-generation/SKILL.md +34 -0
- package/skills/context-pruning/SKILL.md +34 -0
- package/skills/context-window-management/SKILL.md +34 -0
- package/skills/continuous-improvement/SKILL.md +34 -0
- package/skills/cron-scheduling/SKILL.md +34 -0
- package/skills/cross-platform-compat/SKILL.md +34 -0
- package/skills/csv-processing/SKILL.md +34 -0
- package/skills/daily-journal/SKILL.md +34 -0
- package/skills/data-analysis/SKILL.md +34 -0
- package/skills/data-backup/SKILL.md +34 -0
- package/skills/data-cleaning/SKILL.md +34 -0
- package/skills/data-extraction/SKILL.md +34 -0
- package/skills/data-validation/SKILL.md +34 -0
- package/skills/database-migrations/SKILL.md +34 -0
- package/skills/database-schema-design/SKILL.md +34 -0
- package/skills/decision-matrix/SKILL.md +34 -0
- package/skills/dependency-management/SKILL.md +34 -0
- package/skills/dev-environment-setup/SKILL.md +34 -0
- package/skills/distraction-blocker/SKILL.md +34 -0
- package/skills/docker-optimization/SKILL.md +34 -0
- package/skills/document-template/SKILL.md +34 -0
- package/skills/documentation-gen/SKILL.md +34 -0
- package/skills/efficient-formatting/SKILL.md +34 -0
- package/skills/email-management/SKILL.md +34 -0
- package/skills/email-service/SKILL.md +34 -0
- package/skills/embedding-generation/SKILL.md +34 -0
- package/skills/entity-extraction/SKILL.md +34 -0
- package/skills/environment-config/SKILL.md +34 -0
- package/skills/error-handling/SKILL.md +34 -0
- package/skills/estimation-techniques/SKILL.md +34 -0
- package/skills/event-driven-architecture/SKILL.md +34 -0
- package/skills/excel-automation/SKILL.md +34 -0
- package/skills/execution-monitoring/SKILL.md +34 -0
- package/skills/expense-tracking/SKILL.md +34 -0
- package/skills/feature-engineering/SKILL.md +34 -0
- package/skills/feature-flags/SKILL.md +34 -0
- package/skills/file-conversion/SKILL.md +34 -0
- package/skills/file-upload-handling/SKILL.md +34 -0
- package/skills/fine-tuning-prep/SKILL.md +34 -0
- package/skills/focus-mode/SKILL.md +34 -0
- package/skills/form-validation/SKILL.md +34 -0
- package/skills/function-calling/SKILL.md +34 -0
- package/skills/git-workflow/SKILL.md +34 -0
- package/skills/goal-setting/SKILL.md +34 -0
- package/skills/graphql-design/SKILL.md +34 -0
- package/skills/habit-tracker/SKILL.md +34 -0
- package/skills/hallucination-detection/SKILL.md +34 -0
- package/skills/image-processing/SKILL.md +34 -0
- package/skills/json-transformation/SKILL.md +34 -0
- package/skills/keyboard-shortcuts/SKILL.md +34 -0
- package/skills/knowledge-base/SKILL.md +34 -0
- package/skills/knowledge-graph/SKILL.md +34 -0
- package/skills/kubernetes-deployment/SKILL.md +34 -0
- package/skills/language-translation/SKILL.md +34 -0
- package/skills/llm-chaining/SKILL.md +34 -0
- package/skills/localization-i18n/SKILL.md +34 -0
- package/skills/logging-best-practices/SKILL.md +34 -0
- package/skills/machine-learning-pipeline/SKILL.md +34 -0
- package/skills/meeting-notes/SKILL.md +34 -0
- package/skills/meeting-scheduler/SKILL.md +34 -0
- package/skills/message-queues/SKILL.md +34 -0
- package/skills/message-summarization/SKILL.md +34 -0
- package/skills/microservices-architecture/SKILL.md +34 -0
- package/skills/milestone-tracking/SKILL.md +34 -0
- package/skills/model-deployment/SKILL.md +34 -0
- package/skills/model-evaluation/SKILL.md +34 -0
- package/skills/monitoring-setup/SKILL.md +34 -0
- package/skills/monorepo-setup/SKILL.md +34 -0
- package/skills/multi-agent-systems/SKILL.md +34 -0
- package/skills/network-troubleshooting/SKILL.md +34 -0
- package/skills/note-organization/SKILL.md +34 -0
- package/skills/package-publishing/SKILL.md +34 -0
- package/skills/password-generator/SKILL.md +34 -0
- package/skills/pdf-processing/SKILL.md +34 -0
- package/skills/performance-optimization/SKILL.md +34 -0
- package/skills/pomodoro-timer/SKILL.md +34 -0
- package/skills/powershell-automation/SKILL.md +34 -0
- package/skills/priority-filtering/SKILL.md +34 -0
- package/skills/project-tracking/SKILL.md +34 -0
- package/skills/prompt-compression/SKILL.md +34 -0
- package/skills/prompt-engineering/SKILL.md +34 -0
- package/skills/push-notifications/SKILL.md +34 -0
- package/skills/rag-implementation/SKILL.md +34 -0
- package/skills/rate-limiting/SKILL.md +34 -0
- package/skills/real-time-communication/SKILL.md +34 -0
- package/skills/refactoring-strategy/SKILL.md +34 -0
- package/skills/regex-mastery/SKILL.md +34 -0
- package/skills/report-generation/SKILL.md +34 -0
- package/skills/research-compiler/SKILL.md +34 -0
- package/skills/responsive-design/SKILL.md +34 -0
- package/skills/rest-api-patterns/SKILL.md +34 -0
- package/skills/retrospectives/SKILL.md +34 -0
- package/skills/risk-assessment/SKILL.md +34 -0
- package/skills/roadmap-creation/SKILL.md +34 -0
- package/skills/search-implementation/SKILL.md +34 -0
- package/skills/secrets-management/SKILL.md +34 -0
- package/skills/security-audit/SKILL.md +34 -0
- package/skills/selective-memory/SKILL.md +34 -0
- package/skills/semantic-search/SKILL.md +34 -0
- package/skills/semantic-versioning/SKILL.md +34 -0
- package/skills/sentiment-analysis/SKILL.md +34 -0
- package/skills/seo-optimization/SKILL.md +34 -0
- package/skills/snippet-manager/SKILL.md +34 -0
- package/skills/sprint-planning/SKILL.md +34 -0
- package/skills/sql-query-optimization/SKILL.md +34 -0
- package/skills/stakeholder-communication/SKILL.md +34 -0
- package/skills/state-management/SKILL.md +34 -0
- package/skills/statistical-analysis/SKILL.md +34 -0
- package/skills/streaming-responses/SKILL.md +34 -0
- package/skills/structured-output/SKILL.md +34 -0
- package/skills/summarization-techniques/SKILL.md +34 -0
- package/skills/system-diagnostics/SKILL.md +34 -0
- package/skills/task-decomposition/SKILL.md +34 -0
- package/skills/task-prioritization/SKILL.md +34 -0
- package/skills/template-library/SKILL.md +34 -0
- package/skills/test-generation/SKILL.md +34 -0
- package/skills/testing-strategy/SKILL.md +34 -0
- package/skills/text-expansion/SKILL.md +34 -0
- package/skills/time-blocking/SKILL.md +34 -0
- package/skills/token-budgeting/SKILL.md +34 -0
- package/skills/token-optimization/SKILL.md +34 -0
- package/skills/tool-calling-patterns/SKILL.md +34 -0
- package/skills/vector-database-setup/SKILL.md +34 -0
- package/skills/video-editing/SKILL.md +34 -0
- package/skills/visualization-creation/SKILL.md +34 -0
- package/skills/web-scraping/SKILL.md +34 -0
- package/skills/webhook-handling/SKILL.md +34 -0
- package/skills/weekly-review/SKILL.md +34 -0
- package/skills/workflow-automation/SKILL.md +34 -0
- package/src/commands/code_v5.js +377 -73
- package/src/commands/repl.js +505 -86
- package/src/providers/model/anthropic.js +84 -0
- package/src/providers/model/gemini.js +49 -0
- package/src/providers/model/minimax.js +48 -0
- package/src/providers/model/ollama.js +42 -0
- package/src/providers/model/openai.js +49 -0
- package/src/providers/search/duckduckgo.js +45 -0
- package/src/providers/search/exa.js +52 -0
- package/src/providers/search/searxng.js +51 -0
- package/src/providers/search/tavily.js +47 -0
- package/src/tools/model_provider.js +71 -0
- package/src/tools/parallel_search.js +40 -49
- package/src/tools/search_provider.js +79 -0
- package/src/tools/web_search.js +42 -62
- package/src/tools/workflow.js +21 -9
- package/src/utils/cron.js +82 -126
- package/src/utils/effort-levels.js +42 -0
- package/src/utils/fallback-chain.js +72 -0
- package/src/utils/file-history.js +78 -0
- package/src/utils/model-provider.js +113 -0
- package/src/utils/permissions.js +147 -0
- package/src/utils/plan-mode.js +164 -0
- package/src/utils/provider-detect.js +7 -32
- package/src/utils/sandbox.js +60 -0
- package/src/utils/search-provider.js +62 -0
- package/src/utils/session-search.js +66 -0
- package/src/utils/structured-output.js +20 -0
- package/src/utils/system-prompt.js +28 -2
- package/src/utils/tasks.js +132 -0
- package/src/utils/tool-hooks.js +154 -0
- package/src/utils/tools.js +16 -0
- package/src/utils/ultra-review.js +59 -0
- package/src/utils/worktree.js +192 -0
package/src/commands/repl.js
CHANGED
|
@@ -27,7 +27,7 @@ const { accumulateToolCallDeltas, finalizeToolCalls } = require('../utils/stream
|
|
|
27
27
|
const { createPasteSafeInput, createOutputFilter, enableBracketedPaste, disableBracketedPaste, restoreNewlines, clearPasteContext } = require('../utils/paste-safe-input');
|
|
28
28
|
const { getMemoryStore } = require('../utils/memory-store');
|
|
29
29
|
const { buildSkillIndex } = require('../utils/skill-index');
|
|
30
|
-
const { buildTiers, assemble } = require('../utils/system-prompt');
|
|
30
|
+
const { buildTiers, assemble, discoverProjectRules } = require('../utils/system-prompt');
|
|
31
31
|
const { ToolGuardrails } = require('../utils/tool-guardrails');
|
|
32
32
|
|
|
33
33
|
// v5.4.6: Model adi sizintisini engelle — global'e ata, callback'lerden erisebilir olsun
|
|
@@ -71,7 +71,8 @@ function rebuildSystemPrompt(opts) {
|
|
|
71
71
|
_cachedTierOpts.botName !== opts.botName ||
|
|
72
72
|
_cachedTierOpts.soulSummary !== opts.soulSummary ||
|
|
73
73
|
_cachedTierOpts.skillsIndexBlock !== opts.skillsIndexBlock ||
|
|
74
|
-
_cachedTierOpts.crossSessionContext !== opts.crossSessionContext
|
|
74
|
+
_cachedTierOpts.crossSessionContext !== opts.crossSessionContext ||
|
|
75
|
+
_cachedTierOpts.projectRules !== opts.projectRules;
|
|
75
76
|
|
|
76
77
|
if (needsFullRebuild || !_cachedStable) {
|
|
77
78
|
const tiers = buildTiers(opts);
|
|
@@ -82,6 +83,7 @@ function rebuildSystemPrompt(opts) {
|
|
|
82
83
|
soulSummary: opts.soulSummary,
|
|
83
84
|
skillsIndexBlock: opts.skillsIndexBlock,
|
|
84
85
|
crossSessionContext: opts.crossSessionContext,
|
|
86
|
+
projectRules: opts.projectRules,
|
|
85
87
|
};
|
|
86
88
|
}
|
|
87
89
|
// Volatile always rebuilt fresh
|
|
@@ -92,6 +94,7 @@ function rebuildSystemPrompt(opts) {
|
|
|
92
94
|
soulSummary: '',
|
|
93
95
|
skillsIndexBlock: '',
|
|
94
96
|
crossSessionContext: '',
|
|
97
|
+
projectRules: '',
|
|
95
98
|
});
|
|
96
99
|
return assemble(_cachedStable, _cachedContext, volatileOnly.volatile);
|
|
97
100
|
}
|
|
@@ -287,14 +290,240 @@ function apiRequest(providerUrl, providerApiKey, body, stream = false, retries =
|
|
|
287
290
|
async function sendStreaming(providerUrl, providerApiKey, messages, model, onChunk, onToolCall) {
|
|
288
291
|
const isMM = isMiniMax(providerUrl);
|
|
289
292
|
const isGM = isGemini(providerUrl);
|
|
293
|
+
const planMode = getPlanMode();
|
|
294
|
+
|
|
295
|
+
// Simple stdin prompt fallback (no rl dependency)
|
|
296
|
+
function stdinPrompt(question, cb) {
|
|
297
|
+
process.stdout.write(question);
|
|
298
|
+
const stdin = process.stdin;
|
|
299
|
+
const onData = (data) => {
|
|
300
|
+
stdin.removeListener('data', onData);
|
|
301
|
+
stdin.pause();
|
|
302
|
+
cb(data.toString().trim());
|
|
303
|
+
};
|
|
304
|
+
stdin.resume();
|
|
305
|
+
stdin.on('data', onData);
|
|
306
|
+
}
|
|
307
|
+
|
|
290
308
|
const toolDefs = getToolDefs();
|
|
309
|
+
// Inject plan mode virtual tools
|
|
310
|
+
const planToolDefs = [
|
|
311
|
+
{
|
|
312
|
+
name: 'EnterPlanMode',
|
|
313
|
+
description: 'Switch to plan-only mode. Research, explore, and create a detailed plan without making changes. Use ExitPlanMode when ready.',
|
|
314
|
+
parameters: { type: 'object', properties: {}, required: [] },
|
|
315
|
+
execute: async () => {
|
|
316
|
+
if (planMode.enter()) return { result: 'Plan mode activated. You can now research and plan. No changes will be made. Use ExitPlanMode when your plan is ready.' };
|
|
317
|
+
return { result: 'Already in plan mode.' };
|
|
318
|
+
},
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
name: 'ExitPlanMode',
|
|
322
|
+
description: 'Exit plan mode and present your plan for review. The plan must include steps, files to modify, and expected changes.',
|
|
323
|
+
parameters: {
|
|
324
|
+
type: 'object',
|
|
325
|
+
properties: {
|
|
326
|
+
plan: { type: 'string', description: 'Markdown plan with ## Plan title, ### Step N sections listing files and changes' },
|
|
327
|
+
summary: { type: 'string', description: 'One-sentence summary of the plan' },
|
|
328
|
+
},
|
|
329
|
+
required: ['plan'],
|
|
330
|
+
},
|
|
331
|
+
execute: async (args) => {
|
|
332
|
+
const ok = planMode.exit(args.plan);
|
|
333
|
+
if (!ok) return { error: 'Not in plan mode.' };
|
|
334
|
+
return {
|
|
335
|
+
result: `Plan submitted for review.\n\n${args.plan}`,
|
|
336
|
+
_plan_summary: args.summary || '',
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
];
|
|
341
|
+
toolDefs.push(...planToolDefs);
|
|
342
|
+
|
|
343
|
+
// Inject worktree virtual tools
|
|
344
|
+
const wt = getWorktree();
|
|
345
|
+
const worktreeToolDefs = [
|
|
346
|
+
{
|
|
347
|
+
name: 'EnterWorktree',
|
|
348
|
+
description: 'Create an isolated worktree for experimental changes without affecting the main project. Use ExitWorktree to merge changes back.',
|
|
349
|
+
parameters: {
|
|
350
|
+
type: 'object',
|
|
351
|
+
properties: {
|
|
352
|
+
branch: { type: 'string', description: 'Optional branch name for the worktree' },
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
execute: async (args) => wt.enter(args),
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
name: 'ExitWorktree',
|
|
359
|
+
description: 'Exit the current worktree and merge changes back to the main branch.',
|
|
360
|
+
parameters: {
|
|
361
|
+
type: 'object',
|
|
362
|
+
properties: {
|
|
363
|
+
merge: { type: 'boolean', description: 'Merge changes back (default: true)' },
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
execute: async (args) => wt.exit(args),
|
|
367
|
+
},
|
|
368
|
+
];
|
|
369
|
+
toolDefs.push(...worktreeToolDefs);
|
|
370
|
+
|
|
371
|
+
// Inject tool-level virtual tools
|
|
372
|
+
const { getTaskManager } = require('../utils/tasks');
|
|
373
|
+
const taskMgr = getTaskManager();
|
|
374
|
+
const toolVirtualTools = [
|
|
375
|
+
{
|
|
376
|
+
name: 'CreateTask',
|
|
377
|
+
description: 'Run a command in the background. Returns immediately with a task ID. Use GetTaskResult or ListTasks to check status.',
|
|
378
|
+
parameters: {
|
|
379
|
+
type: 'object',
|
|
380
|
+
properties: {
|
|
381
|
+
command: { type: 'string', description: 'Shell command to run' },
|
|
382
|
+
timeout: { type: 'number', description: 'Timeout in ms (default: 300000)' },
|
|
383
|
+
},
|
|
384
|
+
required: ['command'],
|
|
385
|
+
},
|
|
386
|
+
execute: async (args) => taskMgr.create(args.command, args),
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: 'ListTasks',
|
|
390
|
+
description: 'List all background tasks with their status.',
|
|
391
|
+
parameters: { type: 'object', properties: {}, required: [] },
|
|
392
|
+
execute: async () => ({ tasks: taskMgr.list() }),
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: 'GetTaskResult',
|
|
396
|
+
description: 'Get the full result (stdout/stderr) of a completed or running task.',
|
|
397
|
+
parameters: {
|
|
398
|
+
type: 'object',
|
|
399
|
+
properties: { taskId: { type: 'string', description: 'Task ID' } },
|
|
400
|
+
required: ['taskId'],
|
|
401
|
+
},
|
|
402
|
+
execute: async (args) => {
|
|
403
|
+
const task = taskMgr.get(args.taskId);
|
|
404
|
+
if (!task) return { error: 'Task not found' };
|
|
405
|
+
return { result: task.stdout, error: task.stderr, status: task.status, exitCode: task.exitCode };
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
name: 'StopTask',
|
|
410
|
+
description: 'Stop a running background task.',
|
|
411
|
+
parameters: {
|
|
412
|
+
type: 'object',
|
|
413
|
+
properties: { taskId: { type: 'string' } },
|
|
414
|
+
required: ['taskId'],
|
|
415
|
+
},
|
|
416
|
+
execute: async (args) => taskMgr.stop(args.taskId),
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: 'SearchSessions',
|
|
420
|
+
description: 'Search past conversation sessions for information. Useful for finding previously discussed topics or decisions.',
|
|
421
|
+
parameters: {
|
|
422
|
+
type: 'object',
|
|
423
|
+
properties: { query: { type: 'string', description: 'Search query' } },
|
|
424
|
+
required: ['query'],
|
|
425
|
+
},
|
|
426
|
+
execute: async (args) => {
|
|
427
|
+
const { search } = require('../utils/session-search');
|
|
428
|
+
return { results: search(args.query, 5) };
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
name: 'RestoreFile',
|
|
433
|
+
description: 'Restore a file from its snapshot history. Use FileHistory first to list available snapshots.',
|
|
434
|
+
parameters: {
|
|
435
|
+
type: 'object',
|
|
436
|
+
properties: {
|
|
437
|
+
filePath: { type: 'string', description: 'File path to restore' },
|
|
438
|
+
timestamp: { type: 'number', description: 'Snapshot timestamp (from FileHistory). If omitted, restores previous version.' },
|
|
439
|
+
},
|
|
440
|
+
required: ['filePath'],
|
|
441
|
+
},
|
|
442
|
+
execute: async (args) => {
|
|
443
|
+
const fh = require('../utils/file-history');
|
|
444
|
+
const snaps = fh.getHistory(args.filePath);
|
|
445
|
+
if (snaps.length === 0) return { error: 'No history for this file' };
|
|
446
|
+
const ts = args.timestamp || snaps[0].timestamp;
|
|
447
|
+
return fh.restore(args.filePath, ts);
|
|
448
|
+
},
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: 'FileHistory',
|
|
452
|
+
description: 'List snapshot history for a file, showing timestamps and sizes of previous versions.',
|
|
453
|
+
parameters: {
|
|
454
|
+
type: 'object',
|
|
455
|
+
properties: { filePath: { type: 'string' } },
|
|
456
|
+
required: ['filePath'],
|
|
457
|
+
},
|
|
458
|
+
execute: async (args) => {
|
|
459
|
+
const fh = require('../utils/file-history');
|
|
460
|
+
return { snapshots: fh.getHistory(args.filePath) };
|
|
461
|
+
},
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: 'UltraReview',
|
|
465
|
+
description: 'Run a multi-focus code review (security, style, logic, performance) on specified files or git diff.',
|
|
466
|
+
parameters: {
|
|
467
|
+
type: 'object',
|
|
468
|
+
properties: {
|
|
469
|
+
files: { type: 'array', items: { type: 'string' }, description: 'File paths to review' },
|
|
470
|
+
diff: { type: 'string', description: 'Git diff content to review instead of files' },
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
execute: async (args) => {
|
|
474
|
+
const ur = require('../utils/ultra-review');
|
|
475
|
+
if (args.diff) return ur.reviewDiff(args.diff);
|
|
476
|
+
if (args.files) {
|
|
477
|
+
const fs = require('fs');
|
|
478
|
+
return {
|
|
479
|
+
reviews: args.files.map(f => {
|
|
480
|
+
const content = fs.existsSync(f) ? fs.readFileSync(f, 'utf8') : '';
|
|
481
|
+
return ur.reviewFile(f, content);
|
|
482
|
+
}),
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return { error: 'Specify files or diff' };
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
{
|
|
489
|
+
name: 'ScheduleTask',
|
|
490
|
+
description: 'Schedule a recurring task using cron expressions. E.g., "*/5 * * * *" for every 5 min.',
|
|
491
|
+
parameters: {
|
|
492
|
+
type: 'object',
|
|
493
|
+
properties: {
|
|
494
|
+
schedule: { type: 'string', description: 'Cron expression: min hour dom mon dow' },
|
|
495
|
+
command: { type: 'string', description: 'Command to run' },
|
|
496
|
+
description: { type: 'string', description: 'Optional description' },
|
|
497
|
+
},
|
|
498
|
+
required: ['schedule', 'command'],
|
|
499
|
+
},
|
|
500
|
+
execute: async (args) => require('../utils/cron').addJob(args),
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'ListScheduledTasks',
|
|
504
|
+
description: 'List all scheduled cron tasks.',
|
|
505
|
+
parameters: { type: 'object', properties: {} },
|
|
506
|
+
execute: async () => ({ jobs: require('../utils/cron').loadJobs() }),
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: 'RemoveScheduledTask',
|
|
510
|
+
description: 'Remove a scheduled task by ID.',
|
|
511
|
+
parameters: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
|
|
512
|
+
execute: async (args) => {
|
|
513
|
+
require('../utils/cron').removeJob(args.id);
|
|
514
|
+
return { removed: true };
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
];
|
|
518
|
+
toolDefs.push(...toolVirtualTools);
|
|
519
|
+
|
|
291
520
|
const toolParam = toOpenAIFormat(toolDefs);
|
|
292
521
|
guardrails.reset();
|
|
293
522
|
|
|
294
523
|
let currentMessages = messages;
|
|
295
524
|
let fullText = '';
|
|
296
525
|
let iterations = 0;
|
|
297
|
-
const MAX_TOOL_ITERATIONS =
|
|
526
|
+
const MAX_TOOL_ITERATIONS = 50;
|
|
298
527
|
const MAX_CONTEXT_TOKENS = 32000; // safety limit before compression
|
|
299
528
|
|
|
300
529
|
// v5.7.18: Preflight compression — if context too long, compress middle messages
|
|
@@ -323,18 +552,53 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
|
|
|
323
552
|
// Apply preflight on entry
|
|
324
553
|
currentMessages = preflightCompress(currentMessages);
|
|
325
554
|
|
|
326
|
-
while (iterations <
|
|
555
|
+
while (iterations < 50) {
|
|
556
|
+
let effortLevel = 'medium', effortCfg;
|
|
557
|
+
let cfg;
|
|
558
|
+
try {
|
|
559
|
+
cfg = require('../utils/config').getConfig();
|
|
560
|
+
effortLevel = getEffortLevel(cfg);
|
|
561
|
+
effortCfg = getEffortConfig(effortLevel);
|
|
562
|
+
} catch { effortCfg = getEffortConfig(effortLevel); }
|
|
563
|
+
const maxIter = effortCfg ? effortCfg.maxToolIterations : 50;
|
|
327
564
|
iterations++;
|
|
328
565
|
// v5.7.18: Preflight compress before each iteration to prevent context bloat
|
|
329
566
|
currentMessages = preflightCompress(currentMessages);
|
|
330
567
|
const shouldStream = !isMM && !isGM; // MiniMax + Gemini non-stream (tool_calls reliability)
|
|
568
|
+
// Inject plan mode prompt into system message
|
|
569
|
+
const planModePrompt = planMode.getSystemPrompt();
|
|
570
|
+
if (planModePrompt) {
|
|
571
|
+
const sysIdx = currentMessages.findIndex(m => m.role === 'system');
|
|
572
|
+
if (sysIdx >= 0) {
|
|
573
|
+
const base = currentMessages[sysIdx].content;
|
|
574
|
+
if (!base.endsWith(planModePrompt)) {
|
|
575
|
+
currentMessages[sysIdx] = { ...currentMessages[sysIdx], content: base + '\n\n' + planModePrompt };
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
// Clean up any stale plan mode prompt from previous iterations
|
|
580
|
+
const sysIdx = currentMessages.findIndex(m => m.role === 'system');
|
|
581
|
+
if (sysIdx >= 0) {
|
|
582
|
+
const base = currentMessages[sysIdx].content;
|
|
583
|
+
const marker = '\n\nYou are in PLAN MODE.';
|
|
584
|
+
if (base.includes(marker)) {
|
|
585
|
+
currentMessages[sysIdx] = { ...currentMessages[sysIdx], content: base.split('\n\nYou are in PLAN MODE.')[0] };
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const fallbackChain = getFallbackChain();
|
|
591
|
+
|
|
331
592
|
const body = {
|
|
332
|
-
model,
|
|
593
|
+
model: fallbackChain.current || model,
|
|
333
594
|
messages: currentMessages,
|
|
334
595
|
stream: shouldStream,
|
|
335
|
-
temperature:
|
|
336
|
-
max_tokens:
|
|
596
|
+
temperature: effortCfg.temperature,
|
|
597
|
+
max_tokens: effortCfg.maxTokens,
|
|
337
598
|
};
|
|
599
|
+
// Structured output support
|
|
600
|
+
const respFmt = getResponseFormat(cfg);
|
|
601
|
+
if (respFmt) body.response_format = respFmt;
|
|
338
602
|
if (toolParam) body.tools = toolParam;
|
|
339
603
|
if (isMM || isGM) body.tool_choice = 'auto'; // MiniMax + Gemini için explicit
|
|
340
604
|
|
|
@@ -350,7 +614,7 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
|
|
|
350
614
|
fullText = content;
|
|
351
615
|
// Non-stream tool call desteği
|
|
352
616
|
if (msg.tool_calls && msg.tool_calls.length > 0) {
|
|
353
|
-
const toolResults = await processToolCalls(msg.tool_calls, onToolCall);
|
|
617
|
+
const toolResults = await processToolCalls(msg.tool_calls, onToolCall, stdinPrompt);
|
|
354
618
|
currentMessages.push(msg);
|
|
355
619
|
currentMessages.push(...toolResults);
|
|
356
620
|
continue; // Tekrar API çağır
|
|
@@ -365,60 +629,71 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
|
|
|
365
629
|
: isGemini(providerUrl)
|
|
366
630
|
? `${providerUrl.replace(/\/+$/, '')}/openai/chat/completions`
|
|
367
631
|
: `${providerUrl.replace(/\/+$/, '')}/chat/completions`;
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
res.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
let streamText = '';
|
|
382
|
-
const toolCalls = []; // { index, id, name, args }
|
|
383
|
-
res.on('data', (chunk) => {
|
|
384
|
-
buffer += chunk.toString('utf8');
|
|
385
|
-
const lines = buffer.split('\n');
|
|
386
|
-
buffer = lines.pop() || '';
|
|
387
|
-
for (const line of lines) {
|
|
388
|
-
const trimmed = line.trim();
|
|
389
|
-
if (!trimmed || !trimmed.startsWith('data:')) continue;
|
|
390
|
-
const data = trimmed.slice(5).trim();
|
|
391
|
-
if (data === '[DONE]') {
|
|
392
|
-
resolve({ streamText, toolCalls });
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
try {
|
|
396
|
-
const parsed = JSON.parse(data);
|
|
397
|
-
const choice = parsed.choices?.[0];
|
|
398
|
-
if (!choice) continue;
|
|
399
|
-
const delta = choice.delta;
|
|
400
|
-
|
|
401
|
-
// Text content
|
|
402
|
-
if (delta.content) {
|
|
403
|
-
streamText += delta.content;
|
|
404
|
-
onChunk(delta.content);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Tool calls (streaming delta) — shared accumulator,
|
|
408
|
-
// see src/utils/streaming-tools.js for the per-index pattern.
|
|
409
|
-
if (delta.tool_calls) {
|
|
410
|
-
accumulateToolCallDeltas(toolCalls, delta.tool_calls);
|
|
411
|
-
}
|
|
412
|
-
} catch {}
|
|
632
|
+
let result;
|
|
633
|
+
try {
|
|
634
|
+
result = await new Promise((resolve, reject) => {
|
|
635
|
+
const req = https.request(endpoint, {
|
|
636
|
+
method: 'POST',
|
|
637
|
+
headers: { 'Authorization': `Bearer ${providerApiKey}`, 'Content-Type': 'application/json' },
|
|
638
|
+
timeout: 60000,
|
|
639
|
+
}, (res) => {
|
|
640
|
+
if (res.statusCode !== 200) {
|
|
641
|
+
let data = '';
|
|
642
|
+
res.on('data', c => data += c);
|
|
643
|
+
res.on('end', () => reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`)));
|
|
644
|
+
return;
|
|
413
645
|
}
|
|
646
|
+
let buffer = '';
|
|
647
|
+
let streamText = '';
|
|
648
|
+
const toolCalls = []; // { index, id, name, args }
|
|
649
|
+
res.on('data', (chunk) => {
|
|
650
|
+
buffer += chunk.toString('utf8');
|
|
651
|
+
const lines = buffer.split('\n');
|
|
652
|
+
buffer = lines.pop() || '';
|
|
653
|
+
for (const line of lines) {
|
|
654
|
+
const trimmed = line.trim();
|
|
655
|
+
if (!trimmed || !trimmed.startsWith('data:')) continue;
|
|
656
|
+
const data = trimmed.slice(5).trim();
|
|
657
|
+
if (data === '[DONE]') {
|
|
658
|
+
resolve({ streamText, toolCalls });
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
try {
|
|
662
|
+
const parsed = JSON.parse(data);
|
|
663
|
+
const choice = parsed.choices?.[0];
|
|
664
|
+
if (!choice) continue;
|
|
665
|
+
const delta = choice.delta;
|
|
666
|
+
|
|
667
|
+
// Text content
|
|
668
|
+
if (delta.content) {
|
|
669
|
+
streamText += delta.content;
|
|
670
|
+
onChunk(delta.content);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Tool calls (streaming delta) — shared accumulator,
|
|
674
|
+
// see src/utils/streaming-tools.js for the per-index pattern.
|
|
675
|
+
if (delta.tool_calls) {
|
|
676
|
+
accumulateToolCallDeltas(toolCalls, delta.tool_calls);
|
|
677
|
+
}
|
|
678
|
+
} catch {}
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
res.on('end', () => resolve({ streamText, toolCalls }));
|
|
414
682
|
});
|
|
415
|
-
|
|
683
|
+
req.on('error', reject);
|
|
684
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); });
|
|
685
|
+
req.write(JSON.stringify(body));
|
|
686
|
+
req.end();
|
|
416
687
|
});
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
688
|
+
} catch (err) {
|
|
689
|
+
// Fallback chain: try next model on error
|
|
690
|
+
const fb = fallbackChain.recordError(body.model, err);
|
|
691
|
+
if (fb.fallback) {
|
|
692
|
+
console.log(tui.C.yellow(`\n ⚠ ${body.model} başarısız → ${fb.nextModel} deneniyor...\n`));
|
|
693
|
+
continue; // Retry with next model
|
|
694
|
+
}
|
|
695
|
+
throw err;
|
|
696
|
+
}
|
|
422
697
|
|
|
423
698
|
fullText = result.streamText;
|
|
424
699
|
|
|
@@ -433,8 +708,39 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
|
|
|
433
708
|
tool_calls: finalized,
|
|
434
709
|
});
|
|
435
710
|
// Her tool call'ı çalıştır, sonuçları tool mesajı olarak ekle
|
|
436
|
-
const toolResults = await processToolCalls(finalized, onToolCall);
|
|
711
|
+
const toolResults = await processToolCalls(finalized, onToolCall, stdinPrompt);
|
|
437
712
|
currentMessages.push(...toolResults);
|
|
713
|
+
|
|
714
|
+
// Plan mode review: plan submitted, wait for user approval
|
|
715
|
+
if (planMode.inReview()) {
|
|
716
|
+
const { plan } = planMode.planHistory[planMode.planHistory.length - 1] || {};
|
|
717
|
+
console.log('\n' + tui.C.cyan(' 📋 Plan sunuldu — onay bekleniyor...'));
|
|
718
|
+
console.log(tui.C.muted(' ' + '─'.repeat(56)));
|
|
719
|
+
console.log(plan ? `\n ${plan.replace(/\n/g, '\n ')}` : '');
|
|
720
|
+
console.log('\n' + tui.C.muted(' ─'.repeat(28)));
|
|
721
|
+
const approved = await new Promise(resolve => {
|
|
722
|
+
stdinPrompt(tui.C.yellow(' Planı onaylıyor musun? [Y=exec, n=reddet, e=düzenle]: '), answer => {
|
|
723
|
+
const key = answer.trim().toLowerCase();
|
|
724
|
+
if (key === 'n' || key === 'no') { planMode.reject(); resolve(false); }
|
|
725
|
+
else if (key === 'e' || key === 'edit') { planMode.reject(); resolve('edit'); }
|
|
726
|
+
else { planMode.approve(); resolve(true); }
|
|
727
|
+
});
|
|
728
|
+
});
|
|
729
|
+
if (approved === true) {
|
|
730
|
+
console.log(tui.C.green(' ✓ Plan onaylandı. Plan uygulanıyor...\n'));
|
|
731
|
+
// Devam — model cevap versin
|
|
732
|
+
} else if (approved === 'edit') {
|
|
733
|
+
console.log(tui.C.yellow(' 📝 Planı düzenleyin ve /plan ile yeniden gönderin.\n'));
|
|
734
|
+
// Plan modunda kal, mesaj ekle
|
|
735
|
+
currentMessages.push({ role: 'user', content: 'Planı düzenle ve yeniden sun.' });
|
|
736
|
+
continue;
|
|
737
|
+
} else {
|
|
738
|
+
console.log(tui.C.amber(' ⨯ Plan reddedildi. Yeniden plan yapılıyor...\n'));
|
|
739
|
+
currentMessages.push({ role: 'user', content: 'Plan reddedildi. Lütfen farklı bir yaklaşım dene.' });
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
438
744
|
// Devam — model sonuçları görsün, cevap versin
|
|
439
745
|
continue;
|
|
440
746
|
}
|
|
@@ -451,8 +757,37 @@ async function sendStreaming(providerUrl, providerApiKey, messages, model, onChu
|
|
|
451
757
|
*/
|
|
452
758
|
const UNTRUSTED_TOOLS = new Set(['browser', 'web_search', 'duckduckgo_search', 'searxng_search', 'exa_search', 'firecrawl', 'web_readability']);
|
|
453
759
|
const PARALLEL_SAFE_TOOLS = new Set(['read_file', 'file_search', 'grep_search', 'web_search', 'web_readability', 'duckduckgo_search', 'exa_search', 'searxng_search', 'firecrawl', 'memory_search', 'memory']);
|
|
760
|
+
const { checkPreHooks, runPostHooks, permissionSummary } = require('../utils/tool-hooks');
|
|
761
|
+
const { checkPermission, isApproved, markApproved, formatPermissionPrompt } = require('../utils/permissions');
|
|
762
|
+
const { getPlanMode } = require('../utils/plan-mode');
|
|
763
|
+
const { getWorktree } = require('../utils/worktree');
|
|
764
|
+
const { getLevel: getEffortLevel, getConfig: getEffortConfig } = require('../utils/effort-levels');
|
|
765
|
+
const { getResponseFormat, hasStructuredOutput } = require('../utils/structured-output');
|
|
766
|
+
const { getFallbackChain } = require('../utils/fallback-chain');
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Prompt user for permission approval.
|
|
770
|
+
* Returns: true (once), 'session', 'persistent', or false (denied).
|
|
771
|
+
*/
|
|
772
|
+
async function askPermissionPrompt(question, hint, prompter) {
|
|
773
|
+
const full = `${tui.C.yellow('⚠')} ${tui.C.bold('İzin gerekiyor')}: ${question}\n${tui.C.muted(hint)}`;
|
|
774
|
+
return new Promise(resolve => {
|
|
775
|
+
prompter(full, answer => {
|
|
776
|
+
const key = answer.trim();
|
|
777
|
+
if (key === 'y') resolve(true); // once
|
|
778
|
+
else if (key === 'Y') resolve('session'); // session
|
|
779
|
+
else if (key === 'p') resolve('persistent'); // disk
|
|
780
|
+
else if (key === 'a') { _permSessionCache.set('__ALL__', true); resolve(true); } // all (legacy)
|
|
781
|
+
else resolve(false); // no
|
|
782
|
+
});
|
|
783
|
+
});
|
|
784
|
+
}
|
|
454
785
|
|
|
455
|
-
|
|
786
|
+
/** Session permission cache (for ask hooks) */
|
|
787
|
+
const _permSessionCache = new Map();
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
async function processToolCalls(toolCalls, onToolCall, onAsk) {
|
|
456
791
|
const toolDefs = getToolDefs();
|
|
457
792
|
const results = [];
|
|
458
793
|
|
|
@@ -492,23 +827,79 @@ async function processToolCalls(toolCalls, onToolCall) {
|
|
|
492
827
|
if (onToolCall) onToolCall({ name: p.name, args: p.args, status: 'running' });
|
|
493
828
|
}
|
|
494
829
|
|
|
830
|
+
// Shared tool execution with: planCheck → permissions → hooks → execute → post-hooks
|
|
831
|
+
async function executeOne(p) {
|
|
832
|
+
// 0. Plan mode check
|
|
833
|
+
const pm = getPlanMode();
|
|
834
|
+
const planCheck = pm.checkTool(p.name, p.args);
|
|
835
|
+
if (!planCheck.allowed) {
|
|
836
|
+
const denied = { ...p, result: { error: planCheck.reason }, _plan_blocked: true };
|
|
837
|
+
if (onToolCall) onToolCall({ name: p.name, args: p.args, status: 'done', result: { error: planCheck.reason } });
|
|
838
|
+
return denied;
|
|
839
|
+
}
|
|
840
|
+
pm.recordTool(p.name, p.args);
|
|
841
|
+
|
|
842
|
+
// 1. Permission check (config-based granular rules)
|
|
843
|
+
const perm = checkPermission(p.name, p.args);
|
|
844
|
+
if (perm.action === 'deny') {
|
|
845
|
+
const denied = { ...p, result: { error: perm.reason }, _perm_blocked: true };
|
|
846
|
+
if (onToolCall) onToolCall({ name: p.name, args: p.args, status: 'done', result: { error: perm.reason } });
|
|
847
|
+
return denied;
|
|
848
|
+
}
|
|
849
|
+
if (perm.action === 'ask') {
|
|
850
|
+
const permKey = `${perm.rule.raw}:${JSON.stringify(p.args)}`;
|
|
851
|
+
if (!isApproved(permKey)) {
|
|
852
|
+
const ok = await askPermissionPrompt(
|
|
853
|
+
`${formatPermissionPrompt(p.name, p.args, perm.reason)}\n ${perm.reason}`,
|
|
854
|
+
`Bu işleme izin ver? [y=once, Y=session, p=persistent, n=no] `,
|
|
855
|
+
onAsk
|
|
856
|
+
);
|
|
857
|
+
if (ok === 'persistent') markApproved(permKey, true);
|
|
858
|
+
else if (ok === 'session') markApproved(permKey, false);
|
|
859
|
+
else if (!ok) {
|
|
860
|
+
const denied = { ...p, result: { error: `İzin reddedildi: ${perm.rule.raw}` }, _perm_blocked: true };
|
|
861
|
+
if (onToolCall) onToolCall({ name: p.name, args: p.args, status: 'done', result: { error: `İzin reddedildi: ${perm.rule.raw}` } });
|
|
862
|
+
return denied;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// 2. Pre-hook check
|
|
868
|
+
const hook = checkPreHooks(p.name, p.args);
|
|
869
|
+
if (hook.action === 'deny') {
|
|
870
|
+
const denied = { ...p, result: { error: hook.reason }, _hook_blocked: true };
|
|
871
|
+
if (onToolCall) onToolCall({ name: p.name, args: p.args, status: 'done', result: { error: hook.reason } });
|
|
872
|
+
return denied;
|
|
873
|
+
}
|
|
874
|
+
if (hook.action === 'ask') {
|
|
875
|
+
const ok = await askPermissionPrompt(
|
|
876
|
+
permissionSummary(hook.rule, p.name, p.args),
|
|
877
|
+
`Hook onayı [y=once, Y=session, n=no]: `,
|
|
878
|
+
onAsk
|
|
879
|
+
);
|
|
880
|
+
if (!ok) {
|
|
881
|
+
const denied = { ...p, result: { error: `Hook reddetti: ${hook.rule.raw}` }, _hook_blocked: true };
|
|
882
|
+
if (onToolCall) onToolCall({ name: p.name, args: p.args, status: 'done', result: { error: `Hook reddetti: ${hook.rule.raw}` } });
|
|
883
|
+
return denied;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const result = await executeTool(p.name, p.args, toolDefs);
|
|
888
|
+
const success = result?.success !== false;
|
|
889
|
+
guardrails.record(p.name, p.args, success);
|
|
890
|
+
return { ...p, result: runPostHooks(p.name, p.args, result) };
|
|
891
|
+
}
|
|
892
|
+
|
|
495
893
|
// Run parallel batch concurrently
|
|
496
894
|
if (parallelBatch.length > 0) {
|
|
497
|
-
const parallelResults = await Promise.all(parallelBatch.map(
|
|
498
|
-
const result = await executeTool(p.name, p.args, toolDefs);
|
|
499
|
-
const success = result?.success !== false;
|
|
500
|
-
guardrails.record(p.name, p.args, success);
|
|
501
|
-
return { ...p, result };
|
|
502
|
-
}));
|
|
895
|
+
const parallelResults = await Promise.all(parallelBatch.map(executeOne));
|
|
503
896
|
results.push(...parallelResults);
|
|
504
897
|
}
|
|
505
898
|
|
|
506
899
|
// Run sequential batch one at a time
|
|
507
900
|
for (const p of sequentialBatch) {
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
guardrails.record(p.name, p.args, success);
|
|
511
|
-
results.push({ ...p, result });
|
|
901
|
+
const r = await executeOne(p);
|
|
902
|
+
results.push(r);
|
|
512
903
|
}
|
|
513
904
|
|
|
514
905
|
// No-progress check: if all tools failed, inject warning
|
|
@@ -576,6 +967,7 @@ function printHelp() {
|
|
|
576
967
|
console.log(' ' + chalk.yellow('/clear'.padEnd(22)) + chalk.gray('Ekranı temizle'));
|
|
577
968
|
console.log(' ' + chalk.yellow('/history'.padEnd(22)) + chalk.gray('Bu oturumun geçmişi'));
|
|
578
969
|
console.log(' ' + chalk.yellow('/memory'.padEnd(22)) + chalk.gray('Memory\'i göster'));
|
|
970
|
+
console.log(' ' + chalk.yellow('/plan [on|off|show]'.padEnd(22)) + chalk.gray('Plan modu'));
|
|
579
971
|
console.log(' ' + chalk.yellow('/forget'.padEnd(22)) + chalk.gray('Memory\'i temizle'));
|
|
580
972
|
console.log(' ' + chalk.yellow('/sessions'.padEnd(22)) + chalk.gray('Geçmiş oturumları listele'));
|
|
581
973
|
console.log(' ' + chalk.yellow('/resume [id|last]'.padEnd(22)) + chalk.gray('Önceki oturuma dön'));
|
|
@@ -696,10 +1088,16 @@ async function startRepl(args) {
|
|
|
696
1088
|
(cfg.providerUrl || '').includes('mistral.ai') ||
|
|
697
1089
|
(cfg.providerUrl || '').includes('localhost') ||
|
|
698
1090
|
(cfg.providerUrl || '').includes('ollama');
|
|
1091
|
+
// Discover project rules (CLAUDE.md)
|
|
1092
|
+
const projectRules = discoverProjectRules(process.cwd());
|
|
1093
|
+
if (projectRules) {
|
|
1094
|
+
console.log(chalk.cyan(` 📋 Proje kurallari bulundu (CLAUDE.md)\n`));
|
|
1095
|
+
}
|
|
1096
|
+
|
|
699
1097
|
// Build system prompt with tier caching (stable+context cached, volatile fresh)
|
|
700
1098
|
const promptOpts = {
|
|
701
1099
|
botName, userName, soulSummary, isSmallModel,
|
|
702
|
-
memorySnapshotBlock, skillsIndexBlock,
|
|
1100
|
+
memorySnapshotBlock, skillsIndexBlock, projectRules,
|
|
703
1101
|
crossSessionContext: crossSessionContext || '',
|
|
704
1102
|
userHome: cfg.userHome || '',
|
|
705
1103
|
hasHistory: messages.length > 0,
|
|
@@ -980,6 +1378,29 @@ async function startRepl(args) {
|
|
|
980
1378
|
case 'tokens':
|
|
981
1379
|
console.log(chalk.gray(` Token: ~${totalInputTokens} in / ~${totalOutputTokens} out`));
|
|
982
1380
|
break;
|
|
1381
|
+
case 'plan':
|
|
1382
|
+
if (arg === 'on' || arg === 'enter') {
|
|
1383
|
+
if (getPlanMode().enter()) console.log(tui.C.cyan('\n 📋 Plan modu aktif. Plan yapın ve /plan off ile çıkın.\n'));
|
|
1384
|
+
else console.log(tui.C.yellow(' Zaten plan modunda.'));
|
|
1385
|
+
} else if (arg === 'off' || arg === 'exit') {
|
|
1386
|
+
if (getPlanMode().isPlanning()) {
|
|
1387
|
+
console.log(tui.C.yellow(' Plan modundan çıkılıyor. Plan yazılıp ExitPlanMode ile sunulmalı.'));
|
|
1388
|
+
getPlanMode().approve();
|
|
1389
|
+
} else {
|
|
1390
|
+
console.log(tui.C.yellow(' Plan modunda değil.'));
|
|
1391
|
+
}
|
|
1392
|
+
} else if (arg === 'show') {
|
|
1393
|
+
if (getPlanMode().planHistory.length > 0) {
|
|
1394
|
+
const last = getPlanMode().planHistory[getPlanMode().planHistory.length - 1];
|
|
1395
|
+
console.log(tui.C.cyan('\n 📋 Son Plan:\n'));
|
|
1396
|
+
console.log(` ${last.plan.replace(/\n/g, '\n ')}`);
|
|
1397
|
+
} else {
|
|
1398
|
+
console.log(tui.C.yellow(' Henüz plan yok.'));
|
|
1399
|
+
}
|
|
1400
|
+
} else {
|
|
1401
|
+
console.log(tui.C.yellow(' Kullanım: /plan on|off|show'));
|
|
1402
|
+
}
|
|
1403
|
+
break;
|
|
983
1404
|
case 'save':
|
|
984
1405
|
const sessId = saveSession(messages, {
|
|
985
1406
|
provider: providerUrl, model, user: cfg.userName, bot: memory.botName,
|
|
@@ -1044,12 +1465,13 @@ async function startRepl(args) {
|
|
|
1044
1465
|
const wfResult = await executeTool('workflow', { action: 'run', task: line }, wfToolDefs);
|
|
1045
1466
|
const wf = wfResult?.result || {};
|
|
1046
1467
|
if (wf.success !== false) {
|
|
1047
|
-
|
|
1468
|
+
const loaded = wf.skillsLoaded && wf.skillsLoaded.length > 0 ? ` [skill: ${wf.skillsLoaded.join(', ')}]` : '';
|
|
1469
|
+
process.stdout.write(tui.styled(` ✓ workflow${loaded}\n`, { color: tui.PALETTE.success }));
|
|
1048
1470
|
} else {
|
|
1049
1471
|
process.stdout.write(tui.styled(' ✗ workflow\n', { color: tui.PALETTE.danger }));
|
|
1050
1472
|
}
|
|
1051
1473
|
|
|
1052
|
-
if (wf.passthrough && wf.reply) {
|
|
1474
|
+
if (wf.passthrough && wf.reply !== undefined && wf.reply !== null) {
|
|
1053
1475
|
// Simple chat — workflow handled it directly
|
|
1054
1476
|
const fullReply = String(wf.reply);
|
|
1055
1477
|
const displayBotName = memory.botName || 'Asistan';
|
|
@@ -1082,16 +1504,18 @@ async function startRepl(args) {
|
|
|
1082
1504
|
}
|
|
1083
1505
|
return ` ${s} ${t}: ${summary}`;
|
|
1084
1506
|
}).join('\n');
|
|
1507
|
+
const skillInfo = wf.skillsLoaded && wf.skillsLoaded.length > 0
|
|
1508
|
+
? `\n\nKullanilan skill'ler: ${wf.skillsLoaded.join(', ')}`
|
|
1509
|
+
: '';
|
|
1510
|
+
const preWfLen = messages.length;
|
|
1085
1511
|
messages.push({
|
|
1086
1512
|
role: 'system',
|
|
1087
|
-
content: `=== WORKFLOW SONUCLARI ===\nSu araclar calisti:\n${report}\n\nKullaniciya bu sonuclari anlamli bir sekilde ozetle.\n=== SONUC BITTI ===`,
|
|
1088
|
-
_internal: true
|
|
1513
|
+
content: `=== WORKFLOW SONUCLARI ===\nSu araclar calisti:\n${report}${skillInfo}\n\nKullaniciya bu sonuclari anlamli bir sekilde ozetle.\n=== SONUC BITTI ===`,
|
|
1089
1514
|
});
|
|
1090
|
-
const apiMessages = messages.filter(m => !m._internal);
|
|
1091
1515
|
const reply = await sendStreaming(
|
|
1092
1516
|
providerUrl,
|
|
1093
1517
|
providerApiKey,
|
|
1094
|
-
|
|
1518
|
+
messages,
|
|
1095
1519
|
model,
|
|
1096
1520
|
// v5.6.12: Callback bos - tam metin 'reply' olarak gelecek (non-stream mode)
|
|
1097
1521
|
() => {},
|
|
@@ -1109,6 +1533,8 @@ async function startRepl(args) {
|
|
|
1109
1533
|
}
|
|
1110
1534
|
})
|
|
1111
1535
|
);
|
|
1536
|
+
// Remove workflow results message (already served its purpose)
|
|
1537
|
+
messages.splice(preWfLen, 1);
|
|
1112
1538
|
// v5.6.12: Tam metin 'reply' olarak zaten geldi (non-stream mode)
|
|
1113
1539
|
const fullReply = String(reply || '');
|
|
1114
1540
|
// Bot adini al
|
|
@@ -1128,15 +1554,8 @@ async function startRepl(args) {
|
|
|
1128
1554
|
fixedReply = fixedReply.replace(/Ben\s+Asistan[\s\w\.]*/gi, 'Ben ' + displayBotName);
|
|
1129
1555
|
fixedReply = fixedReply.replace(/\*\*(?:MiniMax|Claude|GPT|M2\.5|M2)[^\*]*\*\*/gi, '**' + displayBotName + '**');
|
|
1130
1556
|
process.stdout.write('\n' + fixedReply + '\n');
|
|
1131
|
-
const existingLen = messages.filter(m => !m._internal).length;
|
|
1132
|
-
const toolCallHistory = apiMessages.slice(existingLen);
|
|
1133
|
-
for (const m of toolCallHistory) {
|
|
1134
|
-
if (m.role === 'assistant' || m.role === 'tool') {
|
|
1135
|
-
messages.push(m);
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
1557
|
messages.push({ role: 'assistant', content: fixedReply });
|
|
1139
|
-
totalInputTokens +=
|
|
1558
|
+
totalInputTokens += Math.ceil(((fullReply || '') + report + skillInfo).length / 4);
|
|
1140
1559
|
totalOutputTokens += Math.ceil((fullReply || '').length / 4);
|
|
1141
1560
|
} else {
|
|
1142
1561
|
// Workflow failed or returned unexpected format
|