@melaya/runner 1.0.95 → 1.0.97
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/dist/assistantHost.py +24 -6
- package/dist/connection.js +37 -2
- package/package.json +1 -1
package/dist/assistantHost.py
CHANGED
|
@@ -177,10 +177,15 @@ def _build_agent():
|
|
|
177
177
|
model = os.environ.get("MEL_ASSISTANT_MODEL", "") or None
|
|
178
178
|
language = os.environ.get("MEL_ASSISTANT_LANGUAGE", "en")
|
|
179
179
|
|
|
180
|
-
# Phone control
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
|
|
180
|
+
# Phone control unlocks on the native mobile app surface OR whenever the user
|
|
181
|
+
# has a PAIRED phone (MEL_ASSISTANT_PHONE_READY, set by the server from
|
|
182
|
+
# getPhonePresence). A desktop / web chat drives the paired phone REMOTELY
|
|
183
|
+
# through the same Redis queue, so it must not be surface-gated — without this,
|
|
184
|
+
# desktop chats had zero phone_* tools and the model refused phone tasks.
|
|
185
|
+
phone_enabled = (
|
|
186
|
+
os.environ.get("MEL_ASSISTANT_SURFACE", "") == "mobile-native"
|
|
187
|
+
or os.environ.get("MEL_ASSISTANT_PHONE_READY", "") in ("1", "true", "True")
|
|
188
|
+
)
|
|
184
189
|
|
|
185
190
|
# Read-only platform tools (melaya_agent); phone control (Device Control) on
|
|
186
191
|
# mobile only. These POST to /api/v1/private/assistant-tool + /phone/command
|
|
@@ -201,10 +206,20 @@ def _build_agent():
|
|
|
201
206
|
try:
|
|
202
207
|
if connector_services:
|
|
203
208
|
from shared.orchestration.lazy_registry import build_lazy_toolkit
|
|
209
|
+
# Phone tools must ALWAYS be fully passed, never scoped: a phone task
|
|
210
|
+
# can't afford search_tools/activate_tool round-trips per tap (that is
|
|
211
|
+
# the "why did it restrict the tools + it's slow" regression). The lazy
|
|
212
|
+
# toolkit pins active_categories only UP TO `budget` (default 25), so
|
|
213
|
+
# phone (~23) + melaya_agent got bumped into the deferred pool. When
|
|
214
|
+
# phone is enabled, widen the budget so the WHOLE phone + base set stays
|
|
215
|
+
# active/pinned; the connectors still lazy-defer beyond that.
|
|
216
|
+
_budget = int(os.environ.get("MEL_LAZY_BUDGET", "25"))
|
|
217
|
+
if phone_enabled:
|
|
218
|
+
_budget = max(_budget, 64)
|
|
204
219
|
toolkit = build_lazy_toolkit(
|
|
205
220
|
active_categories=categories,
|
|
206
221
|
include_categories=categories + connector_services,
|
|
207
|
-
budget=
|
|
222
|
+
budget=_budget,
|
|
208
223
|
)
|
|
209
224
|
else:
|
|
210
225
|
toolkit = build_toolkit(categories=categories)
|
|
@@ -237,7 +252,10 @@ def _build_agent():
|
|
|
237
252
|
"phone_ask_user(question): it expands the Melaya tile ON THE PHONE into a reply "
|
|
238
253
|
"card and BLOCKS for the user's typed answer WITHOUT ending your turn, so the run "
|
|
239
254
|
"continues seamlessly the moment they reply. Use it SPARINGLY, for real decisions "
|
|
240
|
-
"only — not routine ones.
|
|
255
|
+
"only — not routine ones. Do NOT call phone_ask_user to cope with repeated tool "
|
|
256
|
+
"ERRORS or because you feel stuck/struggling — that is a failure to REPORT, not a "
|
|
257
|
+
"decision for the user; stop and report what failed instead. Only ask for a genuine "
|
|
258
|
+
"who/which fork. The EXACT-APP rule still holds (a missing app is a stop, "
|
|
241
259
|
"not a substitution). If a feed is algorithmic, use the app's own Search / Explore "
|
|
242
260
|
"to find the right content yourself. Keep going until the task is COMPLETE (e.g. "
|
|
243
261
|
"all N comments posted) or you are genuinely blocked; only then report what you "
|
package/dist/connection.js
CHANGED
|
@@ -77,11 +77,42 @@ export async function connect(opts) {
|
|
|
77
77
|
const localVersion = getLocalSharedVersion();
|
|
78
78
|
if (!localVersion)
|
|
79
79
|
return; // nothing cached yet — let assistant_start do it
|
|
80
|
-
const { ensurePythonEnv } = await import("./pythonEnv.js");
|
|
81
|
-
await ensurePythonEnv(opts.pythonPath, localVersion, (m) => { if (opts.verbose)
|
|
80
|
+
const { ensurePythonEnv, getCertBundlePath } = await import("./pythonEnv.js");
|
|
81
|
+
const envResult = await ensurePythonEnv(opts.pythonPath, localVersion, (m) => { if (opts.verbose)
|
|
82
82
|
console.log(chalk.gray(` [assistant prewarm] ${m}`)); });
|
|
83
83
|
if (opts.verbose)
|
|
84
84
|
console.log(chalk.gray(" [assistant prewarm] ready"));
|
|
85
|
+
// ALSO pre-import the heavy Python stack so the first real run/chat
|
|
86
|
+
// doesn't pay the ~6s cold `import shared.runtime.registry`. Spawn it
|
|
87
|
+
// detached + fire-and-forget with the SAME PYTHONPATH/env a real
|
|
88
|
+
// pipeline spawn uses (see the runner:run handler below) so `shared.*`
|
|
89
|
+
// resolves; this heats the OS file cache + bytecode. Never blocks
|
|
90
|
+
// connect, never throws — swallow everything. Only when a shared
|
|
91
|
+
// bundle exists locally (mirror the ensurePythonEnv guard above).
|
|
92
|
+
if (envResult.ok) {
|
|
93
|
+
try {
|
|
94
|
+
const sharedDir = getSharedDir();
|
|
95
|
+
if (existsSync(sharedDir)) {
|
|
96
|
+
const certBundle = getCertBundlePath();
|
|
97
|
+
const warmEnv = {
|
|
98
|
+
...process.env,
|
|
99
|
+
PYTHONPATH: sharedDir,
|
|
100
|
+
PYTHONIOENCODING: "utf-8",
|
|
101
|
+
PYTHONUTF8: "1",
|
|
102
|
+
...(certBundle ? { SSL_CERT_FILE: certBundle, REQUESTS_CA_BUNDLE: certBundle } : {}),
|
|
103
|
+
};
|
|
104
|
+
const warmChild = spawn(envResult.pythonPath, ["-c", "import shared.runtime.registry"], { env: warmEnv, stdio: "ignore" });
|
|
105
|
+
warmChild.on("error", () => { });
|
|
106
|
+
warmChild.on("exit", (code) => {
|
|
107
|
+
if (code === 0 && opts.verbose) {
|
|
108
|
+
console.log(chalk.gray(" [assistant prewarm] import-warmed"));
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
warmChild.unref();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch { /* fire-and-forget — never throw */ }
|
|
115
|
+
}
|
|
85
116
|
}
|
|
86
117
|
catch (e) {
|
|
87
118
|
if (opts.verbose)
|
|
@@ -901,6 +932,10 @@ export async function connect(opts) {
|
|
|
901
932
|
MEL_ASSISTANT_MODEL: String(payload.model || ""),
|
|
902
933
|
MEL_ASSISTANT_LANGUAGE: String(payload.language || "en"),
|
|
903
934
|
MEL_ASSISTANT_SURFACE: String(payload.surface || ""),
|
|
935
|
+
// Phone toolkit unlocks when the user has a paired phone, on ANY surface
|
|
936
|
+
// (desktop drives the paired phone remotely). The host ORs this with the
|
|
937
|
+
// mobile-native surface check. Empty ⇒ not paired ⇒ no phone tools.
|
|
938
|
+
MEL_ASSISTANT_PHONE_READY: payload.phoneReady ? "1" : "",
|
|
904
939
|
// Connector tool sets the user enabled for this chat (comma-joined service
|
|
905
940
|
// ids). The host seeds a lazy toolkit from these so ANY connector's tools
|
|
906
941
|
// are reachable without exploding context.
|