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.
@@ -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
- - Primal Branding 7 Elements (Hanlon)
31
- - Brand Identity Process (Wheeler)
32
- - 12 Archetypes (Jung)
32
+ - "Primal Branding 7 Elements (Hanlon)"
33
+ - "Brand Identity Process (Wheeler)"
34
+ - "12 Archetypes (Jung)"
33
35
  secondary:
34
- - StoryBrand SB7 (Miller)
35
- - Positioning (Ries/Trout)
36
- - Design Thinking (IDEO)
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
- - visual-designer
42
- - ux-designer
43
- - brand-copywriter
44
- - brand-strategist
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
- - brand identity creation
49
- - visual design direction
50
- - UX/UI strategy
51
- - design systems
52
- - brand voice & tone
53
- - creative direction
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
- - Primal Branding (Hanlon)
56
- - StoryBrand (Miller)
57
- - Brand Archetypes (Jung)
58
- - Wheeler Process
59
- - Atomic Design (Frost)
60
- - Nielsen Heuristics
61
- - Dieter Rams 10 Principles
62
- - Double Diamond
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: 24
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
- - design by committee
72
- - trendy without substance
73
- - skipping strategy for visuals
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "3.66.0",
3
+ "version": "3.67.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "3.66.0"
3
+ version = "3.67.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -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: