@fml-inc/fml 0.8.10-dev.1 → 0.8.11-rc.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/.claude-plugin/plugin.json +1 -1
- package/README.md +114 -3
- package/agent-assets/mini-swe-agent/FML_WORKFLOW.md +62 -0
- package/agent-assets/mini-swe-agent/fml_mini_agent.py +483 -0
- package/agent-assets/mini-swe-agent/fml_mini_bootstrap.py +141 -0
- package/bin/fml +18 -4
- package/dist/{chunk-WE7MAWAW.js → chunk-4YLCUJ6C.js} +4 -3
- package/dist/{chunk-5PEFBQNX.js → chunk-5VP4C72Z.js} +25 -6
- package/dist/{chunk-5SI6S4HW.js → chunk-B46WIVL7.js} +1 -1
- package/dist/chunk-CSOFP7MT.js +67 -0
- package/dist/{chunk-7V7G7NVF.js → chunk-EX5J3MPG.js} +2 -2
- package/dist/{chunk-GDOXY4W2.js → chunk-IUFDFBU7.js} +1 -1
- package/dist/{chunk-2DGHAVVR.js → chunk-JK5JGPUN.js} +2 -2
- package/dist/{chunk-JPHN4VKM.js → chunk-VFZWTVPD.js} +247 -162
- package/dist/cli.js +4603 -3885
- package/dist/hook-handler.js +42 -14
- package/dist/mcp/server.js +7 -7
- package/dist/observer-server.js +8 -8
- package/dist/pi/extension.js +13737 -397
- package/dist/sync-cycle.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-F57IFY3K.js +0 -21
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# <!-- fml-managed-agent-surface:v1 -->
|
|
2
|
+
"""Interpreter-local, fail-open activation for the native mini CLI only.
|
|
3
|
+
|
|
4
|
+
Imported by the owned .pth on Python startup. Ordinary Python invocations do
|
|
5
|
+
not import mini, read its configuration, or install an import hook. Native mini
|
|
6
|
+
still resolves all configuration itself on every invocation.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _explicit_override(arguments):
|
|
13
|
+
values = {"--model", "--model-class", "--environment-class",
|
|
14
|
+
"--task", "--cost-limit", "--output"}
|
|
15
|
+
skip = False
|
|
16
|
+
for argument in arguments:
|
|
17
|
+
if skip:
|
|
18
|
+
skip = False
|
|
19
|
+
continue
|
|
20
|
+
if argument == "--":
|
|
21
|
+
break
|
|
22
|
+
if (argument in ("--config", "--agent-class")
|
|
23
|
+
or argument.startswith(("--config=", "--agent-class="))):
|
|
24
|
+
return True
|
|
25
|
+
skip = argument in values
|
|
26
|
+
if argument.startswith("-") and not argument.startswith("--"):
|
|
27
|
+
# Native mini has one short flag (-y); -c/-m/-t/-l/-o take values.
|
|
28
|
+
# Click consumes the rest of a cluster, or the next argv, as a value.
|
|
29
|
+
for index, option in enumerate(argument[1:], 2):
|
|
30
|
+
if option == "c":
|
|
31
|
+
return True
|
|
32
|
+
if option in "mtlo":
|
|
33
|
+
skip = index == len(argument)
|
|
34
|
+
break
|
|
35
|
+
if option != "y":
|
|
36
|
+
break # Unknown short options stop native parsing too.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _native_cli():
|
|
40
|
+
original = getattr(sys, "orig_argv", ())
|
|
41
|
+
if sys.argv and sys.argv[0] == "-m" and "-m" in original:
|
|
42
|
+
index = original.index("-m")
|
|
43
|
+
return index + 1 < len(original) and original[index + 1] in (
|
|
44
|
+
"minisweagent", "minisweagent.__main__", "minisweagent.run.mini",
|
|
45
|
+
)
|
|
46
|
+
# Console scripts have their real script path in argv at site startup.
|
|
47
|
+
# Check the entrypoint rather than activating arbitrary programs named mini.
|
|
48
|
+
filename = sys.argv[0] if sys.argv else ""
|
|
49
|
+
if filename.replace("\\", "/").rsplit("/", 1)[-1] not in (
|
|
50
|
+
"mini", "mini.exe", "mini-script.py",
|
|
51
|
+
"mini-swe-agent", "mini-swe-agent.exe", "mini-swe-agent-script.py",
|
|
52
|
+
):
|
|
53
|
+
return False
|
|
54
|
+
try:
|
|
55
|
+
if filename.lower().endswith(".exe"):
|
|
56
|
+
# Windows pip launchers append a ZIP containing the same entrypoint.
|
|
57
|
+
import zipfile
|
|
58
|
+
with zipfile.ZipFile(filename) as script:
|
|
59
|
+
return b"from minisweagent.run.mini import app" in script.read("__main__.py")
|
|
60
|
+
with open(filename, "rb") as script:
|
|
61
|
+
return b"from minisweagent.run.mini import app" in script.read(8192)
|
|
62
|
+
except Exception:
|
|
63
|
+
return False
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _patch_agents(module):
|
|
67
|
+
native = module.get_agent
|
|
68
|
+
|
|
69
|
+
def get_agent(model, env, config, *, default_type=""):
|
|
70
|
+
adapted = config
|
|
71
|
+
try:
|
|
72
|
+
from minisweagent.agents.default import DefaultAgent
|
|
73
|
+
from minisweagent.agents.interactive import InteractiveAgent
|
|
74
|
+
from minisweagent.environments.local import LocalEnvironment
|
|
75
|
+
# Class identity, not inheritance: custom agents/environments remain native.
|
|
76
|
+
if type(env) is LocalEnvironment:
|
|
77
|
+
selected = module.get_agent_class(config.get("agent_class", default_type))
|
|
78
|
+
if selected is DefaultAgent or selected is InteractiveAgent:
|
|
79
|
+
from fml_mini_agent import FmlDefaultAgent, FmlInteractiveAgent
|
|
80
|
+
adapted = dict(config)
|
|
81
|
+
adapted["agent_class"] = (
|
|
82
|
+
"fml_mini_agent.FmlDefaultAgent" if selected is DefaultAgent
|
|
83
|
+
else "fml_mini_agent.FmlInteractiveAgent"
|
|
84
|
+
)
|
|
85
|
+
except Exception:
|
|
86
|
+
# An incompatible/missing adapter must not disable the native CLI.
|
|
87
|
+
adapted = config
|
|
88
|
+
return native(model, env, adapted, default_type=default_type)
|
|
89
|
+
|
|
90
|
+
module.get_agent = get_agent
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _activate(expected_prefix):
|
|
94
|
+
if not _native_cli() or _explicit_override(sys.argv[1:]):
|
|
95
|
+
return
|
|
96
|
+
# Other interpreters can inherit this .pth via system site-packages. Their
|
|
97
|
+
# executables may resolve to the same binary, but their prefixes are distinct.
|
|
98
|
+
import os
|
|
99
|
+
actual_prefix = os.path.normcase(os.path.realpath(sys.prefix))
|
|
100
|
+
selected_prefix = os.path.normcase(os.path.realpath(expected_prefix))
|
|
101
|
+
if actual_prefix != selected_prefix:
|
|
102
|
+
return
|
|
103
|
+
# All mini/config imports remain lazy; ordinary Python never reaches here.
|
|
104
|
+
from importlib.machinery import PathFinder
|
|
105
|
+
|
|
106
|
+
class Loader:
|
|
107
|
+
def __init__(self, native):
|
|
108
|
+
self.native = native
|
|
109
|
+
|
|
110
|
+
def __getattr__(self, name):
|
|
111
|
+
return getattr(self.native, name)
|
|
112
|
+
|
|
113
|
+
def create_module(self, spec):
|
|
114
|
+
return self.native.create_module(spec)
|
|
115
|
+
|
|
116
|
+
def exec_module(self, module):
|
|
117
|
+
self.native.exec_module(module)
|
|
118
|
+
try:
|
|
119
|
+
_patch_agents(module)
|
|
120
|
+
except Exception:
|
|
121
|
+
pass
|
|
122
|
+
|
|
123
|
+
class Finder:
|
|
124
|
+
def find_spec(self, fullname, path=None, target=None):
|
|
125
|
+
if fullname != "minisweagent.agents":
|
|
126
|
+
return None
|
|
127
|
+
spec = PathFinder.find_spec(fullname, path, target)
|
|
128
|
+
if spec is not None and spec.loader is not None:
|
|
129
|
+
spec.loader = Loader(spec.loader)
|
|
130
|
+
sys.meta_path.remove(self)
|
|
131
|
+
return spec
|
|
132
|
+
|
|
133
|
+
sys.meta_path.insert(0, Finder())
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def activate(expected_prefix):
|
|
137
|
+
try:
|
|
138
|
+
_activate(expected_prefix)
|
|
139
|
+
except Exception:
|
|
140
|
+
# .pth startup must remain usable even after a partial upgrade/uninstall.
|
|
141
|
+
pass
|
package/bin/fml
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
if (process.argv[2] === "hook") {
|
|
4
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
5
|
+
process.stdout.write(
|
|
6
|
+
"Usage: fml hook --target <target>\n\nCapture an agent callback from JSON stdin and return optional context JSON.\n",
|
|
7
|
+
);
|
|
8
|
+
} else {
|
|
9
|
+
import("../dist/hook-handler.js").catch(() => {
|
|
10
|
+
// Agent callbacks remain fail-open even when the integration is unavailable.
|
|
11
|
+
process.exitCode = 0;
|
|
12
|
+
process.stdout.write("{}");
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
} else {
|
|
16
|
+
import("../dist/cli.js").catch((error) => {
|
|
17
|
+
console.error(error);
|
|
18
|
+
process.exitCode = 1;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
resolveRepoFromCwdAsync
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-EX5J3MPG.js";
|
|
4
4
|
import {
|
|
5
5
|
getActiveEnv,
|
|
6
6
|
getSiteUrlForConvexUrl,
|
|
@@ -373,8 +373,9 @@ async function prepareHookEvent(record, signal) {
|
|
|
373
373
|
return { record, repositoryInfo, ...bounded };
|
|
374
374
|
}
|
|
375
375
|
async function repositoryForRecord(record, signal) {
|
|
376
|
-
if (record.
|
|
377
|
-
|
|
376
|
+
if (record.target === "pi" && !record.repository) return null;
|
|
377
|
+
const liveRepository = record.cwd ? await abortable(resolveRepoFromCwdAsync(record.cwd, signal), signal) : null;
|
|
378
|
+
return record.repository ? { repo: record.repository, branch: liveRepository?.branch ?? null } : liveRepository;
|
|
378
379
|
}
|
|
379
380
|
function hookSessionRow(events) {
|
|
380
381
|
const records = events.map((event) => event.record);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canonicalGitHubRepository
|
|
3
|
+
} from "./chunk-CSOFP7MT.js";
|
|
1
4
|
import {
|
|
2
5
|
FML_HOOK_INBOX_DIR,
|
|
3
6
|
FML_OBSERVER_LOCK_FILE,
|
|
@@ -75,7 +78,8 @@ var HOOK_TARGETS = Object.freeze([
|
|
|
75
78
|
"codex",
|
|
76
79
|
"gemini",
|
|
77
80
|
"grok",
|
|
78
|
-
"pi"
|
|
81
|
+
"pi",
|
|
82
|
+
"mini-swe-agent"
|
|
79
83
|
]);
|
|
80
84
|
function isHookTarget(value) {
|
|
81
85
|
return typeof value === "string" && HOOK_TARGETS.some((targetId) => targetId === value);
|
|
@@ -155,6 +159,14 @@ var hook_events_default = {
|
|
|
155
159
|
"PreCompact",
|
|
156
160
|
"PostCompact",
|
|
157
161
|
"SessionEnd"
|
|
162
|
+
],
|
|
163
|
+
"mini-swe-agent": [
|
|
164
|
+
"SessionStart",
|
|
165
|
+
"UserPromptSubmit",
|
|
166
|
+
"BeforeModel",
|
|
167
|
+
"PostToolUse",
|
|
168
|
+
"Stop",
|
|
169
|
+
"SessionEnd"
|
|
158
170
|
]
|
|
159
171
|
};
|
|
160
172
|
|
|
@@ -172,6 +184,9 @@ var GROK_HOOK_EVENTS = Object.freeze([
|
|
|
172
184
|
...hook_events_default.grok
|
|
173
185
|
]);
|
|
174
186
|
var PI_HOOK_EVENTS = Object.freeze([...hook_events_default.pi]);
|
|
187
|
+
var MINI_SWE_AGENT_HOOK_EVENTS = Object.freeze([
|
|
188
|
+
...hook_events_default["mini-swe-agent"]
|
|
189
|
+
]);
|
|
175
190
|
|
|
176
191
|
// ../../packages/agent-targets/src/hooks/read-command.ts
|
|
177
192
|
import path2 from "path";
|
|
@@ -547,9 +562,9 @@ function normalizedEvent(target, payload, context) {
|
|
|
547
562
|
"command"
|
|
548
563
|
);
|
|
549
564
|
const toolName = firstString(payload, ["tool_name", "toolName", "name"]);
|
|
550
|
-
const filePath = firstString(payload, ["file_path", "filePath", "path"]) ?? (toolInput ? firstString(toolInput, ["file_path", "filePath", "path"]) : void 0) ?? (command && toolName && SHELL_TOOL_NAMES.has(toolName.toLowerCase()) ? extractReadFilePathFromCommand(command) ?? void 0 : void 0);
|
|
565
|
+
const filePath = firstString(payload, ["file_path", "filePath", "path"]) ?? (toolInput ? firstString(toolInput, ["file_path", "filePath", "path"]) : void 0) ?? (target !== "mini-swe-agent" && command && toolName && SHELL_TOOL_NAMES.has(toolName.toLowerCase()) ? extractReadFilePathFromCommand(command) ?? void 0 : void 0);
|
|
551
566
|
const userPrompt = boundedExtractedText(
|
|
552
|
-
firstText(payload, ["prompt", "user_prompt", "userPrompt"]) ?? (target === "gemini" ? geminiUserPrompt(payload) : void 0),
|
|
567
|
+
target === "mini-swe-agent" && eventType !== "UserPromptSubmit" ? void 0 : firstText(payload, ["prompt", "user_prompt", "userPrompt"]) ?? (target === "gemini" ? geminiUserPrompt(payload) : void 0),
|
|
553
568
|
"user_prompt"
|
|
554
569
|
);
|
|
555
570
|
const plan = boundedExtractedText(firstText(payload, ["plan"]), "plan");
|
|
@@ -605,15 +620,18 @@ var HOOK_NORMALIZATION_CAPABILITIES = Object.freeze([
|
|
|
605
620
|
capability("codex", CODEX_HOOK_EVENTS),
|
|
606
621
|
capability("gemini", GEMINI_HOOK_EVENTS),
|
|
607
622
|
capability("grok", GROK_HOOK_EVENTS),
|
|
608
|
-
capability("pi", PI_HOOK_EVENTS)
|
|
623
|
+
capability("pi", PI_HOOK_EVENTS),
|
|
624
|
+
capability("mini-swe-agent", MINI_SWE_AGENT_HOOK_EVENTS)
|
|
609
625
|
]);
|
|
610
626
|
function parseHookCaptureRequest(value) {
|
|
611
627
|
if (!isRecord(value) || !isHookTarget(value.target)) return null;
|
|
612
628
|
if (!isRecord(value.payload)) return null;
|
|
629
|
+
const repository = typeof value.repository === "string" && /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(value.repository) ? canonicalGitHubRepository(`https://github.com/${value.repository}`) : null;
|
|
613
630
|
return {
|
|
614
631
|
target: value.target,
|
|
615
632
|
payload: value.payload,
|
|
616
|
-
...typeof value.cwd === "string" && value.cwd.length > 0 ? { cwd: value.cwd } : {}
|
|
633
|
+
...typeof value.cwd === "string" && value.cwd.length > 0 ? { cwd: value.cwd } : {},
|
|
634
|
+
...repository ? { repository } : {}
|
|
617
635
|
};
|
|
618
636
|
}
|
|
619
637
|
function normalizeTargetHookCapture(request, context) {
|
|
@@ -640,6 +658,7 @@ function normalizeObserverHookCapture(request, options = {}) {
|
|
|
640
658
|
cwd: request.cwd
|
|
641
659
|
});
|
|
642
660
|
if (!normalized) return null;
|
|
661
|
+
const repository = options.repository ?? request.repository;
|
|
643
662
|
return {
|
|
644
663
|
syncId: options.syncId?.() ?? hookSyncId(
|
|
645
664
|
request,
|
|
@@ -653,7 +672,7 @@ function normalizeObserverHookCapture(request, options = {}) {
|
|
|
653
672
|
target: request.target,
|
|
654
673
|
payload: request.payload,
|
|
655
674
|
...normalized.cwd ? { cwd: normalized.cwd } : {},
|
|
656
|
-
...
|
|
675
|
+
...repository ? { repository } : {},
|
|
657
676
|
...normalized.toolName ? { toolName: normalized.toolName } : {},
|
|
658
677
|
...normalized.userPrompt ? { userPrompt: normalized.userPrompt } : {},
|
|
659
678
|
...normalized.filePath ? { filePath: normalized.filePath } : {},
|
|
@@ -53,7 +53,7 @@ var PACKAGE_NAME = "@fml-inc/fml";
|
|
|
53
53
|
var UPDATE_TIMEOUT_MS = 12e4;
|
|
54
54
|
var SETUP_TIMEOUT_MS = 3e5;
|
|
55
55
|
function getCurrentVersion() {
|
|
56
|
-
return true ? "0.8.
|
|
56
|
+
return true ? "0.8.11-rc.1+80d59e99" : "unknown";
|
|
57
57
|
}
|
|
58
58
|
function parseVersion(version) {
|
|
59
59
|
const match = SEMVER_PATTERN.exec(version);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// ../../packages/shared/src/github-repository.ts
|
|
2
|
+
var MAX_REPOSITORY_BYTES = 201;
|
|
3
|
+
var UTF8_ENCODER = new TextEncoder();
|
|
4
|
+
function utf8Bytes(value) {
|
|
5
|
+
return UTF8_ENCODER.encode(value).byteLength;
|
|
6
|
+
}
|
|
7
|
+
function githubRepositoryPath(pathname) {
|
|
8
|
+
const parts = pathname.replace(/^\/+|\/+$/g, "").split("/");
|
|
9
|
+
if (parts.length !== 2) return null;
|
|
10
|
+
const owner = parts[0];
|
|
11
|
+
const repository = parts[1]?.replace(/\.git$/i, "");
|
|
12
|
+
if (!owner || !repository) return null;
|
|
13
|
+
const segmentPattern = /^[A-Za-z0-9_.-]+$/;
|
|
14
|
+
if (!segmentPattern.test(owner) || !segmentPattern.test(repository)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const canonical = `${owner.toLowerCase()}/${repository.toLowerCase()}`;
|
|
18
|
+
if (canonical.startsWith("./") || canonical.startsWith("../") || repository === "." || repository === ".." || repository.toLowerCase().endsWith(".git")) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return utf8Bytes(canonical) <= MAX_REPOSITORY_BYTES ? canonical : null;
|
|
22
|
+
}
|
|
23
|
+
function canonicalGitHubRepository(remote) {
|
|
24
|
+
const trimmed = remote.trim();
|
|
25
|
+
if (!trimmed || trimmed.includes("\0")) return null;
|
|
26
|
+
const scp = trimmed.match(/^git@github\.com:([^?#]+)$/i);
|
|
27
|
+
if (scp?.[1]) return githubRepositoryPath(scp[1]);
|
|
28
|
+
let url;
|
|
29
|
+
try {
|
|
30
|
+
url = new URL(trimmed);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
if (url.hostname.toLowerCase() !== "github.com") return null;
|
|
35
|
+
if (url.search || url.hash || url.password || url.port) return null;
|
|
36
|
+
if (url.protocol === "https:" || url.protocol === "http:") {
|
|
37
|
+
if (url.username) return null;
|
|
38
|
+
} else if (url.protocol === "ssh:") {
|
|
39
|
+
if (url.username && url.username !== "git") return null;
|
|
40
|
+
} else {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
return githubRepositoryPath(url.pathname);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/package-root.ts
|
|
47
|
+
import fs from "fs";
|
|
48
|
+
import path from "path";
|
|
49
|
+
import { fileURLToPath } from "url";
|
|
50
|
+
function resolvePackageRoot(startDir = path.dirname(fileURLToPath(import.meta.url))) {
|
|
51
|
+
let directory = startDir;
|
|
52
|
+
while (true) {
|
|
53
|
+
if (fs.existsSync(path.join(directory, "package.json")) && fs.existsSync(path.join(directory, "bin"))) {
|
|
54
|
+
return directory;
|
|
55
|
+
}
|
|
56
|
+
const parent = path.dirname(directory);
|
|
57
|
+
if (parent === directory) {
|
|
58
|
+
throw new Error(`Could not resolve FML package root from ${startDir}`);
|
|
59
|
+
}
|
|
60
|
+
directory = parent;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export {
|
|
65
|
+
canonicalGitHubRepository,
|
|
66
|
+
resolvePackageRoot
|
|
67
|
+
};
|
|
@@ -72,11 +72,11 @@ function gitAsync(cwd, args, signal) {
|
|
|
72
72
|
}
|
|
73
73
|
function repositorySlug(remote) {
|
|
74
74
|
const ssh = remote.match(/^[^@]+@[^:]+:([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
75
|
-
if (ssh?.[1]) return ssh[1];
|
|
75
|
+
if (ssh?.[1]) return ssh[1].toLowerCase();
|
|
76
76
|
try {
|
|
77
77
|
const url = new URL(remote);
|
|
78
78
|
const slug = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
|
|
79
|
-
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(slug) ? slug : null;
|
|
79
|
+
return /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(slug) ? slug.toLowerCase() : null;
|
|
80
80
|
} catch {
|
|
81
81
|
return null;
|
|
82
82
|
}
|