mindvest-atlas 0.44.0 → 0.46.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/package.json +1 -1
- package/src/commands/computer-use.js +35 -0
- package/src/config.js +1 -1
- package/src/helpers/atlas_env.py +46 -0
package/package.json
CHANGED
|
@@ -155,6 +155,37 @@ export function askServer(job, op, args, root) {
|
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/** Run one atlas_env call inside the project, and hand back what it said. */
|
|
158
|
+
/** What using the computer needs, and what it is called when you import it. */
|
|
159
|
+
const NEEDED = [['pyautogui', 'pyautogui'], ['mss', 'mss'], ['Pillow', 'PIL']];
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Put what is missing into the interpreter that is ABOUT TO BE USED.
|
|
163
|
+
*
|
|
164
|
+
* Atlas installs these when the switch goes on, but into the ambient python,
|
|
165
|
+
* and a project with a venv of its own runs on that instead: the switch said
|
|
166
|
+
* ready and the run then found nothing. The interpreter here is the one the
|
|
167
|
+
* work will happen in, which is the only one worth checking.
|
|
168
|
+
*
|
|
169
|
+
* Best effort and quiet: a machine with no network or no pip is one that runs
|
|
170
|
+
* without it, and every call site already says what it cannot do.
|
|
171
|
+
*/
|
|
172
|
+
export function ensureDeps(python, cwd, env) {
|
|
173
|
+
const check = `import ${NEEDED.map(([, mod]) => mod).join(', ')}`;
|
|
174
|
+
const has = () => spawnSync(python[0], [...python.slice(1), '-c', check],
|
|
175
|
+
{ cwd, env, stdio: 'ignore' }).status === 0;
|
|
176
|
+
if (has()) return true;
|
|
177
|
+
println(color.dim(' Getting this computer ready (one time)…'));
|
|
178
|
+
// --user is wrong inside a venv and right outside one, so ask the interpreter
|
|
179
|
+
// which it is in rather than guessing.
|
|
180
|
+
const inVenv = spawnSync(python[0],
|
|
181
|
+
[...python.slice(1), '-c', 'import sys; sys.exit(0 if sys.prefix != sys.base_prefix else 1)'],
|
|
182
|
+
{ cwd, env, stdio: 'ignore' }).status === 0;
|
|
183
|
+
const args = ['-m', 'pip', 'install', '--quiet', ...(inVenv ? [] : ['--user'])];
|
|
184
|
+
spawnSync(python[0], [...python.slice(1), ...args, ...NEEDED.map(([name]) => name)],
|
|
185
|
+
{ cwd, env, stdio: 'ignore', timeout: 300000 });
|
|
186
|
+
return has();
|
|
187
|
+
}
|
|
188
|
+
|
|
158
189
|
export function runInProject(job, code, root) {
|
|
159
190
|
const { dir } = workspace(job, root);
|
|
160
191
|
if (!fs.existsSync(dir)) {
|
|
@@ -165,6 +196,10 @@ export function runInProject(job, code, root) {
|
|
|
165
196
|
const venv = venvPython(dir);
|
|
166
197
|
const python = venv ? [String(venv)] : [...ambientPython()];
|
|
167
198
|
const env = { ...process.env, ...jobEnv(dir), ATLAS_JOB: path.basename(dir) };
|
|
199
|
+
// BEFORE ANYTHING TRIES TO IMPORT THEM. Checked every time and installed only
|
|
200
|
+
// when something is missing, so the cost is one python start on a machine
|
|
201
|
+
// that is already set up.
|
|
202
|
+
ensureDeps(python, dir, env);
|
|
168
203
|
// THE SAME HOLDER FOR EVERY COMMAND in this shell, and no process behind the
|
|
169
204
|
// claim: each of these exits, and a claim needing a live process would be
|
|
170
205
|
// gone before the next one ran.
|
package/src/config.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
|
|
5
|
-
export const VERSION = '0.
|
|
5
|
+
export const VERSION = '0.46.0';
|
|
6
6
|
export const DEFAULT_BASE_URL = 'https://atlasmcp.finmanagerai.com';
|
|
7
7
|
export const DEFAULT_SCOPE = 'atlas broker';
|
|
8
8
|
export const CLIENT_NAME = 'Atlas CLI';
|
package/src/helpers/atlas_env.py
CHANGED
|
@@ -843,6 +843,27 @@ def _do(action, fn, detail=None, points=()):
|
|
|
843
843
|
if holder:
|
|
844
844
|
_log_action(action, detail, ok=False, error="in use by %s" % (holder.get("job") or "another project"))
|
|
845
845
|
raise RuntimeError(_busy(holder))
|
|
846
|
+
# AND NEVER ON ATLAS'S OWN STRIP. It is excluded from screen capture, so it
|
|
847
|
+
# is never in a picture, and it is still a window that takes clicks: a
|
|
848
|
+
# position read off a picture that happens to be under it presses Stop and
|
|
849
|
+
# ends the run, which then has no idea what it pressed (owner, 2026-09-06:
|
|
850
|
+
# "the agent should be careful so it does not stop itself").
|
|
851
|
+
#
|
|
852
|
+
# Atlas keeps the strip off the screen a run is working on where it can, and
|
|
853
|
+
# puts none up at all on a single screen. This is the second line: a refusal
|
|
854
|
+
# that holds whatever the placement decided, and on any machine whose window
|
|
855
|
+
# titles cannot be read.
|
|
856
|
+
if action in _ACTS_ON_A_WINDOW:
|
|
857
|
+
for point in points:
|
|
858
|
+
if _on_the_strip(*point):
|
|
859
|
+
_log_action(action, detail, ok=False, error="on the Atlas strip")
|
|
860
|
+
raise RuntimeError(
|
|
861
|
+
"(%s, %s) is under the Atlas strip, which is the bar showing "
|
|
862
|
+
"what this run is doing. It is not in the picture because it "
|
|
863
|
+
"is excluded from capture, and pressing it would stop this "
|
|
864
|
+
"run. Whatever you meant to click is elsewhere: look again."
|
|
865
|
+
% (point[0], point[1])
|
|
866
|
+
)
|
|
846
867
|
# NOT INSIDE ATLAS UNLESS SOMEBODY SAID SO. Checked at the moment of acting
|
|
847
868
|
# rather than when the window was focused: what matters is where the click
|
|
848
869
|
# lands, and the front window can change between the two.
|
|
@@ -1653,6 +1674,31 @@ def _front_window():
|
|
|
1653
1674
|
return ""
|
|
1654
1675
|
|
|
1655
1676
|
|
|
1677
|
+
def _on_the_strip(x, y):
|
|
1678
|
+
"""Is this point under Atlas's own driving strip?
|
|
1679
|
+
|
|
1680
|
+
The strip writes where it is, because nothing else can see it: it is
|
|
1681
|
+
excluded from capture, so a picture never shows it and no amount of looking
|
|
1682
|
+
would find it.
|
|
1683
|
+
"""
|
|
1684
|
+
import json as _json
|
|
1685
|
+
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
1686
|
+
try:
|
|
1687
|
+
with open(os.path.join(root, ".studio", "strip.json"), encoding="utf-8") as fh:
|
|
1688
|
+
box = _json.load(fh)
|
|
1689
|
+
# A note left by a strip that has gone is not a no-go area for ever.
|
|
1690
|
+
import time as _time
|
|
1691
|
+
if _time.time() - float(box.get("at") or 0) > 120:
|
|
1692
|
+
return False
|
|
1693
|
+
# A little margin: a click at the very edge of it is still a click
|
|
1694
|
+
# somebody did not mean to make there.
|
|
1695
|
+
pad = 6
|
|
1696
|
+
return (int(box["x"]) - pad <= int(x) <= int(box["x"]) + int(box["width"]) + pad
|
|
1697
|
+
and int(box["y"]) - pad <= int(y) <= int(box["y"]) + int(box["height"]) + pad)
|
|
1698
|
+
except Exception:
|
|
1699
|
+
return False
|
|
1700
|
+
|
|
1701
|
+
|
|
1656
1702
|
def _in_the_atlas_window():
|
|
1657
1703
|
front = _front_window().lower()
|
|
1658
1704
|
return any(name in front for name in _ATLAS_WINDOWS) if front else False
|