@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.
@@ -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
- import("../dist/cli.js").catch((error) => {
4
- console.error(error);
5
- process.exitCode = 1;
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-7V7G7NVF.js";
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.repository) return { repo: record.repository, branch: null };
377
- return record.cwd ? await abortable(resolveRepoFromCwdAsync(record.cwd, signal), signal) : null;
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
- ...options.repository ? { repository: options.repository } : {},
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.10-dev.1+09fae5b6" : "unknown";
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
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  resolveRepoFromCwd
3
- } from "./chunk-7V7G7NVF.js";
3
+ } from "./chunk-EX5J3MPG.js";
4
4
  import {
5
5
  CONVEX_URL,
6
6
  getActiveEnv,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runRawSyncOnce
3
- } from "./chunk-JPHN4VKM.js";
3
+ } from "./chunk-VFZWTVPD.js";
4
4
  import {
5
5
  resolvePackageRoot
6
- } from "./chunk-F57IFY3K.js";
6
+ } from "./chunk-CSOFP7MT.js";
7
7
 
8
8
  // src/observer/sync-cycle.ts
9
9
  import { fork } from "child_process";