mindvest-atlas 0.93.1 → 0.95.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/remote-control.js +12 -0
- package/src/config.js +1 -1
- package/src/remote-control.js +78 -0
package/package.json
CHANGED
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
SCRIPTS_DIR, projectsForAlert, readMode,
|
|
30
30
|
streamStudioFrames,
|
|
31
31
|
localBrokers, localBrokerCall,
|
|
32
|
+
studioVoice,
|
|
33
|
+
appendSaid,
|
|
32
34
|
} from '../remote-control.js';
|
|
33
35
|
import fs from 'node:fs';
|
|
34
36
|
import os from 'node:os';
|
|
@@ -465,6 +467,8 @@ async function handleAsk(ask, root) {
|
|
|
465
467
|
if (ask.op === 'chats') return answer(ask.request_id, { ok: true, chats: listChats(ask.job || '', root) });
|
|
466
468
|
if (ask.op === 'chat_read') return answer(ask.request_id, readChat(ask.job || '', ask.chat_id || '', root));
|
|
467
469
|
if (ask.op === 'chat_delete') return answer(ask.request_id, deleteChat(ask.job || '', ask.chat_id || '', root));
|
|
470
|
+
// What was said out loud in a voice conversation about this project.
|
|
471
|
+
if (ask.op === 'chat_said') return answer(ask.request_id, appendSaid(ask.job || '', ask.chat_id || '', ask.lines, root));
|
|
468
472
|
if (ask.op === 'models') {
|
|
469
473
|
void listModels().then((m) => answer(ask.request_id, { ok: true, ...m }));
|
|
470
474
|
return undefined;
|
|
@@ -486,6 +490,14 @@ async function handleAsk(ask, root) {
|
|
|
486
490
|
.catch((err) => answer(ask.request_id, { ok: false, error: String(err.message || err) }));
|
|
487
491
|
return undefined;
|
|
488
492
|
}
|
|
493
|
+
if (ask.op === 'voice') {
|
|
494
|
+
// A voice the person is talking to on their phone, working Atlas Studio
|
|
495
|
+
// on this computer: one screen tool, carried out by the app itself.
|
|
496
|
+
void studioVoice(ask.name, ask.args, ask.allowed === true, root)
|
|
497
|
+
.then((out) => answer(ask.request_id, out))
|
|
498
|
+
.catch((err) => answer(ask.request_id, { ok: false, error: String(err.message || err) }));
|
|
499
|
+
return undefined;
|
|
500
|
+
}
|
|
489
501
|
if (ask.op === 'build') {
|
|
490
502
|
// THE POINT OF ALL OF THIS. Words in, working script out: the owner says
|
|
491
503
|
// what they want in one sentence and their own model writes it, runs it,
|
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.95.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/remote-control.js
CHANGED
|
@@ -2018,6 +2018,52 @@ export async function localBrokerCall(connector, tool, args, root, timeoutMs = 1
|
|
|
2018
2018
|
return out;
|
|
2019
2019
|
}
|
|
2020
2020
|
|
|
2021
|
+
/**
|
|
2022
|
+
* A VOICE THE PERSON IS TALKING TO SOMEWHERE ELSE, WORKING ATLAS STUDIO HERE
|
|
2023
|
+
* (owner, 2026-10-06: "when I switch to local, where is the voice ... it
|
|
2024
|
+
* controls atlas studio remotely and the user will approve too").
|
|
2025
|
+
*
|
|
2026
|
+
* The voice is on their phone or in their browser. One of its screen tools
|
|
2027
|
+
* (look, go_to, point_at, press, type_into) is carried out in the app's own
|
|
2028
|
+
* window on this computer, by the app's own rules. This only passes the
|
|
2029
|
+
* question to the app and the answer back; a press that needs a yes comes
|
|
2030
|
+
* back as `ask`, to be asked where the person is.
|
|
2031
|
+
*
|
|
2032
|
+
* There is nothing to press without the app, so a runner started by hand in a
|
|
2033
|
+
* terminal says so.
|
|
2034
|
+
*/
|
|
2035
|
+
export const STUDIO_VOICE_TOOLS = ['look', 'go_to', 'point_at', 'press', 'type_into'];
|
|
2036
|
+
export async function studioVoice(name, args, allowed, root, opts = {}) {
|
|
2037
|
+
const fetchImpl = opts.fetch || fetch;
|
|
2038
|
+
if (!STUDIO_VOICE_TOOLS.includes(String(name || ''))) {
|
|
2039
|
+
return { ok: false, error: 'That cannot be done on the computer from here.' };
|
|
2040
|
+
}
|
|
2041
|
+
const at = (opts.address || askAddress)(root);
|
|
2042
|
+
if (!at) return { ok: false, error: 'Atlas Studio is not open on that computer.' };
|
|
2043
|
+
try {
|
|
2044
|
+
const res = await fetchImpl(at.url.replace('/ask', '/voice'), {
|
|
2045
|
+
method: 'POST',
|
|
2046
|
+
headers: { 'Content-Type': 'application/json', 'X-Atlas-Token': at.token },
|
|
2047
|
+
body: JSON.stringify({
|
|
2048
|
+
name: String(name),
|
|
2049
|
+
args: args && typeof args === 'object' && !Array.isArray(args) ? args : {},
|
|
2050
|
+
// Only `true` is a yes.
|
|
2051
|
+
allowed: allowed === true,
|
|
2052
|
+
}),
|
|
2053
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 25000),
|
|
2054
|
+
});
|
|
2055
|
+
const out = await res.json().catch(() => ({}));
|
|
2056
|
+
// An app from before this door existed answers 404 or its own refusal.
|
|
2057
|
+
if (res.status === 404 || res.status === 405) {
|
|
2058
|
+
return { ok: false, error: 'Atlas Studio on that computer needs updating before a voice can work it from here.' };
|
|
2059
|
+
}
|
|
2060
|
+
if (!res.ok || !out || out.ok === false) return { ok: false, error: (out && out.error) || `Atlas Studio answered ${res.status}.` };
|
|
2061
|
+
return { ok: true, result: out.result && typeof out.result === 'object' ? out.result : {} };
|
|
2062
|
+
} catch (err) {
|
|
2063
|
+
return { ok: false, error: 'Atlas Studio on that computer did not answer.' };
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2021
2067
|
/**
|
|
2022
2068
|
* Stream a chat turn from the model on THIS computer.
|
|
2023
2069
|
*
|
|
@@ -2409,6 +2455,38 @@ export function appendChat(job, id, role, text, root, payload, model) {
|
|
|
2409
2455
|
try { fs.writeFileSync(f, JSON.stringify(data), 'utf8'); } catch { /* best effort */ }
|
|
2410
2456
|
}
|
|
2411
2457
|
|
|
2458
|
+
/**
|
|
2459
|
+
* WHAT WAS SAID OUT LOUD, WRITTEN INTO THE PROJECT'S OWN CHAT (owner,
|
|
2460
|
+
* 2026-10-06: "when I'm using the voice from the web but connected to the
|
|
2461
|
+
* computer and a project, this is no longer atlas cloud, so things are done
|
|
2462
|
+
* there and chat are written there").
|
|
2463
|
+
*
|
|
2464
|
+
* A voice conversation had on the website or a phone, with the chat pointed at
|
|
2465
|
+
* a project on this computer, belongs to that project. Its spoken lines are
|
|
2466
|
+
* written here, in the same record a typed message is, so the conversation
|
|
2467
|
+
* reads whole from Atlas Studio and from anywhere else, and nothing of it is
|
|
2468
|
+
* kept by Atlas. Only lines: a person's and Atlas's words, in order.
|
|
2469
|
+
*/
|
|
2470
|
+
export function cleanSaidLines(lines) {
|
|
2471
|
+
if (!Array.isArray(lines)) return [];
|
|
2472
|
+
const out = [];
|
|
2473
|
+
for (const l of lines.slice(0, 20)) {
|
|
2474
|
+
if (!l || typeof l !== 'object') continue;
|
|
2475
|
+
const role = l.role === 'user' ? 'user' : l.role === 'assistant' ? 'assistant' : '';
|
|
2476
|
+
const text = typeof l.text === 'string' ? l.text.trim().slice(0, 4000) : '';
|
|
2477
|
+
if (role && text) out.push({ role, text });
|
|
2478
|
+
}
|
|
2479
|
+
return out;
|
|
2480
|
+
}
|
|
2481
|
+
export function appendSaid(job, chatId, lines, root) {
|
|
2482
|
+
const name = String(job || '').trim();
|
|
2483
|
+
const id = String(chatId || '').trim();
|
|
2484
|
+
if (!name || !id) return { ok: false, error: 'Which conversation?' };
|
|
2485
|
+
const clean = cleanSaidLines(lines);
|
|
2486
|
+
for (const l of clean) appendChat(name, id, l.role, l.text, root);
|
|
2487
|
+
return { ok: true, written: clean.length };
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2412
2490
|
/**
|
|
2413
2491
|
* FRAMES FROM ATLAS STUDIO, when it is here.
|
|
2414
2492
|
*
|