axel-protocol 2.3.0__tar.gz → 2.3.2__tar.gz
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.
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/PKG-INFO +1 -1
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/cli.py +57 -1
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/server.py +57 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/static/monitor.html +191 -1
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/pyproject.toml +1 -1
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/.github/workflows/ci.yml +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/.github/workflows/publish.yml +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/.gitignore +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/CHANGELOG.md +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/CONTRIBUTING.md +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/Dockerfile +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/LICENSE +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/README.md +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/__init__.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/adapters/__init__.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/adapters/bedrock.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/adapters/cohere.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/adapters/gemini.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/adapters/groq.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/adapters/litellm.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/adapters/mistral.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/adapters/together.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/client.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/core.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/learning.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/persistence.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/testing.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/axel/universal.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/docker-compose.yml +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/docs/message-schemas.md +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/examples/demo_live.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/install.sh +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/tests/__init__.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/tests/test_adapters.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/tests/test_client.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/tests/test_core.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/tests/test_new_features.py +0 -0
- {axel_protocol-2.3.0 → axel_protocol-2.3.2}/tests/test_server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: axel-protocol
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
4
4
|
Summary: AXEL — Agent eXchange Language: a universal protocol for multi-LLM networks
|
|
5
5
|
Project-URL: Homepage, https://github.com/sectorx/axel-protocol
|
|
6
6
|
Project-URL: Repository, https://github.com/sectorx/axel-protocol
|
|
@@ -427,6 +427,27 @@ def _fire_task(server: str, fr: str, to: str, action: str, body: dict) -> None:
|
|
|
427
427
|
pass # demo continues even if event fire fails
|
|
428
428
|
|
|
429
429
|
|
|
430
|
+
def _fire_progress(server: str, run: int, step: int, total: int, agent: str, action: str) -> None:
|
|
431
|
+
"""Broadcast pipeline progress so the dashboard can show a step indicator."""
|
|
432
|
+
import json as _json
|
|
433
|
+
import urllib.request as _ur
|
|
434
|
+
import uuid as _uuid
|
|
435
|
+
msg = {
|
|
436
|
+
"t": "PROG", "v": "2.1",
|
|
437
|
+
"id": str(_uuid.uuid4()),
|
|
438
|
+
"fr": "axel-demo", "to": "dashboard",
|
|
439
|
+
"body": {"run": run, "step": step, "total": total,
|
|
440
|
+
"agent": agent, "action": action},
|
|
441
|
+
}
|
|
442
|
+
data = _json.dumps(msg).encode()
|
|
443
|
+
req = _ur.Request(f"{server}/send", data=data,
|
|
444
|
+
headers={"Content-Type": "application/json"}, method="POST")
|
|
445
|
+
try:
|
|
446
|
+
_ur.urlopen(req, timeout=3)
|
|
447
|
+
except Exception:
|
|
448
|
+
pass
|
|
449
|
+
|
|
450
|
+
|
|
430
451
|
def _fire_result(server: str, fr: str, to: str, action: str, body: dict) -> None:
|
|
431
452
|
"""POST an AXEL OK result message through the server so SSE/dashboard sees it."""
|
|
432
453
|
import json as _json
|
|
@@ -657,6 +678,24 @@ def cmd_demo(args): # noqa: C901
|
|
|
657
678
|
"Building trust and reliability in multi-agent systems",
|
|
658
679
|
]
|
|
659
680
|
|
|
681
|
+
def _check_user_queue(srv):
|
|
682
|
+
"""Check if a user submitted a task via the dashboard. Returns task string or None."""
|
|
683
|
+
try:
|
|
684
|
+
resp = _ur.urlopen(f"{srv}/queue", timeout=3)
|
|
685
|
+
data = json.loads(resp.read())
|
|
686
|
+
return data.get("task") # None if queue was empty
|
|
687
|
+
except Exception:
|
|
688
|
+
return None
|
|
689
|
+
|
|
690
|
+
def _fetch_topic_library(srv):
|
|
691
|
+
"""Fetch user-defined learning topics from the server. Returns list or []."""
|
|
692
|
+
try:
|
|
693
|
+
resp = _ur.urlopen(f"{srv}/topics", timeout=3)
|
|
694
|
+
data = json.loads(resp.read())
|
|
695
|
+
return data.get("topics", [])
|
|
696
|
+
except Exception:
|
|
697
|
+
return []
|
|
698
|
+
|
|
660
699
|
def _fetch_memory_context(srv):
|
|
661
700
|
"""Pull top lessons from shared memory and format as context."""
|
|
662
701
|
import json
|
|
@@ -674,12 +713,16 @@ def cmd_demo(args): # noqa: C901
|
|
|
674
713
|
except Exception:
|
|
675
714
|
return ""
|
|
676
715
|
|
|
716
|
+
step_counter = [0] # mutable so inner func can update it
|
|
717
|
+
|
|
677
718
|
def step(fr, to, action, prompt, learn_key=None, learn_insight=None, mem_ctx=""):
|
|
678
719
|
"""Fire a task, call the LLM (or use mock), fire the result."""
|
|
720
|
+
step_counter[0] += 1
|
|
679
721
|
full_prompt = (mem_ctx + prompt) if mem_ctx else prompt
|
|
680
722
|
mdl = _agent_model(to)
|
|
681
723
|
mdl_short = mdl.split("/")[-1].replace(":free", "") if mdl != "auto/free" else "free"
|
|
682
724
|
print(f"\n {fr} → {to} [{action}] ({mdl_short})")
|
|
725
|
+
_fire_progress(server, run_count, step_counter[0], 5, to, action)
|
|
683
726
|
_fire_task(server, fr, to, action, {"prompt": prompt[:200]})
|
|
684
727
|
if mock:
|
|
685
728
|
time.sleep(0.8)
|
|
@@ -713,7 +756,20 @@ def cmd_demo(args): # noqa: C901
|
|
|
713
756
|
try:
|
|
714
757
|
while True:
|
|
715
758
|
run_count += 1
|
|
716
|
-
|
|
759
|
+
step_counter[0] = 0 # reset step counter for new run
|
|
760
|
+
# Check if user submitted a one-off task from the dashboard
|
|
761
|
+
user_task = _check_user_queue(server)
|
|
762
|
+
if user_task:
|
|
763
|
+
topic = user_task
|
|
764
|
+
print(f"\n ⌨️ User task received: \"{topic}\"")
|
|
765
|
+
else:
|
|
766
|
+
# Pull user-defined topic library — use it if populated
|
|
767
|
+
user_topics = _fetch_topic_library(server)
|
|
768
|
+
if user_topics:
|
|
769
|
+
topic = user_topics[(run_count - 1) % len(user_topics)]
|
|
770
|
+
print(f"\n 📚 Topic Library ({len(user_topics)} topics) — exploring: \"{topic}\"")
|
|
771
|
+
else:
|
|
772
|
+
topic = _TOPICS[(run_count - 1) % len(_TOPICS)]
|
|
717
773
|
|
|
718
774
|
# Fetch lessons learned so far — injected into prompts so agents remember
|
|
719
775
|
mem_ctx = _fetch_memory_context(server) if run_count > 1 else ""
|
|
@@ -375,6 +375,12 @@ class AXELServer:
|
|
|
375
375
|
# Liveness: agent_id → last-seen timestamp (unix seconds)
|
|
376
376
|
self._last_seen: Dict[str, float] = {}
|
|
377
377
|
self._liveness_lock = threading.Lock()
|
|
378
|
+
# User task queue — dashboard POSTs tasks here; demo loop pops them
|
|
379
|
+
self._task_queue: list = []
|
|
380
|
+
self._task_queue_lock = threading.Lock()
|
|
381
|
+
# User-defined topic library — agents explore these instead of built-in topics
|
|
382
|
+
self._topic_library: list = []
|
|
383
|
+
self._topic_lock = threading.Lock()
|
|
378
384
|
# Restore persisted agents/memory if db is attached
|
|
379
385
|
if self.persistence:
|
|
380
386
|
self._restore_from_db()
|
|
@@ -927,6 +933,57 @@ class AXELServer:
|
|
|
927
933
|
result["db_stats"] = self.persistence.stats()
|
|
928
934
|
return result
|
|
929
935
|
|
|
936
|
+
# ── User Task Queue ───────────────────────────────────────
|
|
937
|
+
@app.post("/queue")
|
|
938
|
+
async def queue_task(request: Request):
|
|
939
|
+
"""Dashboard submits a user task here; the demo loop picks it up."""
|
|
940
|
+
body = await request.json()
|
|
941
|
+
task = (body.get("task") or "").strip()
|
|
942
|
+
if not task:
|
|
943
|
+
return {"ok": False, "error": "task is required"}
|
|
944
|
+
with self._task_queue_lock:
|
|
945
|
+
self._task_queue.append(task)
|
|
946
|
+
# Broadcast so the dashboard knows the task was received
|
|
947
|
+
self.event_bus.publish("user.task", {"task": task, "queued": len(self._task_queue)})
|
|
948
|
+
return {"ok": True, "queued": len(self._task_queue)}
|
|
949
|
+
|
|
950
|
+
@app.get("/queue")
|
|
951
|
+
async def peek_queue():
|
|
952
|
+
"""Pop and return the next user task, or null if queue is empty."""
|
|
953
|
+
with self._task_queue_lock:
|
|
954
|
+
task = self._task_queue.pop(0) if self._task_queue else None
|
|
955
|
+
return {"task": task, "remaining": len(self._task_queue)}
|
|
956
|
+
|
|
957
|
+
# ── Topic Library ─────────────────────────────────────────
|
|
958
|
+
@app.post("/topics")
|
|
959
|
+
async def add_topic(request: Request):
|
|
960
|
+
"""Add a topic to the learning library."""
|
|
961
|
+
body = await request.json()
|
|
962
|
+
topic = (body.get("topic") or "").strip()
|
|
963
|
+
if not topic:
|
|
964
|
+
return {"ok": False, "error": "topic is required"}
|
|
965
|
+
with self._topic_lock:
|
|
966
|
+
if topic not in self._topic_library:
|
|
967
|
+
self._topic_library.append(topic)
|
|
968
|
+
self.event_bus.publish("topics.updated", {"topics": list(self._topic_library)})
|
|
969
|
+
return {"ok": True, "topics": list(self._topic_library)}
|
|
970
|
+
|
|
971
|
+
@app.get("/topics")
|
|
972
|
+
async def list_topics():
|
|
973
|
+
"""Return all user-defined topics."""
|
|
974
|
+
with self._topic_lock:
|
|
975
|
+
return {"topics": list(self._topic_library)}
|
|
976
|
+
|
|
977
|
+
@app.delete("/topics/{idx}")
|
|
978
|
+
async def remove_topic(idx: int):
|
|
979
|
+
"""Remove a topic by index."""
|
|
980
|
+
with self._topic_lock:
|
|
981
|
+
if idx < 0 or idx >= len(self._topic_library):
|
|
982
|
+
return {"ok": False, "error": "index out of range"}
|
|
983
|
+
removed = self._topic_library.pop(idx)
|
|
984
|
+
self.event_bus.publish("topics.updated", {"topics": list(self._topic_library)})
|
|
985
|
+
return {"ok": True, "removed": removed, "topics": list(self._topic_library)}
|
|
986
|
+
|
|
930
987
|
# ── SSE Event Stream ─────────────────────────────────────
|
|
931
988
|
@app.get("/stream")
|
|
932
989
|
async def event_stream(request: Request):
|
|
@@ -63,6 +63,30 @@ header {
|
|
|
63
63
|
.stat-val { font-size: 20px; font-weight: 700; color: var(--accent); line-height: 1; }
|
|
64
64
|
.stat-lbl { font-size: 9px; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); margin-top: 2px; }
|
|
65
65
|
|
|
66
|
+
/* ── Mission input bar ── */
|
|
67
|
+
.mission-bar {
|
|
68
|
+
display: flex; align-items: center; gap: 10px;
|
|
69
|
+
padding: 8px 18px; background: rgba(129,140,248,.04);
|
|
70
|
+
border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
71
|
+
}
|
|
72
|
+
.mission-input {
|
|
73
|
+
flex: 1; background: var(--bg); border: 1px solid var(--border); color: var(--text);
|
|
74
|
+
padding: 7px 12px; border-radius: 7px; font-size: 12px;
|
|
75
|
+
transition: border-color .2s;
|
|
76
|
+
}
|
|
77
|
+
.mission-input:focus { outline: none; border-color: var(--accent); }
|
|
78
|
+
.mission-input::placeholder { color: var(--muted); }
|
|
79
|
+
.mission-btn {
|
|
80
|
+
background: var(--accent); color: #fff; border: none;
|
|
81
|
+
padding: 7px 16px; border-radius: 7px; font-size: 12px; font-weight: 600; cursor: pointer;
|
|
82
|
+
white-space: nowrap; transition: opacity .2s;
|
|
83
|
+
}
|
|
84
|
+
.mission-btn:hover { opacity: .85; }
|
|
85
|
+
.mission-btn:disabled { opacity: .4; cursor: default; }
|
|
86
|
+
.mission-status { font-size: 11px; color: var(--muted); white-space: nowrap; min-width: 120px; }
|
|
87
|
+
.mission-status.sent { color: var(--green); }
|
|
88
|
+
.mission-status.waiting { color: var(--yellow); }
|
|
89
|
+
|
|
66
90
|
/* ── Progress strip ── */
|
|
67
91
|
.progress-strip {
|
|
68
92
|
display: none; align-items: center; gap: 14px;
|
|
@@ -199,6 +223,37 @@ header {
|
|
|
199
223
|
.conf-bar { height: 2px; background: var(--border); border-radius: 2px; margin-top: 5px; }
|
|
200
224
|
.conf-fill { height: 100%; background: var(--green); border-radius: 2px; transition: width .5s; }
|
|
201
225
|
|
|
226
|
+
/* ── Topic Library bar ── */
|
|
227
|
+
.topic-bar {
|
|
228
|
+
display: flex; align-items: flex-start; gap: 10px;
|
|
229
|
+
padding: 8px 18px; background: rgba(251,191,36,.03);
|
|
230
|
+
border-bottom: 1px solid var(--border); flex-shrink: 0;
|
|
231
|
+
}
|
|
232
|
+
.topic-bar-label { font-size: 10px; text-transform: uppercase; letter-spacing: .7px; color: var(--yellow); font-weight: 700; }
|
|
233
|
+
.topic-bar-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 5px; }
|
|
234
|
+
.topic-bar-row { display: flex; align-items: center; gap: 8px; }
|
|
235
|
+
.topic-input {
|
|
236
|
+
flex: 1; background: var(--bg); border: 1px solid var(--border); color: var(--text);
|
|
237
|
+
padding: 5px 10px; border-radius: 6px; font-size: 12px; min-width: 0;
|
|
238
|
+
}
|
|
239
|
+
.topic-input:focus { outline: none; border-color: var(--yellow); }
|
|
240
|
+
.topic-add-btn {
|
|
241
|
+
padding: 5px 12px; border-radius: 6px; border: 1px solid rgba(251,191,36,.4);
|
|
242
|
+
background: rgba(251,191,36,.08); color: var(--yellow); font-size: 12px; cursor: pointer;
|
|
243
|
+
white-space: nowrap; transition: background .15s;
|
|
244
|
+
}
|
|
245
|
+
.topic-add-btn:hover { background: rgba(251,191,36,.18); }
|
|
246
|
+
.topic-chips { display: flex; flex-wrap: wrap; gap: 5px; }
|
|
247
|
+
.topic-chip {
|
|
248
|
+
display: inline-flex; align-items: center; gap: 5px;
|
|
249
|
+
background: rgba(251,191,36,.1); border: 1px solid rgba(251,191,36,.25);
|
|
250
|
+
border-radius: 20px; padding: 3px 8px 3px 10px; font-size: 11px; color: var(--text);
|
|
251
|
+
}
|
|
252
|
+
.topic-chip-text { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 200px; }
|
|
253
|
+
.topic-chip-x { cursor: pointer; color: var(--muted); font-size: 14px; line-height: 1; flex-shrink: 0; transition: color .15s; }
|
|
254
|
+
.topic-chip-x:hover { color: var(--red); }
|
|
255
|
+
.topic-empty { font-size: 11px; color: var(--muted); }
|
|
256
|
+
|
|
202
257
|
/* ── Settings overlay ── */
|
|
203
258
|
.settings-overlay {
|
|
204
259
|
position: fixed; inset: 0; z-index: 200;
|
|
@@ -289,6 +344,27 @@ header {
|
|
|
289
344
|
<div class="stat"><div class="stat-val" id="sUptime">–</div><div class="stat-lbl">Uptime</div></div>
|
|
290
345
|
</div>
|
|
291
346
|
|
|
347
|
+
<div class="mission-bar">
|
|
348
|
+
<span style="font-size:13px">🎯</span>
|
|
349
|
+
<input class="mission-input" id="missionInput" placeholder="Give the agents a task… e.g. 'Research the impact of AI on creative industries'" />
|
|
350
|
+
<button class="mission-btn" id="missionBtn" onclick="submitMission()">Send to Agents →</button>
|
|
351
|
+
<span class="mission-status" id="missionStatus">agents running autonomously</span>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<div class="topic-bar">
|
|
355
|
+
<span style="font-size:15px;line-height:1;padding-top:1px">📚</span>
|
|
356
|
+
<div class="topic-bar-body">
|
|
357
|
+
<div class="topic-bar-label">Learning Topics <span style="font-weight:400;text-transform:none;letter-spacing:0;color:var(--muted)">— agents will cycle through these instead of built-in topics</span></div>
|
|
358
|
+
<div class="topic-bar-row">
|
|
359
|
+
<input class="topic-input" id="topicInput" placeholder="Add a topic… e.g. 'The ethics of autonomous AI systems'" />
|
|
360
|
+
<button class="topic-add-btn" onclick="addTopic()">+ Add Topic</button>
|
|
361
|
+
</div>
|
|
362
|
+
<div class="topic-chips" id="topicChips">
|
|
363
|
+
<span class="topic-empty">No topics yet — agents use built-in rotation</span>
|
|
364
|
+
</div>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
|
|
292
368
|
<div class="progress-strip" id="progressStrip">
|
|
293
369
|
<div class="prog-label" id="progLabel">Run #1</div>
|
|
294
370
|
<div class="prog-dots">
|
|
@@ -422,7 +498,7 @@ function reconnect() {
|
|
|
422
498
|
function connect() {
|
|
423
499
|
setConn(false);
|
|
424
500
|
sse = new EventSource(serverUrl + '/stream');
|
|
425
|
-
sse.onopen = () => { setConn(true); poll(); };
|
|
501
|
+
sse.onopen = () => { setConn(true); poll(); loadTopics(); };
|
|
426
502
|
sse.onerror = () => {
|
|
427
503
|
setConn(false);
|
|
428
504
|
if (document.getElementById('toggleAR').classList.contains('on')) {
|
|
@@ -533,10 +609,20 @@ function onEvent(d) {
|
|
|
533
609
|
pulseAgent(from, 'learning');
|
|
534
610
|
setTimeout(poll, 600);
|
|
535
611
|
|
|
612
|
+
} else if (type === 'user.task') {
|
|
613
|
+
const task = p.task || p.data?.task || '';
|
|
614
|
+
addFeed('announce', '🎯', `<b>User</b> sent a mission: <i>${esc(task.slice(0,80))}${task.length>80?'…':''}</i>`, 'Agents will pick this up on the next run', { rawType:'USER', fullPrompt: task });
|
|
615
|
+
const status = document.getElementById('missionStatus');
|
|
616
|
+
if (status) { status.textContent = '⏳ waiting for agents…'; status.className = 'mission-status waiting'; }
|
|
617
|
+
|
|
536
618
|
} else if (p.t === 'PROG' || type.includes('progress')) {
|
|
537
619
|
const b = p.body || p;
|
|
538
620
|
updateProgress(b.run ?? 1, b.step ?? 1, b.total ?? 5, b.agent || '', b.action || '');
|
|
539
621
|
|
|
622
|
+
} else if (type === 'topics.updated') {
|
|
623
|
+
topicLibrary = p.topics || [];
|
|
624
|
+
renderTopics();
|
|
625
|
+
|
|
540
626
|
} else if (type.includes('error')) {
|
|
541
627
|
const msg = p.body?.error || p.error || '';
|
|
542
628
|
addFeed('error', '❌', `Error from <b>${p.fr||'?'}</b>`, msg, { rawType:'ERR', from:p.fr, fullText:msg });
|
|
@@ -901,6 +987,110 @@ function toggleMem() {
|
|
|
901
987
|
document.getElementById('memHint').textContent = memExpanded ? '▴ collapse' : '▾ expand';
|
|
902
988
|
}
|
|
903
989
|
|
|
990
|
+
// ─── Mission input ─────────────────────────────────────────
|
|
991
|
+
function submitMission() {
|
|
992
|
+
const input = document.getElementById('missionInput');
|
|
993
|
+
const btn = document.getElementById('missionBtn');
|
|
994
|
+
const status = document.getElementById('missionStatus');
|
|
995
|
+
const task = input.value.trim();
|
|
996
|
+
if (!task) { input.focus(); return; }
|
|
997
|
+
|
|
998
|
+
btn.disabled = true;
|
|
999
|
+
status.textContent = 'sending…';
|
|
1000
|
+
status.className = 'mission-status';
|
|
1001
|
+
|
|
1002
|
+
fetch(serverUrl + '/queue', {
|
|
1003
|
+
method: 'POST',
|
|
1004
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1005
|
+
body: JSON.stringify({ task }),
|
|
1006
|
+
})
|
|
1007
|
+
.then(r => r.json())
|
|
1008
|
+
.then(d => {
|
|
1009
|
+
if (d.ok) {
|
|
1010
|
+
status.textContent = '✓ queued — agents will pick it up next run';
|
|
1011
|
+
status.className = 'mission-status sent';
|
|
1012
|
+
input.value = '';
|
|
1013
|
+
setTimeout(() => {
|
|
1014
|
+
status.textContent = 'agents running autonomously';
|
|
1015
|
+
status.className = 'mission-status';
|
|
1016
|
+
btn.disabled = false;
|
|
1017
|
+
}, 4000);
|
|
1018
|
+
} else {
|
|
1019
|
+
status.textContent = 'error: ' + (d.error || 'unknown');
|
|
1020
|
+
btn.disabled = false;
|
|
1021
|
+
}
|
|
1022
|
+
})
|
|
1023
|
+
.catch(() => {
|
|
1024
|
+
status.textContent = 'could not reach server';
|
|
1025
|
+
status.className = 'mission-status';
|
|
1026
|
+
btn.disabled = false;
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Allow Enter key to submit
|
|
1031
|
+
document.addEventListener('keydown', e => {
|
|
1032
|
+
if (e.key === 'Enter' && document.activeElement.id === 'missionInput') submitMission();
|
|
1033
|
+
if (e.key === 'Enter' && document.activeElement.id === 'topicInput') addTopic();
|
|
1034
|
+
});
|
|
1035
|
+
|
|
1036
|
+
// ─── Topic Library ─────────────────────────────────────────────
|
|
1037
|
+
let topicLibrary = [];
|
|
1038
|
+
|
|
1039
|
+
function renderTopics() {
|
|
1040
|
+
const chips = document.getElementById('topicChips');
|
|
1041
|
+
if (!chips) return;
|
|
1042
|
+
if (topicLibrary.length === 0) {
|
|
1043
|
+
chips.innerHTML = '<span class="topic-empty">No topics yet — agents use built-in rotation</span>';
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
chips.innerHTML = topicLibrary.map((t, i) =>
|
|
1047
|
+
`<span class="topic-chip">
|
|
1048
|
+
<span class="topic-chip-text" title="${esc(t)}">${esc(t)}</span>
|
|
1049
|
+
<span class="topic-chip-x" onclick="removeTopic(${i})" title="Remove">×</span>
|
|
1050
|
+
</span>`
|
|
1051
|
+
).join('');
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function addTopic() {
|
|
1055
|
+
const input = document.getElementById('topicInput');
|
|
1056
|
+
const topic = (input.value || '').trim();
|
|
1057
|
+
if (!topic) { input.focus(); return; }
|
|
1058
|
+
fetch(serverUrl + '/topics', {
|
|
1059
|
+
method: 'POST',
|
|
1060
|
+
headers: {'Content-Type':'application/json'},
|
|
1061
|
+
body: JSON.stringify({topic})
|
|
1062
|
+
})
|
|
1063
|
+
.then(r => r.json())
|
|
1064
|
+
.then(d => {
|
|
1065
|
+
if (d.ok) {
|
|
1066
|
+
topicLibrary = d.topics;
|
|
1067
|
+
input.value = '';
|
|
1068
|
+
renderTopics();
|
|
1069
|
+
addFeed('announce', '📚', `<b>Topic added:</b> ${esc(topic.slice(0,80))}`, 'Agents will explore this on the next run', {rawType:'TOPIC'});
|
|
1070
|
+
}
|
|
1071
|
+
})
|
|
1072
|
+
.catch(() => {});
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
function removeTopic(idx) {
|
|
1076
|
+
fetch(serverUrl + `/topics/${idx}`, { method: 'DELETE' })
|
|
1077
|
+
.then(r => r.json())
|
|
1078
|
+
.then(d => {
|
|
1079
|
+
if (d.ok !== false) {
|
|
1080
|
+
topicLibrary = d.topics;
|
|
1081
|
+
renderTopics();
|
|
1082
|
+
}
|
|
1083
|
+
})
|
|
1084
|
+
.catch(() => {});
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function loadTopics() {
|
|
1088
|
+
fetch(serverUrl + '/topics')
|
|
1089
|
+
.then(r => r.json())
|
|
1090
|
+
.then(d => { topicLibrary = d.topics || []; renderTopics(); })
|
|
1091
|
+
.catch(() => {});
|
|
1092
|
+
}
|
|
1093
|
+
|
|
904
1094
|
// ─── Settings ──────────────────────────────────────────────────
|
|
905
1095
|
function openSettings() {
|
|
906
1096
|
document.getElementById('settingsUrl').value = serverUrl;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|