@tiens.nguyen/gonext-local-worker 1.0.266 → 1.0.268
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/gonext-local-worker.mjs +4 -0
- package/gonext-repl.mjs +85 -9
- package/gonext_agent_chat.py +98 -25
- package/package.json +1 -1
package/gonext-local-worker.mjs
CHANGED
|
@@ -1948,6 +1948,10 @@ async function runAgentChatJob(job) {
|
|
|
1948
1948
|
ragEmbedModel: payload?.ragEmbedModel ?? "",
|
|
1949
1949
|
ragEmbedUrl: payload?.ragEmbedUrl ?? "",
|
|
1950
1950
|
ragTopK: payload?.ragTopK ?? 6,
|
|
1951
|
+
// RAG storage backend for this folder (task #97): "local" = on disk under ~/.gonext
|
|
1952
|
+
// (per-folder, no S3 creds), else "cloud" (the user's S3). Terminal sends the folder's
|
|
1953
|
+
// choice; web never sets it → "cloud". python re-validates and defaults to cloud.
|
|
1954
|
+
ragMode: payload?.ragMode === "local" ? "local" : "cloud",
|
|
1951
1955
|
// Workspaces the agent may read/edit/test code in (see the terminal-vs-web split
|
|
1952
1956
|
// computed above): the real registry for the terminal, a lone temp folder for web.
|
|
1953
1957
|
workspaces: jobWorkspaces,
|
package/gonext-repl.mjs
CHANGED
|
@@ -189,6 +189,7 @@ if (!workerKey || !apiBase) {
|
|
|
189
189
|
const COMMANDS = [
|
|
190
190
|
{ name: "/model", desc: "switch the coding model for this session" },
|
|
191
191
|
{ name: "/test-auto", desc: "toggle auto-test (verify & fix until it passes) for this folder" },
|
|
192
|
+
{ name: "/rag-local", desc: "toggle storing this folder's RAG knowledge base locally (vs cloud/S3)" },
|
|
192
193
|
{ name: "/server", desc: "pick a deployment server (host/user) for this session" },
|
|
193
194
|
{ name: "/revert", usage: "/revert [runId]", desc: "undo the agent's file edits (latest run)" },
|
|
194
195
|
{ name: "/reset", aliases: ["/new"], desc: "forget this folder's saved conversation and start fresh" },
|
|
@@ -611,6 +612,37 @@ async function loadSessionMaxCodeTokens(cwd) {
|
|
|
611
612
|
}
|
|
612
613
|
}
|
|
613
614
|
|
|
615
|
+
// RAG storage backend remembered per folder (task #97): "local" | "cloud" | null (never
|
|
616
|
+
// chosen → we ask once at startup). Local keeps the index on disk under ~/.gonext; cloud
|
|
617
|
+
// uses the user's S3 bucket (the original behavior).
|
|
618
|
+
async function loadSessionRagMode(cwd) {
|
|
619
|
+
try {
|
|
620
|
+
const raw = JSON.parse(await readFile(sessionFilePath(cwd), "utf8"));
|
|
621
|
+
return raw?.ragMode === "local" || raw?.ragMode === "cloud" ? raw.ragMode : null;
|
|
622
|
+
} catch {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Persist just the RAG-mode choice without touching history (used at startup, before the
|
|
628
|
+
// full session is loaded). Read-merge-write so nothing else in the file is lost.
|
|
629
|
+
async function saveSessionRagMode(cwd, mode) {
|
|
630
|
+
try {
|
|
631
|
+
let raw = {};
|
|
632
|
+
try {
|
|
633
|
+
raw = JSON.parse(await readFile(sessionFilePath(cwd), "utf8")) || {};
|
|
634
|
+
} catch {
|
|
635
|
+
raw = {};
|
|
636
|
+
}
|
|
637
|
+
raw.ragMode = mode;
|
|
638
|
+
raw.updatedAt = new Date().toISOString();
|
|
639
|
+
await mkdir(SESSIONS_DIR, { recursive: true });
|
|
640
|
+
await writeFile(sessionFilePath(cwd), JSON.stringify(raw, null, 2) + "\n");
|
|
641
|
+
} catch (err) {
|
|
642
|
+
console.error(dim(`(rag-mode save failed: ${err.message})`));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
614
646
|
async function saveSession(cwd, history) {
|
|
615
647
|
try {
|
|
616
648
|
await mkdir(SESSIONS_DIR, { recursive: true });
|
|
@@ -624,6 +656,7 @@ async function saveSession(cwd, history) {
|
|
|
624
656
|
testAuto: sessionTestAuto,
|
|
625
657
|
deployServer: selectedServer, // remember the /server pick per folder (task #69)
|
|
626
658
|
maxCodeTokens: sessionMaxCodeTokens, // peak per-request tokens (task #84)
|
|
659
|
+
ragMode: sessionRagMode, // local vs cloud RAG for this folder (task #97)
|
|
627
660
|
history: trimmed,
|
|
628
661
|
},
|
|
629
662
|
null,
|
|
@@ -975,6 +1008,10 @@ let sessionCodingModel = "";
|
|
|
975
1008
|
// and fixes → re-tests until it passes before finishing. Default off; remembered per
|
|
976
1009
|
// folder in the session file; sent to /agent-ask as autoTest so python drives the loop.
|
|
977
1010
|
let sessionTestAuto = false;
|
|
1011
|
+
// RAG storage backend for THIS folder (task #97): "local" (index on disk under ~/.gonext,
|
|
1012
|
+
// per-folder, no S3 needed) | "cloud" (the user's S3 bucket) | null (never chosen → ask once
|
|
1013
|
+
// at startup when RAG is enabled). Remembered per folder; sent to /agent-ask as ragMode.
|
|
1014
|
+
let sessionRagMode = null;
|
|
978
1015
|
// Step-budget opt-out (task #80): set once the user picks "yes, don't ask again" at a
|
|
979
1016
|
// max-step prompt. Session-scoped (not persisted per folder); sent to /agent-ask each turn
|
|
980
1017
|
// so the agent auto-extends past the step budget without prompting again.
|
|
@@ -1146,6 +1183,9 @@ async function runAgentTurn(history) {
|
|
|
1146
1183
|
...(alwaysExtendOnMaxStep ? { alwaysExtendOnMaxStep: true } : {}),
|
|
1147
1184
|
// Auto-test mode (/test-auto): the agent verifies its work and fixes until it passes.
|
|
1148
1185
|
...(sessionTestAuto ? { autoTest: true } : {}),
|
|
1186
|
+
// RAG storage backend for this folder (task #97): local disk vs cloud/S3. Only sent
|
|
1187
|
+
// once chosen; absent → the API/python default (cloud), preserving old behavior.
|
|
1188
|
+
...(sessionRagMode ? { ragMode: sessionRagMode } : {}),
|
|
1149
1189
|
// Deploy target chosen via /server — host/user only (no secret), for key-auth deploys.
|
|
1150
1190
|
...(selectedServer ? { deployServer: selectedServer } : {}),
|
|
1151
1191
|
...(sessionCodingModel ? { codingModelOverride: sessionCodingModel } : {}),
|
|
@@ -1288,15 +1328,25 @@ async function runAgentTurn(history) {
|
|
|
1288
1328
|
// "almost done" > "Thinking". When clickable we drive line 1 from the SAME fuller string, so
|
|
1289
1329
|
// the grey expansion CONTINUES where line 1 was cut off instead of repeating the head.
|
|
1290
1330
|
const clickable = !!fullLiveThought && !runningCmd && fullLiveThought.length > max;
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
:
|
|
1294
|
-
|
|
1331
|
+
let label, rest;
|
|
1332
|
+
if (clickable) {
|
|
1333
|
+
// Split at a WORD boundary near the line-1 width so nothing is cut mid-word: the last
|
|
1334
|
+
// space at/before max-1 (unless that snaps back too far into the line — then hard-cut).
|
|
1335
|
+
// Line 1 shows the head; the grey expansion CONTINUES from the same point (no dup, no
|
|
1336
|
+
// split word).
|
|
1337
|
+
const sp = fullLiveThought.lastIndexOf(" ", max - 1);
|
|
1338
|
+
const cut = sp >= Math.floor(max * 0.6) ? sp : max - 1;
|
|
1339
|
+
label = `${fullLiveThought.slice(0, cut).replace(/\s+$/, "")}… (${secs}s)`;
|
|
1340
|
+
rest = fullLiveThought.slice(cut).trim();
|
|
1341
|
+
} else {
|
|
1342
|
+
const primary = runningCmd
|
|
1343
|
+
? `Running ${runningCmd}`
|
|
1295
1344
|
: liveThought || (almostDone ? "Almost done" : "Thinking");
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1345
|
+
label = `${fit(primary)}… (${secs}s)`;
|
|
1346
|
+
rest = "";
|
|
1347
|
+
}
|
|
1348
|
+
// When clicked, unfold the remainder as up to 4 wrapped GREY lines BETWEEN the thought and
|
|
1349
|
+
// the playful-word line.
|
|
1300
1350
|
const expLines = thoughtExpanded && rest ? wrapThought(rest, max, 4) : [];
|
|
1301
1351
|
const drawn = 2 + expLines.length;
|
|
1302
1352
|
const rows = process.stdout.rows || 24;
|
|
@@ -1759,15 +1809,27 @@ async function main() {
|
|
|
1759
1809
|
sessionTestAuto = await loadSessionTestAuto(resolve(process.cwd()));
|
|
1760
1810
|
selectedServer = await loadSessionServer(resolve(process.cwd()));
|
|
1761
1811
|
sessionMaxCodeTokens = await loadSessionMaxCodeTokens(resolve(process.cwd()));
|
|
1812
|
+
sessionRagMode = await loadSessionRagMode(resolve(process.cwd())); // task #97: local/cloud/null
|
|
1762
1813
|
// Show which models will answer, straight from user settings (also validates auth).
|
|
1763
1814
|
try {
|
|
1764
1815
|
const probe = await fetchAgentPayload([{ role: "user", content: "ping" }]);
|
|
1765
1816
|
const p = probe?.payload ?? {};
|
|
1817
|
+
// RAG storage choice (task #97): ask ONCE per folder, and only when RAG is actually
|
|
1818
|
+
// enabled in Settings. Default Yes = LOCAL (kept on this machine, no S3 needed).
|
|
1819
|
+
if (p.ragEnabled && sessionRagMode === null) {
|
|
1820
|
+
const local = await askYesNo(
|
|
1821
|
+
"Store this folder's RAG knowledge base LOCALLY on this machine?\n" +
|
|
1822
|
+
"(Yes = local, kept in ~/.gonext per folder — no S3 needed; No = cloud, your S3 bucket)",
|
|
1823
|
+
true /* default Yes = local */
|
|
1824
|
+
);
|
|
1825
|
+
sessionRagMode = local ? "local" : "cloud";
|
|
1826
|
+
await saveSessionRagMode(resolve(process.cwd()), sessionRagMode);
|
|
1827
|
+
}
|
|
1766
1828
|
console.log(
|
|
1767
1829
|
dim(
|
|
1768
1830
|
`agent model: ${p.agentModelId || probe?.modelKey || "?"}` +
|
|
1769
1831
|
(p.codingModelId ? ` · coder: ${p.codingModelId}` : "") +
|
|
1770
|
-
(p.ragEnabled ?
|
|
1832
|
+
(p.ragEnabled ? ` · RAG ${sessionRagMode === "cloud" ? "cloud" : "local"}` : "") +
|
|
1771
1833
|
` · auto-test: ${sessionTestAuto ? "on" : "off"}` +
|
|
1772
1834
|
(selectedServer ? ` · deploy: ${selectedServer.name} (${selectedServer.user}@${selectedServer.host})` : "")
|
|
1773
1835
|
) + (sessionTestAuto ? "" : dim(" (/test-auto to enable)"))
|
|
@@ -1875,6 +1937,20 @@ async function main() {
|
|
|
1875
1937
|
);
|
|
1876
1938
|
continue;
|
|
1877
1939
|
}
|
|
1940
|
+
if (line === "/rag-local") {
|
|
1941
|
+
// Toggle this folder's RAG store between local disk and the cloud (S3). task #97.
|
|
1942
|
+
sessionRagMode = sessionRagMode === "local" ? "cloud" : "local";
|
|
1943
|
+
await saveSession(cwd, history); // persist the choice for this folder
|
|
1944
|
+
console.log(
|
|
1945
|
+
(sessionRagMode === "local" ? green("RAG: LOCAL") : dim("RAG: cloud")) +
|
|
1946
|
+
dim(
|
|
1947
|
+
sessionRagMode === "local"
|
|
1948
|
+
? " — this folder's knowledge base is stored on this machine (~/.gonext), no S3 needed.\n"
|
|
1949
|
+
: " — this folder's knowledge base is stored in your S3 bucket.\n"
|
|
1950
|
+
)
|
|
1951
|
+
);
|
|
1952
|
+
continue;
|
|
1953
|
+
}
|
|
1878
1954
|
if (line === "/reset" || line === "/new") {
|
|
1879
1955
|
history.length = 0;
|
|
1880
1956
|
await clearSession(cwd);
|
package/gonext_agent_chat.py
CHANGED
|
@@ -1757,6 +1757,21 @@ def _rag_base_dir(source_key: str) -> str:
|
|
|
1757
1757
|
return _rag_workdir(source_key)
|
|
1758
1758
|
|
|
1759
1759
|
|
|
1760
|
+
def _rag_local_index_dir(source_url: str) -> str:
|
|
1761
|
+
"""Local RAG index dir for a source, scoped PER-FOLDER (task #97): the vector shards
|
|
1762
|
+
live at ~/.gonext/rag/<sha256(activeFolder)>/<sha256(sourceUrl)>/. Keying on the active
|
|
1763
|
+
terminal folder (the SAME sha256 scheme the REPL uses for its session files) isolates
|
|
1764
|
+
each project's knowledge base, so the same URL indexed from two folders stays separate."""
|
|
1765
|
+
import hashlib
|
|
1766
|
+
import os
|
|
1767
|
+
folder = _WS_ACTIVE or os.getcwd()
|
|
1768
|
+
fhash = hashlib.sha256(os.path.realpath(folder).encode("utf-8")).hexdigest()[:32]
|
|
1769
|
+
base = os.path.join(os.path.expanduser("~"), ".gonext", "rag", fhash,
|
|
1770
|
+
_rag_source_key(source_url))
|
|
1771
|
+
os.makedirs(base, exist_ok=True)
|
|
1772
|
+
return base
|
|
1773
|
+
|
|
1774
|
+
|
|
1760
1775
|
def _rag_download(url: str, dest_path: str = "") -> tuple:
|
|
1761
1776
|
"""Download a public URL with SSRF + size guards — IDEMPOTENT per URL. Every URL
|
|
1762
1777
|
maps to a deterministic work dir (~/.gonext/rag-work/<urlHash>/); relative
|
|
@@ -2827,9 +2842,24 @@ def run_agent_chat(cfg):
|
|
|
2827
2842
|
_boto3_ok = True
|
|
2828
2843
|
except Exception: # noqa: BLE001
|
|
2829
2844
|
_boto3_ok = False
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2845
|
+
# RAG storage backend (task #97): "local" keeps the index on disk under ~/.gonext,
|
|
2846
|
+
# PER-FOLDER, and needs NO S3 creds / boto3 — only the embedding server. "cloud" (default,
|
|
2847
|
+
# and what the web app always uses) stores it on the user's own S3 bucket via boto3. The
|
|
2848
|
+
# terminal REPL picks this per-folder and sends it as cfg.ragMode.
|
|
2849
|
+
rag_mode = (cfg.get("ragMode") or "cloud").strip().lower()
|
|
2850
|
+
if rag_mode not in ("local", "cloud"):
|
|
2851
|
+
rag_mode = "cloud"
|
|
2852
|
+
_RAG_LOCAL = rag_mode == "local"
|
|
2853
|
+
_RAG_AVAILABLE = bool(
|
|
2854
|
+
rag_enabled and rag_embed_base and (
|
|
2855
|
+
_RAG_LOCAL or (rag_akid and rag_secret and _boto3_ok)
|
|
2856
|
+
)
|
|
2857
|
+
)
|
|
2858
|
+
if rag_enabled and not _RAG_LOCAL and not _boto3_ok:
|
|
2859
|
+
_log("RAG (cloud) requested but boto3 is not installed in the worker python — RAG "
|
|
2860
|
+
"tools disabled (switch to local RAG with /rag-local to avoid needing S3/boto3)")
|
|
2861
|
+
if _RAG_AVAILABLE:
|
|
2862
|
+
_log(f"RAG mode: {'LOCAL (index under ~/.gonext/rag, per-folder)' if _RAG_LOCAL else 'cloud (S3)'}")
|
|
2833
2863
|
|
|
2834
2864
|
# ---- Workspaces (local folders the agent may read/edit/test code in) ----
|
|
2835
2865
|
# Registered on the Mac via `gonext-local-worker workspace add <path>`; the worker
|
|
@@ -2937,6 +2967,59 @@ def run_agent_chat(cfg):
|
|
|
2937
2967
|
break
|
|
2938
2968
|
return out
|
|
2939
2969
|
|
|
2970
|
+
# --- Storage backend abstraction (task #97): local disk vs the user's S3, chosen by
|
|
2971
|
+
# ragMode. The three rag_* tools go through these so their logic stays identical. ---
|
|
2972
|
+
def _rag_write_shard(source_url: str, records: list, tag: str) -> None:
|
|
2973
|
+
"""Write one JSONL shard of chunk records. tag is 'chunks' (index) or 'chunks-add'."""
|
|
2974
|
+
import os
|
|
2975
|
+
import time as _t
|
|
2976
|
+
body = "\n".join(json.dumps(r, ensure_ascii=False) for r in records)
|
|
2977
|
+
name = f"{tag}-{int(_t.time())}.jsonl"
|
|
2978
|
+
if _RAG_LOCAL:
|
|
2979
|
+
path = os.path.join(_rag_local_index_dir(source_url), name)
|
|
2980
|
+
with open(path, "w", encoding="utf-8") as fh:
|
|
2981
|
+
fh.write(body)
|
|
2982
|
+
return
|
|
2983
|
+
client, bucket, prefix = _rag_s3_and_loc()
|
|
2984
|
+
base = _rag_prefix_for(source_url, prefix)
|
|
2985
|
+
client.put_object(Bucket=bucket, Key=f"{base}/{name}",
|
|
2986
|
+
Body=body.encode("utf-8"), ContentType="application/x-ndjson")
|
|
2987
|
+
|
|
2988
|
+
def _rag_write_manifest(source_url: str, manifest: dict) -> None:
|
|
2989
|
+
import os
|
|
2990
|
+
data = json.dumps(manifest).encode("utf-8")
|
|
2991
|
+
if _RAG_LOCAL:
|
|
2992
|
+
with open(os.path.join(_rag_local_index_dir(source_url), "manifest.json"), "wb") as fh:
|
|
2993
|
+
fh.write(data)
|
|
2994
|
+
return
|
|
2995
|
+
client, bucket, prefix = _rag_s3_and_loc()
|
|
2996
|
+
base = _rag_prefix_for(source_url, prefix)
|
|
2997
|
+
client.put_object(Bucket=bucket, Key=f"{base}/manifest.json",
|
|
2998
|
+
Body=data, ContentType="application/json")
|
|
2999
|
+
|
|
3000
|
+
def _rag_load_all(source_url: str) -> list:
|
|
3001
|
+
"""Load every chunk record for a source across all shards (local or S3)."""
|
|
3002
|
+
import glob as _glob
|
|
3003
|
+
import os
|
|
3004
|
+
if _RAG_LOCAL:
|
|
3005
|
+
out = []
|
|
3006
|
+
for fp in sorted(_glob.glob(os.path.join(_rag_local_index_dir(source_url), "chunks*.jsonl"))):
|
|
3007
|
+
try:
|
|
3008
|
+
with open(fp, "r", encoding="utf-8", errors="replace") as fh:
|
|
3009
|
+
for line in fh:
|
|
3010
|
+
line = line.strip()
|
|
3011
|
+
if line:
|
|
3012
|
+
try:
|
|
3013
|
+
out.append(json.loads(line))
|
|
3014
|
+
except json.JSONDecodeError:
|
|
3015
|
+
pass
|
|
3016
|
+
except OSError:
|
|
3017
|
+
continue
|
|
3018
|
+
return out
|
|
3019
|
+
client, bucket, prefix = _rag_s3_and_loc()
|
|
3020
|
+
base = _rag_prefix_for(source_url, prefix)
|
|
3021
|
+
return _rag_load_chunks(client, bucket, base)
|
|
3022
|
+
|
|
2940
3023
|
def _prior_email_preview() -> bool:
|
|
2941
3024
|
"""True if an earlier assistant turn showed a send_email preview awaiting confirm."""
|
|
2942
3025
|
for m in reversed(messages[:last_user_idx]):
|
|
@@ -3172,7 +3255,7 @@ def run_agent_chat(cfg):
|
|
|
3172
3255
|
" - read_text_file(path) — read ONE file's text (e.g. a README) directly. Best for a QUICK "
|
|
3173
3256
|
"project summary — you do NOT need to index for that.\n"
|
|
3174
3257
|
" - rag_index(path, source_url) — index MANY text/code files into a searchable knowledge "
|
|
3175
|
-
"base (embeds
|
|
3258
|
+
"base (embeds the chunks). source_url MUST be the ORIGINAL url the user gave. Use for deep Q&A "
|
|
3176
3259
|
"across the whole project.\n"
|
|
3177
3260
|
" - rag_add(source_url, text) — add extra info the user provides to that knowledge base.\n"
|
|
3178
3261
|
" - rag_search(source_url, query) — retrieve the most relevant chunks to answer a question.\n"
|
|
@@ -3312,7 +3395,7 @@ def run_agent_chat(cfg):
|
|
|
3312
3395
|
"edit_lines(file, start, end, new_content) to change them, THEN "
|
|
3313
3396
|
"run_command('…test…') to VERIFY if run permission exists — read failures and fix, "
|
|
3314
3397
|
"iterate. Finish with final_answer summarizing WHAT you changed (files + lines). "
|
|
3315
|
-
"AFTER editing, re-read with grep_repo/read_file_lines (an
|
|
3398
|
+
"AFTER editing, re-read with grep_repo/read_file_lines (an indexed knowledge base may be "
|
|
3316
3399
|
"stale — do not trust rag_search for freshly edited code).\n"
|
|
3317
3400
|
) if _WS_AVAILABLE else ""
|
|
3318
3401
|
_pdf_read_choose_line = (
|
|
@@ -4991,14 +5074,16 @@ def run_agent_chat(cfg):
|
|
|
4991
5074
|
|
|
4992
5075
|
@tool
|
|
4993
5076
|
def rag_index(path: str, source_url: str) -> str:
|
|
4994
|
-
"""Index a local file or directory of TEXT/code files into the knowledge base for source_url (stores embeddings
|
|
5077
|
+
"""Index a local file or directory of TEXT/code files into the knowledge base for source_url (stores embeddings in the knowledge base). Use after unzip_file so the files become searchable. source_url MUST be the original URL the user provided.
|
|
4995
5078
|
|
|
4996
5079
|
Args:
|
|
4997
5080
|
path: local file or directory to index (from unzip_file).
|
|
4998
5081
|
source_url: the original URL the user gave — used as the knowledge-base key.
|
|
4999
5082
|
"""
|
|
5000
5083
|
if not _RAG_AVAILABLE:
|
|
5001
|
-
msg = "Error: RAG is not configured (enable
|
|
5084
|
+
msg = ("Error: RAG is not configured (enable RAG in Settings"
|
|
5085
|
+
+ ("" if _RAG_LOCAL else " and add AWS credentials, or switch to local RAG")
|
|
5086
|
+
+ ").")
|
|
5002
5087
|
_last_obs["text"] = msg
|
|
5003
5088
|
return msg
|
|
5004
5089
|
_emit({"type": "step", "text": f"RAG source → {source_url}"})
|
|
@@ -5025,21 +5110,16 @@ def run_agent_chat(cfg):
|
|
|
5025
5110
|
for r, v in zip(batch, vecs):
|
|
5026
5111
|
r["embedding"] = v
|
|
5027
5112
|
_emit({"type": "step", "text": f"Embedded {min(i + 64, len(records))}/{len(records)} chunks…"})
|
|
5028
|
-
|
|
5029
|
-
base = _rag_prefix_for(source_url, prefix)
|
|
5030
|
-
shard = f"{base}/chunks-{int(_t.time())}.jsonl"
|
|
5031
|
-
body = "\n".join(json.dumps(r, ensure_ascii=False) for r in records).encode("utf-8")
|
|
5032
|
-
client.put_object(Bucket=bucket, Key=shard, Body=body, ContentType="application/x-ndjson")
|
|
5113
|
+
_rag_write_shard(source_url, records, "chunks")
|
|
5033
5114
|
manifest = {
|
|
5034
5115
|
"sourceUrl": source_url, "embedModel": rag_embed_model,
|
|
5035
5116
|
"dims": len(records[0].get("embedding") or []),
|
|
5036
5117
|
"files": files, "chunks": len(records),
|
|
5037
5118
|
"updatedAt": _t.strftime("%Y-%m-%dT%H:%M:%SZ", _t.gmtime()),
|
|
5038
5119
|
}
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
out = (f"Indexed {files} files into {len(records)} chunks for {source_url}. "
|
|
5120
|
+
_rag_write_manifest(source_url, manifest)
|
|
5121
|
+
where = "locally" if _RAG_LOCAL else "in the cloud knowledge base"
|
|
5122
|
+
out = (f"Indexed {files} files into {len(records)} chunks for {source_url} ({where}). "
|
|
5043
5123
|
f"Use rag_search(source_url='{source_url}', query=...) to retrieve, then answer the user.")
|
|
5044
5124
|
_last_obs["text"] = out
|
|
5045
5125
|
return out
|
|
@@ -5067,16 +5147,11 @@ def run_agent_chat(cfg):
|
|
|
5067
5147
|
_emit({"type": "step", "text": f"RAG source → {source_url}"})
|
|
5068
5148
|
_emit({"type": "step", "text": "Adding info to RAG…"})
|
|
5069
5149
|
try:
|
|
5070
|
-
import time as _t
|
|
5071
5150
|
records = _rag_chunk_text(text, "user-note", ".txt")
|
|
5072
5151
|
vecs = _embed(rag_embed_base, rag_embed_model, [r["text"] for r in records])
|
|
5073
5152
|
for r, v in zip(records, vecs):
|
|
5074
5153
|
r["embedding"] = v
|
|
5075
|
-
|
|
5076
|
-
base = _rag_prefix_for(source_url, prefix)
|
|
5077
|
-
shard = f"{base}/chunks-add-{int(_t.time())}.jsonl"
|
|
5078
|
-
body = "\n".join(json.dumps(r, ensure_ascii=False) for r in records).encode("utf-8")
|
|
5079
|
-
client.put_object(Bucket=bucket, Key=shard, Body=body, ContentType="application/x-ndjson")
|
|
5154
|
+
_rag_write_shard(source_url, records, "chunks-add")
|
|
5080
5155
|
out = f"Added {len(records)} chunk(s) to the knowledge base for {source_url}."
|
|
5081
5156
|
_last_obs["text"] = out
|
|
5082
5157
|
return out
|
|
@@ -5101,9 +5176,7 @@ def run_agent_chat(cfg):
|
|
|
5101
5176
|
_emit({"type": "step", "text": f"RAG source → {source_url}"})
|
|
5102
5177
|
_emit({"type": "step", "text": f"Searching RAG → {query[:60]}"})
|
|
5103
5178
|
try:
|
|
5104
|
-
|
|
5105
|
-
base = _rag_prefix_for(source_url, prefix)
|
|
5106
|
-
chunks = _rag_load_chunks(client, bucket, base)
|
|
5179
|
+
chunks = _rag_load_all(source_url)
|
|
5107
5180
|
if not chunks:
|
|
5108
5181
|
msg = f"No knowledge base found for {source_url}. Run rag_index first."
|
|
5109
5182
|
_last_obs["text"] = msg
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gonext-local-worker",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.268",
|
|
4
4
|
"description": "Polls GoNext cloud API for async local LLM jobs and runs them against Ollama/OpenAI-compatible servers on this Mac",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|