arkaos 3.66.0 → 3.67.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/VERSION +1 -1
- package/core/__pycache__/favorites.cpython-313.pyc +0 -0
- package/core/terminal/__init__.py +27 -0
- package/core/terminal/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/terminal/__pycache__/audit.cpython-313.pyc +0 -0
- package/core/terminal/__pycache__/session.cpython-313.pyc +0 -0
- package/core/terminal/__pycache__/token.cpython-313.pyc +0 -0
- package/core/terminal/audit.py +58 -0
- package/core/terminal/session.py +288 -0
- package/core/terminal/token.py +38 -0
- package/dashboard/app/pages/agents/index.vue +6 -6
- package/dashboard/app/pages/personas.vue +719 -0
- package/departments/brand/agents/brand-director.yaml +40 -38
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/dashboard-api.py +188 -0
|
@@ -4,18 +4,19 @@ role: Creative Director
|
|
|
4
4
|
department: brand
|
|
5
5
|
tier: 1
|
|
6
6
|
model: sonnet
|
|
7
|
+
|
|
7
8
|
behavioral_dna:
|
|
8
9
|
disc:
|
|
9
10
|
primary: S
|
|
10
11
|
secondary: I
|
|
11
|
-
communication_style: Thoughtful, visual, builds consensus before executing
|
|
12
|
-
under_pressure: Protects creative quality, refuses to rush aesthetics
|
|
13
|
-
motivator: Beautiful, meaningful brands that resonate emotionally
|
|
12
|
+
communication_style: "Thoughtful, visual, builds consensus before executing"
|
|
13
|
+
under_pressure: "Protects creative quality, refuses to rush aesthetics"
|
|
14
|
+
motivator: "Beautiful, meaningful brands that resonate emotionally"
|
|
14
15
|
enneagram:
|
|
15
16
|
type: 4
|
|
16
17
|
wing: 3
|
|
17
|
-
core_motivation: Creating unique, authentic brand identities
|
|
18
|
-
core_fear: Producing generic, forgettable brand work
|
|
18
|
+
core_motivation: "Creating unique, authentic brand identities"
|
|
19
|
+
core_fear: "Producing generic, forgettable brand work"
|
|
19
20
|
subtype: social
|
|
20
21
|
big_five:
|
|
21
22
|
openness: 92
|
|
@@ -25,52 +26,53 @@ behavioral_dna:
|
|
|
25
26
|
neuroticism: 35
|
|
26
27
|
mbti:
|
|
27
28
|
type: INFP
|
|
29
|
+
|
|
28
30
|
mental_models:
|
|
29
31
|
primary:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
- "Primal Branding 7 Elements (Hanlon)"
|
|
33
|
+
- "Brand Identity Process (Wheeler)"
|
|
34
|
+
- "12 Archetypes (Jung)"
|
|
33
35
|
secondary:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
- "StoryBrand SB7 (Miller)"
|
|
37
|
+
- "Positioning (Ries/Trout)"
|
|
38
|
+
- "Design Thinking (IDEO)"
|
|
39
|
+
|
|
37
40
|
authority:
|
|
38
41
|
orchestrate: true
|
|
39
42
|
approve_quality: true
|
|
40
43
|
delegates_to:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
- visual-designer
|
|
45
|
+
- ux-designer
|
|
46
|
+
- brand-copywriter
|
|
47
|
+
- brand-strategist
|
|
45
48
|
escalates_to: coo-sofia
|
|
49
|
+
|
|
46
50
|
expertise:
|
|
47
51
|
domains:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
- brand identity creation
|
|
53
|
+
- visual design direction
|
|
54
|
+
- UX/UI strategy
|
|
55
|
+
- design systems
|
|
56
|
+
- brand voice & tone
|
|
57
|
+
- creative direction
|
|
54
58
|
frameworks:
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
- Primal Branding (Hanlon)
|
|
60
|
+
- StoryBrand (Miller)
|
|
61
|
+
- Brand Archetypes (Jung)
|
|
62
|
+
- Wheeler Process
|
|
63
|
+
- Atomic Design (Frost)
|
|
64
|
+
- Nielsen Heuristics
|
|
65
|
+
- Dieter Rams 10 Principles
|
|
66
|
+
- Double Diamond
|
|
63
67
|
depth: master
|
|
64
|
-
years_equivalent:
|
|
68
|
+
years_equivalent: 14
|
|
69
|
+
|
|
65
70
|
communication:
|
|
66
71
|
language: en
|
|
67
|
-
tone: warm, visual, metaphor-rich
|
|
72
|
+
tone: "warm, visual, metaphor-rich"
|
|
68
73
|
vocabulary_level: advanced
|
|
69
|
-
preferred_format: mood boards, visual references, brand decks
|
|
74
|
+
preferred_format: "mood boards, visual references, brand decks"
|
|
70
75
|
avoid:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
frameworks: []
|
|
75
|
-
expertise_domains: []
|
|
76
|
-
linked_personas: []
|
|
76
|
+
- "design by committee"
|
|
77
|
+
- "trendy without substance"
|
|
78
|
+
- "skipping strategy for visuals"
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
Binary file
|
package/scripts/dashboard-api.py
CHANGED
|
@@ -2467,6 +2467,194 @@ def workflow_update_yaml(workflow_id: str, body: dict):
|
|
|
2467
2467
|
return {"updated": True, "id": workflow_id, "file": str(target)}
|
|
2468
2468
|
|
|
2469
2469
|
|
|
2470
|
+
# ============================================================================
|
|
2471
|
+
# PR99a v3.67.0 — Terminal PTY WebSocket + REST.
|
|
2472
|
+
#
|
|
2473
|
+
# Replaces the v3.51.0 allowlist runner. Spawns a real PTY per session
|
|
2474
|
+
# (`pty.fork` + user's $SHELL), streams bidirectionally over a WS to
|
|
2475
|
+
# xterm.js, and exposes thin REST endpoints to list / create / close
|
|
2476
|
+
# sessions. Origin pinning + bearer token in handshake. Idle-kill is
|
|
2477
|
+
# driven by a 60s background coroutine.
|
|
2478
|
+
# ============================================================================
|
|
2479
|
+
|
|
2480
|
+
|
|
2481
|
+
import re as _terminal_re
|
|
2482
|
+
|
|
2483
|
+
|
|
2484
|
+
def _terminal_origin_ok(origin: str) -> bool:
|
|
2485
|
+
"""Allow only http(s)://localhost or 127.0.0.1 (any port)."""
|
|
2486
|
+
if not origin:
|
|
2487
|
+
return False
|
|
2488
|
+
return bool(
|
|
2489
|
+
_terminal_re.match(r"^https?://(localhost|127\.0\.0\.1)(:\d+)?$", origin)
|
|
2490
|
+
)
|
|
2491
|
+
|
|
2492
|
+
|
|
2493
|
+
@app.get("/api/terminal/token")
|
|
2494
|
+
def terminal_token_endpoint():
|
|
2495
|
+
"""Return the per-process bearer token used in WS handshakes.
|
|
2496
|
+
|
|
2497
|
+
The token rotates whenever the API restarts. CORS already pins
|
|
2498
|
+
origins to localhost; this is the second factor that protects
|
|
2499
|
+
against a malicious page on another localhost port opening the WS
|
|
2500
|
+
without a CORS preflight.
|
|
2501
|
+
"""
|
|
2502
|
+
from core.terminal import token as _token_mod
|
|
2503
|
+
return {"token": _token_mod.current_token()}
|
|
2504
|
+
|
|
2505
|
+
|
|
2506
|
+
@app.get("/api/terminal/sessions")
|
|
2507
|
+
def terminal_sessions_list():
|
|
2508
|
+
from core.terminal.session import default_manager
|
|
2509
|
+
mgr = default_manager()
|
|
2510
|
+
return {
|
|
2511
|
+
"sessions": mgr.list_all(),
|
|
2512
|
+
"max_sessions": mgr.max_sessions,
|
|
2513
|
+
"idle_timeout_seconds": mgr.idle_timeout_s,
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
|
|
2517
|
+
@app.post("/api/terminal/sessions")
|
|
2518
|
+
def terminal_sessions_create(body: dict):
|
|
2519
|
+
from core.terminal.session import default_manager, SessionCapacityError
|
|
2520
|
+
from core.terminal import token as _token_mod
|
|
2521
|
+
body = body if isinstance(body, dict) else {}
|
|
2522
|
+
cwd = body.get("cwd")
|
|
2523
|
+
shell = body.get("shell")
|
|
2524
|
+
cols = int(body.get("cols") or 120)
|
|
2525
|
+
rows = int(body.get("rows") or 32)
|
|
2526
|
+
mgr = default_manager()
|
|
2527
|
+
try:
|
|
2528
|
+
s = mgr.create(shell=shell, cwd=cwd, cols=cols, rows=rows)
|
|
2529
|
+
except SessionCapacityError as exc:
|
|
2530
|
+
from fastapi import HTTPException
|
|
2531
|
+
raise HTTPException(status_code=429, detail=str(exc))
|
|
2532
|
+
return {
|
|
2533
|
+
"session_id": s.session_id,
|
|
2534
|
+
"shell": s.shell,
|
|
2535
|
+
"cwd": s.cwd,
|
|
2536
|
+
"token": _token_mod.current_token(),
|
|
2537
|
+
"ws_path": f"/ws/terminal/{s.session_id}",
|
|
2538
|
+
"max_sessions": mgr.max_sessions,
|
|
2539
|
+
"active_count": mgr.count(),
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
|
|
2543
|
+
@app.delete("/api/terminal/sessions/{session_id}")
|
|
2544
|
+
def terminal_sessions_delete(session_id: str):
|
|
2545
|
+
from core.terminal.session import default_manager
|
|
2546
|
+
ok = default_manager().close(session_id, reason="manual-close")
|
|
2547
|
+
return {"closed": ok, "session_id": session_id}
|
|
2548
|
+
|
|
2549
|
+
|
|
2550
|
+
@app.websocket("/ws/terminal/{session_id}")
|
|
2551
|
+
async def ws_terminal(ws: WebSocket, session_id: str, token: str = Query("")):
|
|
2552
|
+
"""Bidirectional PTY pump.
|
|
2553
|
+
|
|
2554
|
+
Client → server: either text frames `{"type": "resize", "cols", "rows"}`
|
|
2555
|
+
/ `{"type": "input", "data": "..."}` or raw binary frames (input bytes).
|
|
2556
|
+
Server → client: raw binary frames of PTY output.
|
|
2557
|
+
|
|
2558
|
+
Closes with code 4401 on bad token, 4403 on bad origin, 4404 when the
|
|
2559
|
+
session is not found.
|
|
2560
|
+
"""
|
|
2561
|
+
origin = ws.headers.get("origin", "")
|
|
2562
|
+
if not _terminal_origin_ok(origin):
|
|
2563
|
+
await ws.close(code=4403, reason="origin not allowed")
|
|
2564
|
+
return
|
|
2565
|
+
from core.terminal import token as _token_mod
|
|
2566
|
+
if not _token_mod.verify(token):
|
|
2567
|
+
await ws.close(code=4401, reason="bad token")
|
|
2568
|
+
return
|
|
2569
|
+
from core.terminal.session import default_manager
|
|
2570
|
+
session = default_manager().get(session_id)
|
|
2571
|
+
if session is None:
|
|
2572
|
+
await ws.close(code=4404, reason="session not found")
|
|
2573
|
+
return
|
|
2574
|
+
|
|
2575
|
+
await ws.accept()
|
|
2576
|
+
loop = asyncio.get_event_loop()
|
|
2577
|
+
output_queue: asyncio.Queue = asyncio.Queue()
|
|
2578
|
+
|
|
2579
|
+
def _on_readable():
|
|
2580
|
+
try:
|
|
2581
|
+
data = session.read(8192)
|
|
2582
|
+
except OSError:
|
|
2583
|
+
data = b""
|
|
2584
|
+
if data:
|
|
2585
|
+
output_queue.put_nowait(data)
|
|
2586
|
+
|
|
2587
|
+
try:
|
|
2588
|
+
loop.add_reader(session.master_fd, _on_readable)
|
|
2589
|
+
except (ValueError, OSError):
|
|
2590
|
+
await ws.close(code=1011, reason="pty unavailable")
|
|
2591
|
+
return
|
|
2592
|
+
|
|
2593
|
+
async def pump_to_client():
|
|
2594
|
+
while True:
|
|
2595
|
+
data = await output_queue.get()
|
|
2596
|
+
try:
|
|
2597
|
+
await ws.send_bytes(data)
|
|
2598
|
+
except Exception:
|
|
2599
|
+
break
|
|
2600
|
+
|
|
2601
|
+
pump_task = asyncio.create_task(pump_to_client())
|
|
2602
|
+
|
|
2603
|
+
try:
|
|
2604
|
+
while True:
|
|
2605
|
+
msg = await ws.receive()
|
|
2606
|
+
mtype = msg.get("type")
|
|
2607
|
+
if mtype == "websocket.disconnect":
|
|
2608
|
+
break
|
|
2609
|
+
text = msg.get("text")
|
|
2610
|
+
data = msg.get("bytes")
|
|
2611
|
+
if text is not None:
|
|
2612
|
+
try:
|
|
2613
|
+
payload = json.loads(text)
|
|
2614
|
+
except json.JSONDecodeError:
|
|
2615
|
+
continue
|
|
2616
|
+
if not isinstance(payload, dict):
|
|
2617
|
+
continue
|
|
2618
|
+
kind = payload.get("type")
|
|
2619
|
+
if kind == "resize":
|
|
2620
|
+
session.resize(
|
|
2621
|
+
int(payload.get("cols") or 80),
|
|
2622
|
+
int(payload.get("rows") or 24),
|
|
2623
|
+
)
|
|
2624
|
+
elif kind == "input":
|
|
2625
|
+
session.write(str(payload.get("data") or "").encode("utf-8"))
|
|
2626
|
+
elif data is not None:
|
|
2627
|
+
session.write(data)
|
|
2628
|
+
except WebSocketDisconnect:
|
|
2629
|
+
pass
|
|
2630
|
+
except Exception:
|
|
2631
|
+
pass
|
|
2632
|
+
finally:
|
|
2633
|
+
pump_task.cancel()
|
|
2634
|
+
try:
|
|
2635
|
+
loop.remove_reader(session.master_fd)
|
|
2636
|
+
except (ValueError, OSError):
|
|
2637
|
+
pass
|
|
2638
|
+
|
|
2639
|
+
|
|
2640
|
+
@app.on_event("startup")
|
|
2641
|
+
async def _terminal_reaper_startup():
|
|
2642
|
+
"""Background loop that reaps dead and idle sessions every 60s."""
|
|
2643
|
+
async def _loop():
|
|
2644
|
+
from core.terminal.session import default_manager
|
|
2645
|
+
while True:
|
|
2646
|
+
try:
|
|
2647
|
+
await asyncio.sleep(60)
|
|
2648
|
+
mgr = default_manager()
|
|
2649
|
+
mgr.reap_dead()
|
|
2650
|
+
mgr.reap_idle()
|
|
2651
|
+
except asyncio.CancelledError:
|
|
2652
|
+
break
|
|
2653
|
+
except Exception:
|
|
2654
|
+
continue
|
|
2655
|
+
asyncio.create_task(_loop())
|
|
2656
|
+
|
|
2657
|
+
|
|
2470
2658
|
def _resolve_workflow_yaml(workflow_id: str):
|
|
2471
2659
|
"""Return the YAML path for a workflow id, or None when missing."""
|
|
2472
2660
|
try:
|