micro-models-agent 0.60.1 → 0.61.1
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 +601 -584
- package/README.md +358 -358
- package/dist/certification/certifications.json +493 -493
- package/dist/cli/commands.js +447 -0
- package/dist/cli/completer.js +167 -0
- package/dist/cli/index.js +2 -0
- package/dist/cli/main.js +153 -0
- package/dist/cli/plugin-commands.js +36 -0
- package/dist/cli/repl-commands.js +761 -0
- package/dist/cli/repl.js +702 -0
- package/dist/cli/run-result.js +33 -0
- package/dist/cli/security-commands.js +164 -0
- package/dist/cli/setup.js +237 -0
- package/dist/config/config.js +276 -0
- package/dist/config/defaults.js +141 -0
- package/dist/config/domains.js +179 -0
- package/dist/config/experts.js +15 -0
- package/dist/config/index.js +4 -0
- package/dist/config/security.js +213 -0
- package/dist/config/types.js +1 -0
- package/dist/core/agent-moe.js +102 -0
- package/dist/core/agent.js +1018 -0
- package/dist/core/bootstrap.js +481 -0
- package/dist/core/crash-handler.js +51 -0
- package/dist/core/environment.js +199 -0
- package/dist/core/index.js +2 -0
- package/dist/core/prompt-builder.js +76 -0
- package/dist/core/session-logger.js +251 -0
- package/dist/core/types.js +1 -0
- package/dist/core/version.js +26 -0
- package/dist/core/workspace.js +76 -0
- package/dist/i18n/en.json +757 -746
- package/dist/i18n/index.js +46 -0
- package/dist/i18n/ru.json +757 -746
- package/dist/index.js +22 -0
- package/dist/llm/image-utils.js +143 -0
- package/dist/llm/index.js +4 -0
- package/dist/llm/model-loader.js +78 -0
- package/dist/llm/openai-compat.js +497 -0
- package/dist/llm/orchestrator.js +200 -0
- package/dist/llm/provider.js +10 -0
- package/dist/llm/response.js +39 -0
- package/dist/llm/token-counter.js +39 -0
- package/dist/llm/types.js +1 -0
- package/dist/logger/app-logger.js +189 -0
- package/dist/logger/file-log.js +151 -0
- package/dist/logger/index.js +1 -0
- package/dist/main.js +503 -340
- package/dist/migration/backup.js +45 -0
- package/dist/migration/detect.js +50 -0
- package/dist/migration/index.js +2 -0
- package/dist/modules/artifacts/store.js +61 -0
- package/dist/modules/browser/actions.js +76 -0
- package/dist/modules/browser/bridge-client.js +199 -0
- package/dist/modules/browser/bridge-path.js +10 -0
- package/dist/modules/browser/bridge-server.mjs +219 -219
- package/dist/modules/browser/cookie-store.js +24 -0
- package/dist/modules/browser/driver.js +136 -0
- package/dist/modules/browser/index.js +7 -0
- package/dist/modules/browser/module.js +29 -0
- package/dist/modules/browser/session.js +342 -0
- package/dist/modules/browser/snapshot.js +148 -0
- package/dist/modules/browser/types.js +12 -0
- package/dist/modules/certification/cli.js +213 -0
- package/dist/modules/certification/fact-checker.js +82 -0
- package/dist/modules/certification/loader.js +106 -0
- package/dist/modules/certification/manifest.js +58 -0
- package/dist/modules/certification/runner.js +245 -0
- package/dist/modules/certification/scenarios.js +407 -0
- package/dist/modules/certification/types.js +1 -0
- package/dist/modules/context/chunk-query.js +100 -0
- package/dist/modules/context/fact-extractor.js +168 -0
- package/dist/modules/context/history.js +15 -0
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +440 -0
- package/dist/modules/execution/audit-runners.js +206 -0
- package/dist/modules/execution/auditor.js +218 -0
- package/dist/modules/execution/execution-plugin.js +431 -0
- package/dist/modules/execution/index.js +8 -0
- package/dist/modules/execution/module.js +625 -0
- package/dist/modules/execution/moe-executor.js +304 -0
- package/dist/modules/execution/plan-coverage.js +68 -0
- package/dist/modules/execution/plan-persister.js +46 -0
- package/dist/modules/execution/plan-store.js +196 -0
- package/dist/modules/execution/plan-tool.js +677 -0
- package/dist/modules/execution/plan-validator.js +153 -0
- package/dist/modules/execution/planner.js +94 -0
- package/dist/modules/execution/stuck-detector.js +746 -0
- package/dist/modules/execution/tracker.js +69 -0
- package/dist/modules/execution/types.js +1 -0
- package/dist/modules/execution/verifier.js +235 -0
- package/dist/modules/execution/windows-commands.js +41 -0
- package/dist/modules/hallucination/confidence.js +66 -0
- package/dist/modules/hallucination/consistency.js +26 -0
- package/dist/modules/hallucination/detector.js +47 -0
- package/dist/modules/hallucination/factual.js +169 -0
- package/dist/modules/hallucination/index.js +5 -0
- package/dist/modules/hallucination/js-identifiers.js +262 -0
- package/dist/modules/hallucination/llm-judge.js +101 -0
- package/dist/modules/index.js +5 -0
- package/dist/modules/indexer/cache.js +40 -0
- package/dist/modules/indexer/index.js +3 -0
- package/dist/modules/indexer/module.js +246 -0
- package/dist/modules/indexer/project-profile.js +183 -0
- package/dist/modules/indexer/walker.js +101 -0
- package/dist/modules/lsp/check-tool.js +58 -0
- package/dist/modules/lsp/client.js +389 -0
- package/dist/modules/lsp/command.js +60 -0
- package/dist/modules/lsp/config.js +135 -0
- package/dist/modules/lsp/index.js +3 -0
- package/dist/modules/lsp/module.js +260 -0
- package/dist/modules/lsp/probe.js +86 -0
- package/dist/modules/lsp/project-root.js +32 -0
- package/dist/modules/lsp/startup-check.js +144 -0
- package/dist/modules/lsp/types.js +1 -0
- package/dist/modules/mcp/client.js +399 -0
- package/dist/modules/mcp/index.js +3 -0
- package/dist/modules/mcp/module.js +142 -0
- package/dist/modules/mcp/registry.js +15 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +96 -0
- package/dist/modules/memory/search.js +42 -0
- package/dist/modules/memory/store.js +69 -0
- package/dist/modules/pipelines/engine.js +60 -0
- package/dist/modules/pipelines/index.js +3 -0
- package/dist/modules/pipelines/parser.js +56 -0
- package/dist/modules/pipelines/template.js +14 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +334 -0
- package/dist/modules/plugins/builtin/notify.js +9 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +70 -0
- package/dist/modules/plugins/manager.js +261 -0
- package/dist/modules/plugins/types.js +1 -0
- package/dist/modules/pricing/index.js +61 -0
- package/dist/modules/pricing/prices.js +129 -0
- package/dist/modules/processes/detect.js +34 -0
- package/dist/modules/processes/index.js +2 -0
- package/dist/modules/processes/registry.js +327 -0
- package/dist/modules/processes/runner.js +23 -0
- package/dist/modules/providers/create.js +22 -0
- package/dist/modules/providers/fallback.js +79 -0
- package/dist/modules/providers/health.js +46 -0
- package/dist/modules/providers/index.js +5 -0
- package/dist/modules/providers/manager.js +161 -0
- package/dist/modules/providers/presets.js +128 -0
- package/dist/modules/providers/registry.js +22 -0
- package/dist/modules/providers/types.js +1 -0
- package/dist/modules/registry.js +48 -0
- package/dist/modules/security/audit-log.js +136 -0
- package/dist/modules/security/audit-notifier.js +292 -0
- package/dist/modules/security/command-validator.js +219 -0
- package/dist/modules/security/content-scanner.js +53 -0
- package/dist/modules/security/data-sanitizer.js +89 -0
- package/dist/modules/security/encryption.js +242 -0
- package/dist/modules/security/index.js +14 -0
- package/dist/modules/security/network-validator.js +88 -0
- package/dist/modules/security/path-validator.js +203 -0
- package/dist/modules/security/rate-limiter.js +119 -0
- package/dist/modules/security/security-policies.js +531 -0
- package/dist/modules/security/session-encryption.js +210 -0
- package/dist/modules/security/session-isolation.js +95 -0
- package/dist/modules/session/index.js +3 -0
- package/dist/modules/session/manager.js +172 -0
- package/dist/modules/session/module.js +24 -0
- package/dist/modules/session/store.js +222 -0
- package/dist/modules/session/types.js +1 -0
- package/dist/modules/skills/index.js +2 -0
- package/dist/modules/skills/loader.js +72 -0
- package/dist/modules/skills/matcher.js +27 -0
- package/dist/modules/skills/module.js +129 -0
- package/dist/modules/types.js +1 -0
- package/dist/modules/updater/checker.js +96 -0
- package/dist/modules/updater/index.js +2 -0
- package/dist/modules/updater/module.js +116 -0
- package/dist/modules/user-profile/compressor.js +16 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +68 -0
- package/dist/skills/builtin/git.md +36 -36
- package/dist/skills/builtin/typescript.md +35 -35
- package/dist/tools/approve.js +33 -0
- package/dist/tools/attach-image.js +101 -0
- package/dist/tools/bash.js +519 -0
- package/dist/tools/browser.js +115 -0
- package/dist/tools/chunk-query.js +100 -0
- package/dist/tools/create-dir.js +56 -0
- package/dist/tools/delete-file.js +63 -0
- package/dist/tools/download-file.js +117 -0
- package/dist/tools/edit-file.js +80 -0
- package/dist/tools/enable-tools.js +59 -0
- package/dist/tools/executor.js +154 -0
- package/dist/tools/file-info.js +47 -0
- package/dist/tools/filter-tools.js +17 -0
- package/dist/tools/glob-tool.js +27 -0
- package/dist/tools/grep-tool.js +125 -0
- package/dist/tools/hidden-tools-block.js +37 -0
- package/dist/tools/index.js +78 -0
- package/dist/tools/list-dir.js +49 -0
- package/dist/tools/load-skill.js +43 -0
- package/dist/tools/mcp-call.js +69 -0
- package/dist/tools/move-file.js +86 -0
- package/dist/tools/path-utils.js +101 -0
- package/dist/tools/pipeline-run.js +145 -0
- package/dist/tools/preview.js +2 -0
- package/dist/tools/process-kill.js +40 -0
- package/dist/tools/process-list.js +37 -0
- package/dist/tools/process-log.js +54 -0
- package/dist/tools/question.js +141 -0
- package/dist/tools/read-file.js +179 -0
- package/dist/tools/recall.js +118 -0
- package/dist/tools/registry.js +47 -0
- package/dist/tools/remember.js +68 -0
- package/dist/tools/scope-check.js +32 -0
- package/dist/tools/search-history.js +85 -0
- package/dist/tools/subagent.js +196 -0
- package/dist/tools/types.js +1 -0
- package/dist/tools/user-input.js +123 -0
- package/dist/tools/web-browse.js +87 -0
- package/dist/tools/web-fetch.js +119 -0
- package/dist/tools/web-search.js +105 -0
- package/dist/tools/write-file.js +82 -0
- package/dist/ui/box.js +77 -0
- package/dist/ui/colors.js +4 -0
- package/dist/ui/diff.js +178 -0
- package/dist/ui/index.js +6 -0
- package/dist/ui/line-editor.js +822 -0
- package/dist/ui/line-math.js +73 -0
- package/dist/ui/md-formatter.js +212 -0
- package/dist/ui/output.js +13 -0
- package/dist/ui/plan-view.js +103 -0
- package/dist/ui/renderer.js +259 -0
- package/dist/ui/spinner.js +70 -0
- package/dist/ui/table.js +144 -0
- package/package.json +51 -51
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { resolve, normalize, dirname, basename, sep, relative, isAbsolute } from "path";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
/**
|
|
4
|
+
* True when targetPath equals dirPath or lies inside it.
|
|
5
|
+
*
|
|
6
|
+
* Cross-platform notes:
|
|
7
|
+
* - Uses path.relative() instead of raw startsWith, so a sibling directory
|
|
8
|
+
* sharing the string prefix ("src" vs "src-other") can never pass.
|
|
9
|
+
* - On Windows the filesystem is case-insensitive: both sides are lowercased
|
|
10
|
+
* before comparison so "E:\Proj" and "e:\proj" resolve to the same dir.
|
|
11
|
+
*/
|
|
12
|
+
export function isInsideDir(targetPath, dirPath) {
|
|
13
|
+
const norm = (p) => (process.platform === "win32" ? p.toLowerCase() : p);
|
|
14
|
+
const t = norm(resolve(targetPath));
|
|
15
|
+
const d = norm(resolve(dirPath));
|
|
16
|
+
if (t === d)
|
|
17
|
+
return true;
|
|
18
|
+
const rel = relative(d, t);
|
|
19
|
+
return rel.length > 0 && !isAbsolute(rel) && !rel.startsWith("..");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* True when targetPath matches a scope entry: either the entry file itself or
|
|
23
|
+
* anything inside the entry directory. Separator- and case-safe (see
|
|
24
|
+
* isInsideDir).
|
|
25
|
+
*/
|
|
26
|
+
export function matchesScopeEntry(targetResolved, entryResolved) {
|
|
27
|
+
if (targetResolved === entryResolved)
|
|
28
|
+
return true;
|
|
29
|
+
return isInsideDir(targetResolved, entryResolved);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a user-provided path against baseDir.
|
|
33
|
+
*
|
|
34
|
+
* Handles common model mistakes:
|
|
35
|
+
* 1. Leading slash: "/testing/cat3" → resolve relative to baseDir
|
|
36
|
+
* 2. Missing separator: "E:\agent_test" when baseDir is "E:\agent\_test"
|
|
37
|
+
* → inserts the missing backslash
|
|
38
|
+
*
|
|
39
|
+
* For new files the parent directory must exist; we check that instead of
|
|
40
|
+
* the full path so write_file still works.
|
|
41
|
+
*/
|
|
42
|
+
export function safeResolvePath(baseDir, userPath) {
|
|
43
|
+
// Absolute path that actually exists — honor it as-is
|
|
44
|
+
const asIs = resolve(normalize(userPath));
|
|
45
|
+
if (userPath.startsWith("/") || userPath.startsWith("\\")) {
|
|
46
|
+
if (existsSync(asIs) || existsSync(dirname(asIs)))
|
|
47
|
+
return asIs;
|
|
48
|
+
}
|
|
49
|
+
const norm = normalize(userPath);
|
|
50
|
+
// Absolute paths are used verbatim — do NOT strip a leading slash and
|
|
51
|
+
// re-resolve against baseDir (that would turn /var/folders/... into
|
|
52
|
+
// baseDir/var/folders/... on POSIX). The leading-slash heuristic below
|
|
53
|
+
// only applies as a fallback when the absolute path does not resolve.
|
|
54
|
+
if (isAbsolute(norm)) {
|
|
55
|
+
if (existsSync(norm) || existsSync(dirname(norm)))
|
|
56
|
+
return norm;
|
|
57
|
+
// Absolute but not on disk yet (e.g. a new file whose dirs don't exist).
|
|
58
|
+
// Model mistake recovery: a leading slash may mean "relative to baseDir".
|
|
59
|
+
const stripped = norm.replace(/^[/\\]/, "");
|
|
60
|
+
const relativeCandidate = resolve(baseDir, stripped);
|
|
61
|
+
if (existsSync(relativeCandidate) || existsSync(dirname(relativeCandidate))) {
|
|
62
|
+
return relativeCandidate;
|
|
63
|
+
}
|
|
64
|
+
return norm;
|
|
65
|
+
}
|
|
66
|
+
const stripped = norm.replace(/^[/\\]/, "");
|
|
67
|
+
const resolved = resolve(baseDir, stripped);
|
|
68
|
+
// Fast path: if it exists (or parent does for new files), return it
|
|
69
|
+
if (existsSync(resolved) || existsSync(dirname(resolved)))
|
|
70
|
+
return resolved;
|
|
71
|
+
const baseNorm = normalize(baseDir);
|
|
72
|
+
// Walk ancestors and look for missing separators
|
|
73
|
+
let cur = baseNorm;
|
|
74
|
+
while (cur && cur !== dirname(cur)) {
|
|
75
|
+
const name = basename(cur);
|
|
76
|
+
if (!name) {
|
|
77
|
+
cur = dirname(cur);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
let idx = stripped.toLowerCase().indexOf(name.toLowerCase());
|
|
81
|
+
while (idx >= 0) {
|
|
82
|
+
const afterIdx = idx + name.length;
|
|
83
|
+
const afterChar = stripped[afterIdx];
|
|
84
|
+
if (afterChar && afterChar !== "\\" && afterChar !== "/") {
|
|
85
|
+
const fixed = stripped.slice(0, afterIdx) + sep + stripped.slice(afterIdx);
|
|
86
|
+
const fixedResolved = resolve(baseDir, normalize(fixed));
|
|
87
|
+
if (existsSync(fixedResolved) || existsSync(dirname(fixedResolved))) {
|
|
88
|
+
return fixedResolved;
|
|
89
|
+
}
|
|
90
|
+
// Try from ancestor's parent
|
|
91
|
+
const fromParent = resolve(dirname(cur), normalize(fixed));
|
|
92
|
+
if (existsSync(fromParent) || existsSync(dirname(fromParent))) {
|
|
93
|
+
return fromParent;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
idx = stripped.toLowerCase().indexOf(name.toLowerCase(), idx + 1);
|
|
97
|
+
}
|
|
98
|
+
cur = dirname(cur);
|
|
99
|
+
}
|
|
100
|
+
return resolved;
|
|
101
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { t } from "../i18n/index";
|
|
2
|
+
import { PipelineEngine } from "../modules/pipelines/engine";
|
|
3
|
+
import { PipelineParser } from "../modules/pipelines/parser";
|
|
4
|
+
import { TemplateEngine } from "../modules/pipelines/template";
|
|
5
|
+
import { Agent } from "../core/agent";
|
|
6
|
+
import { ContextManager } from "../modules/context/manager";
|
|
7
|
+
import { PluginManager } from "../modules/plugins/manager";
|
|
8
|
+
import { HallucinationDetector } from "../modules/hallucination/detector";
|
|
9
|
+
import { logSecurityBlock } from "../modules/security/audit-log";
|
|
10
|
+
import { getSessionSecurityConfig } from "../modules/security/session-isolation";
|
|
11
|
+
const engine = new PipelineEngine();
|
|
12
|
+
const MAX_CONCURRENT = 3;
|
|
13
|
+
const MAX_ATTEMPTS = 3;
|
|
14
|
+
async function runStep(ctx, step, params, outputs) {
|
|
15
|
+
const prompt = TemplateEngine.render(step.prompt, params, outputs);
|
|
16
|
+
let lastError = "";
|
|
17
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
|
18
|
+
try {
|
|
19
|
+
const subContextManager = new ContextManager(ctx.config.contextWindow, ctx.config.contextBudget);
|
|
20
|
+
const subPluginManager = new PluginManager();
|
|
21
|
+
const systemPrompt = {
|
|
22
|
+
content: `You are a pipeline step agent ("${step.agent}"). Complete the given task using the available tools.`,
|
|
23
|
+
priority: "critical",
|
|
24
|
+
essential: true,
|
|
25
|
+
estimatedTokens: 80,
|
|
26
|
+
};
|
|
27
|
+
const subDeps = {
|
|
28
|
+
config: ctx.config,
|
|
29
|
+
llmProvider: ctx.llmProvider,
|
|
30
|
+
toolExecutor: ctx.toolExecutor,
|
|
31
|
+
pluginManager: subPluginManager,
|
|
32
|
+
contextManager: subContextManager,
|
|
33
|
+
hallucinationDetector: new HallucinationDetector(ctx.baseDir, ctx.llmProvider),
|
|
34
|
+
logger: ctx.logger,
|
|
35
|
+
baseDir: ctx.baseDir,
|
|
36
|
+
scope: ctx.scope,
|
|
37
|
+
recursionDepth: (ctx.recursionDepth ?? 0) + 1,
|
|
38
|
+
promptBlocks: [systemPrompt],
|
|
39
|
+
};
|
|
40
|
+
const subAgent = new Agent(subDeps);
|
|
41
|
+
const result = await subAgent.run(prompt);
|
|
42
|
+
if (result.success) {
|
|
43
|
+
return { ok: true, output: result.text };
|
|
44
|
+
}
|
|
45
|
+
lastError = result.error || "no output";
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
lastError = e.message;
|
|
49
|
+
}
|
|
50
|
+
await new Promise((r) => setTimeout(r, 500 * attempt));
|
|
51
|
+
}
|
|
52
|
+
return { ok: false, output: "", error: lastError };
|
|
53
|
+
}
|
|
54
|
+
export const pipelineRunTool = {
|
|
55
|
+
name: "pipeline_run",
|
|
56
|
+
icon: "▶️",
|
|
57
|
+
description: "Run a named pipeline with YAML definition. Creates a DAG of sub-agents that execute in dependency order.",
|
|
58
|
+
tags: ["shell", "code"],
|
|
59
|
+
parameters: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
name: { type: "string", description: "Pipeline name" },
|
|
63
|
+
yaml: { type: "string", description: "Pipeline YAML definition with steps" },
|
|
64
|
+
params: { type: "object", description: "Template params for {key} placeholders" },
|
|
65
|
+
},
|
|
66
|
+
required: ["name", "yaml"],
|
|
67
|
+
},
|
|
68
|
+
handler: async (ctx, args) => {
|
|
69
|
+
const name = String(args.name || "");
|
|
70
|
+
const yaml = String(args.yaml || "");
|
|
71
|
+
const params = args.params || {};
|
|
72
|
+
if (!yaml) {
|
|
73
|
+
return { success: false, output: t("pipeline.invalid") };
|
|
74
|
+
}
|
|
75
|
+
if (!ctx.llmProvider || !ctx.toolExecutor) {
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
output: "Pipeline cannot run: missing llmProvider or toolExecutor in context",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const securityConfig = ctx.sessionContext
|
|
82
|
+
? getSessionSecurityConfig(ctx.config, ctx.sessionContext)
|
|
83
|
+
: ctx.config.security;
|
|
84
|
+
const maxDepth = securityConfig?.maxRecursionDepth ?? 3;
|
|
85
|
+
const currentDepth = ctx.recursionDepth ?? 0;
|
|
86
|
+
if (currentDepth >= maxDepth) {
|
|
87
|
+
logSecurityBlock(ctx.sessionId, "bash_command", `Maximum recursion depth (${maxDepth}) exceeded`, name);
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
output: `[SECURITY BLOCKED] Maximum pipeline recursion depth (${maxDepth}) exceeded`,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const pipeline = PipelineParser.parse(yaml);
|
|
95
|
+
engine.reset();
|
|
96
|
+
const order = engine.resolveDependencies(pipeline.steps);
|
|
97
|
+
const completed = new Set();
|
|
98
|
+
const outputs = {};
|
|
99
|
+
const logs = [];
|
|
100
|
+
const failed = new Set();
|
|
101
|
+
while (completed.size < pipeline.steps.length) {
|
|
102
|
+
const ready = engine
|
|
103
|
+
.getReadySteps(pipeline.steps, completed)
|
|
104
|
+
.filter((s) => !failed.has(s.id));
|
|
105
|
+
if (ready.length === 0) {
|
|
106
|
+
if (failed.size > 0)
|
|
107
|
+
break;
|
|
108
|
+
throw new Error(t("pipeline.circular", { stepId: order.join(", ") }));
|
|
109
|
+
}
|
|
110
|
+
const batch = ready.slice(0, MAX_CONCURRENT);
|
|
111
|
+
const results = await Promise.all(batch.map((step) => runStep(ctx, step, params, outputs)));
|
|
112
|
+
for (let i = 0; i < batch.length; i++) {
|
|
113
|
+
const step = batch[i];
|
|
114
|
+
const result = results[i];
|
|
115
|
+
if (result.ok) {
|
|
116
|
+
completed.add(step.id);
|
|
117
|
+
outputs[step.id] = { output: result.output };
|
|
118
|
+
engine.setStepStatus(step.id, "done");
|
|
119
|
+
logs.push(` ✓ ${step.id}: ${result.output.split("\n")[0]}`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
failed.add(step.id);
|
|
123
|
+
engine.setStepStatus(step.id, "failed");
|
|
124
|
+
logs.push(` ✗ ${step.id}: ${result.error}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (failed.size > 0)
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
if (failed.size > 0) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
output: `Pipeline "${pipeline.name}" failed. Executed ${completed.size}/${pipeline.steps.length} steps.\n${logs.join("\n")}`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
output: `Pipeline "${pipeline.name}" completed successfully (${completed.size} steps).\n${logs.join("\n")}`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
return { success: false, output: `Pipeline error: ${e.message}` };
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { processRegistry } from "../modules/processes";
|
|
2
|
+
import { t } from "../i18n/index";
|
|
3
|
+
export const processKillTool = {
|
|
4
|
+
name: "process_kill",
|
|
5
|
+
icon: "⛔",
|
|
6
|
+
description: "Stop a background process started via bash (dev server, watcher). Kills the whole process tree (children included). Use the id returned by bash or process_list.",
|
|
7
|
+
tags: ["shell"],
|
|
8
|
+
parameters: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
id: { type: "string", description: "Process id from bash output or process_list" },
|
|
12
|
+
},
|
|
13
|
+
required: ["id"],
|
|
14
|
+
},
|
|
15
|
+
handler: async (ctx, args) => {
|
|
16
|
+
if (args.id === undefined || String(args.id).trim() === "") {
|
|
17
|
+
// ses_mt4fn58c: `process_kill -p=5173` produced "Process not found:
|
|
18
|
+
// undefined" — no hint about the correct format. Answer with usage.
|
|
19
|
+
const known = processRegistry.list();
|
|
20
|
+
const ids = known.map((p) => p.id);
|
|
21
|
+
const listPart = ids.length
|
|
22
|
+
? `\n${t("proc.list_header")}: ${ids.join(", ")}`
|
|
23
|
+
: `\n${t("proc.missing_id_list")}`;
|
|
24
|
+
return { success: false, output: `${t("proc.missing_id")}${listPart}` };
|
|
25
|
+
}
|
|
26
|
+
const id = String(args.id);
|
|
27
|
+
const entry = processRegistry.get(id);
|
|
28
|
+
if (!entry) {
|
|
29
|
+
return { success: false, output: t("proc.not_found", { id }) };
|
|
30
|
+
}
|
|
31
|
+
const killed = processRegistry.kill(id);
|
|
32
|
+
if (!killed) {
|
|
33
|
+
return { success: false, output: t("proc.kill_failed", { id }) };
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
success: true,
|
|
37
|
+
output: t("proc.killed", { id, pid: entry.pid }),
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { processRegistry } from "../modules/processes";
|
|
2
|
+
import { t } from "../i18n/index";
|
|
3
|
+
export const processListTool = {
|
|
4
|
+
name: "process_list",
|
|
5
|
+
icon: "📊",
|
|
6
|
+
description: "List background processes started via the bash tool (dev servers, watchers, long-running commands). Shows id, pid, command, status, and recent output. Use with process_log and process_kill to inspect or stop them.",
|
|
7
|
+
tags: ["shell"],
|
|
8
|
+
parameters: {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {},
|
|
11
|
+
},
|
|
12
|
+
handler: async (ctx) => {
|
|
13
|
+
const list = processRegistry.list(ctx.sessionId);
|
|
14
|
+
if (list.length === 0) {
|
|
15
|
+
return { success: true, output: t("proc.none") };
|
|
16
|
+
}
|
|
17
|
+
const statusLabel = (status) => {
|
|
18
|
+
if (status === "running")
|
|
19
|
+
return t("proc.status_running");
|
|
20
|
+
if (status === "exited")
|
|
21
|
+
return t("proc.status_exited");
|
|
22
|
+
return t("proc.status_killed");
|
|
23
|
+
};
|
|
24
|
+
const lines = [`${t("proc.list_header")} (${list.length}):`];
|
|
25
|
+
for (const entry of list) {
|
|
26
|
+
const tail = entry.log.length > 0 ? entry.log[entry.log.length - 1] : "";
|
|
27
|
+
const detail = tail ? ` — ${tail.slice(0, 80)}${tail.length > 80 ? "…" : ""}` : "";
|
|
28
|
+
lines.push(` ${entry.id} PID ${entry.pid} ${statusLabel(entry.status)} ${entry.command}${detail}`);
|
|
29
|
+
}
|
|
30
|
+
lines.push(`\n${t("proc.hint", {
|
|
31
|
+
list: "process_list",
|
|
32
|
+
log: "process_log",
|
|
33
|
+
kill: "process_kill",
|
|
34
|
+
})}`);
|
|
35
|
+
return { success: true, output: lines.join("\n") };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { processRegistry } from "../modules/processes";
|
|
2
|
+
import { t } from "../i18n/index";
|
|
3
|
+
import { MAX_PREVIEW_LINES } from "./preview";
|
|
4
|
+
const DEFAULT_TAIL = MAX_PREVIEW_LINES;
|
|
5
|
+
export const processLogTool = {
|
|
6
|
+
name: "process_log",
|
|
7
|
+
icon: "📜",
|
|
8
|
+
description: `Show the buffered output of a background process started via bash. Shows the last ${DEFAULT_TAIL} lines by default. Use after starting a dev server to verify it came up without errors, and while it runs to check its state.`,
|
|
9
|
+
tags: ["shell"],
|
|
10
|
+
boundedOutput: true,
|
|
11
|
+
parameters: {
|
|
12
|
+
type: "object",
|
|
13
|
+
properties: {
|
|
14
|
+
id: { type: "string", description: "Process id from bash output or process_list" },
|
|
15
|
+
tail: {
|
|
16
|
+
type: "number",
|
|
17
|
+
description: `Number of trailing lines to show (default: ${DEFAULT_TAIL}, max 300)`,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
required: ["id"],
|
|
21
|
+
},
|
|
22
|
+
handler: async (ctx, args) => {
|
|
23
|
+
if (args.id === undefined || String(args.id).trim() === "") {
|
|
24
|
+
const known = processRegistry.list();
|
|
25
|
+
const ids = known.map((p) => p.id);
|
|
26
|
+
const listPart = ids.length
|
|
27
|
+
? `\n${t("proc.list_header")}: ${ids.join(", ")}`
|
|
28
|
+
: `\n${t("proc.missing_id_list")}`;
|
|
29
|
+
return { success: false, output: `${t("proc.missing_id")}${listPart}` };
|
|
30
|
+
}
|
|
31
|
+
const id = String(args.id);
|
|
32
|
+
const entry = processRegistry.get(id);
|
|
33
|
+
if (!entry) {
|
|
34
|
+
return { success: false, output: t("proc.not_found", { id }) };
|
|
35
|
+
}
|
|
36
|
+
const status = entry.status === "running"
|
|
37
|
+
? t("proc.status_running")
|
|
38
|
+
: entry.status === "exited"
|
|
39
|
+
? t("proc.status_exited")
|
|
40
|
+
: t("proc.status_killed");
|
|
41
|
+
const tail = typeof args.tail === "number" ? args.tail : DEFAULT_TAIL;
|
|
42
|
+
const log = processRegistry.getLog(id, tail);
|
|
43
|
+
if (!log) {
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
output: `${t("proc.log_header", { id, status })} ${t("proc.log_empty")}`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
output: `${t("proc.log_header", { id, status })}\n${log}`,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { t } from "../i18n/index";
|
|
2
|
+
import { askUser } from "./user-input";
|
|
3
|
+
const DESCRIPTION = `Use this tool when you need to ask the user questions during execution. This allows you to:
|
|
4
|
+
1. Gather user preferences or requirements
|
|
5
|
+
2. Clarify ambiguous instructions
|
|
6
|
+
3. Get decisions on implementation choices as you work
|
|
7
|
+
4. Offer choices to the user about what direction to take.
|
|
8
|
+
|
|
9
|
+
Usage notes:
|
|
10
|
+
- When "custom" is enabled (default), a "Type your own answer" option is added automatically; don't include "Other" or catch-all options
|
|
11
|
+
- Answers are returned as arrays of labels; set "multiple": true to allow selecting more than one
|
|
12
|
+
- If you recommend a specific option, make that the first option in the list and add "(Recommended)" at the end of the label
|
|
13
|
+
- Execution blocks until the user answers every question
|
|
14
|
+
- Omit "options" for a free-text question`;
|
|
15
|
+
function isOption(val) {
|
|
16
|
+
if (!val || typeof val !== "object")
|
|
17
|
+
return false;
|
|
18
|
+
const opt = val;
|
|
19
|
+
return typeof opt.label === "string" && typeof opt.description === "string";
|
|
20
|
+
}
|
|
21
|
+
function normalizeQuestions(args) {
|
|
22
|
+
const raw = args.questions;
|
|
23
|
+
if (Array.isArray(raw)) {
|
|
24
|
+
const specs = [];
|
|
25
|
+
for (const item of raw) {
|
|
26
|
+
if (!item || typeof item !== "object")
|
|
27
|
+
continue;
|
|
28
|
+
const q = item;
|
|
29
|
+
if (typeof q.question !== "string" || !q.question.trim())
|
|
30
|
+
continue;
|
|
31
|
+
const options = Array.isArray(q.options) ? q.options.filter(isOption) : undefined;
|
|
32
|
+
// An explicitly provided options array with no valid entries is a
|
|
33
|
+
// malformed call — skip the question instead of blocking on stdin.
|
|
34
|
+
if (Array.isArray(q.options) && options.length === 0)
|
|
35
|
+
continue;
|
|
36
|
+
specs.push({
|
|
37
|
+
question: q.question,
|
|
38
|
+
header: typeof q.header === "string" ? q.header : undefined,
|
|
39
|
+
options,
|
|
40
|
+
multiple: q.multiple === true,
|
|
41
|
+
custom: q.custom === false ? false : undefined,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return specs;
|
|
45
|
+
}
|
|
46
|
+
// Legacy format: { question: "..." } — plain free-text question.
|
|
47
|
+
if (typeof args.question === "string" && args.question.trim()) {
|
|
48
|
+
return [{ question: args.question }];
|
|
49
|
+
}
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
export const questionTool = {
|
|
53
|
+
name: "question",
|
|
54
|
+
icon: "❓",
|
|
55
|
+
description: DESCRIPTION,
|
|
56
|
+
tags: ["core"],
|
|
57
|
+
interactive: true,
|
|
58
|
+
parameters: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
questions: {
|
|
62
|
+
type: "array",
|
|
63
|
+
description: "Questions to ask",
|
|
64
|
+
items: {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties: {
|
|
67
|
+
question: { type: "string", description: "Complete question" },
|
|
68
|
+
header: {
|
|
69
|
+
type: "string",
|
|
70
|
+
description: "Very short label (max 30 chars)",
|
|
71
|
+
},
|
|
72
|
+
options: {
|
|
73
|
+
type: "array",
|
|
74
|
+
description: "Available choices; omit for a free-text question",
|
|
75
|
+
items: {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
label: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: "Display text (1-5 words, concise)",
|
|
81
|
+
},
|
|
82
|
+
description: {
|
|
83
|
+
type: "string",
|
|
84
|
+
description: "Explanation of choice",
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ["label", "description"],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
multiple: {
|
|
91
|
+
type: "boolean",
|
|
92
|
+
description: "Allow selecting multiple choices",
|
|
93
|
+
},
|
|
94
|
+
custom: {
|
|
95
|
+
type: "boolean",
|
|
96
|
+
description: "Allow typing a custom answer (default: true)",
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
required: ["question"],
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
question: {
|
|
103
|
+
type: "string",
|
|
104
|
+
description: "Legacy single free-text question",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
handler: async (ctx, args) => {
|
|
109
|
+
if (ctx.exitOnComplete) {
|
|
110
|
+
return { success: false, output: t("tool.interactive_disabled") };
|
|
111
|
+
}
|
|
112
|
+
const questions = normalizeQuestions(args);
|
|
113
|
+
if (questions.length === 0) {
|
|
114
|
+
return { success: false, output: t("tool.question.no_questions") };
|
|
115
|
+
}
|
|
116
|
+
const answers = [];
|
|
117
|
+
for (let i = 0; i < questions.length; i++) {
|
|
118
|
+
const q = questions[i];
|
|
119
|
+
const progress = questions.length > 1
|
|
120
|
+
? t("tool.question.progress", {
|
|
121
|
+
current: i + 1,
|
|
122
|
+
total: questions.length,
|
|
123
|
+
})
|
|
124
|
+
: undefined;
|
|
125
|
+
answers.push(await askUser(q.question, {
|
|
126
|
+
header: q.header,
|
|
127
|
+
options: q.options,
|
|
128
|
+
multiple: q.multiple,
|
|
129
|
+
custom: q.custom,
|
|
130
|
+
progress,
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
const formatted = questions
|
|
134
|
+
.map((q, i) => `"${q.question}"="${answers[i].length ? answers[i].join(", ") : t("tool.question.unanswered")}"`)
|
|
135
|
+
.join(", ");
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
output: t("tool.question.answered", { formatted }),
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
};
|