codedeck 0.4.0 → 0.4.1

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.
@@ -50,7 +50,7 @@ export const CANVAS_PAGE = `<!doctype html>
50
50
  #feed li.fresh { color: #f5f5f7; }
51
51
  @keyframes feedIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; } }
52
52
  #hint { left: 50%; transform: translateX(-50%); bottom: 16px; font-size: 12px; color: #98989f; white-space: nowrap; }
53
- #detail { position: fixed; top: 0; right: 0; bottom: 0; width: min(360px, 92vw); z-index: 20;
53
+ #detail { position: fixed; top: 0; right: 0; bottom: 0; width: min(400px, 94vw); z-index: 20;
54
54
  background: rgba(10, 12, 16, 0.92); border-left: 1px solid rgba(255,255,255,0.1);
55
55
  backdrop-filter: blur(14px); padding: 18px; overflow-y: auto; display: none; }
56
56
  #detail.open { display: block; }
@@ -69,6 +69,28 @@ export const CANVAS_PAGE = `<!doctype html>
69
69
  border: 1px solid rgba(255,255,255,0.12); background: transparent; color: #98989f;
70
70
  cursor: pointer; font-size: 14px; line-height: 1; font-family: inherit; }
71
71
  #detailClose:hover { color: #f5f5f7; border-color: rgba(255,255,255,0.25); }
72
+ #dChat { border: 1px solid rgba(255,255,255,0.08); border-radius: 10px; padding: 10px; max-height: 52vh; min-height: 140px; overflow-y: auto; display: flex; flex-direction: column; gap: 9px; }
73
+ /* Scroll fino escuro no painel e no chat (Firefox + WebKit): o padrao
74
+ claro do SO quebra o tema escuro da pagina. */
75
+ #detail, #dChat { scrollbar-width: thin; scrollbar-color: rgba(255,255,255,0.24) transparent; }
76
+ #detail::-webkit-scrollbar, #dChat::-webkit-scrollbar { width: 10px; }
77
+ #detail::-webkit-scrollbar-track, #dChat::-webkit-scrollbar-track { background: transparent; }
78
+ #detail::-webkit-scrollbar-thumb, #dChat::-webkit-scrollbar-thumb { background-color: rgba(255,255,255,0.18); border-radius: 8px; border: 3px solid transparent; background-clip: content-box; }
79
+ #detail::-webkit-scrollbar-thumb:hover, #dChat::-webkit-scrollbar-thumb:hover { background-color: rgba(255,255,255,0.32); }
80
+ #dChat .msg { font-size: 12.5px; line-height: 1.45; white-space: pre-wrap; word-break: break-word; padding: 7px 9px; border-radius: 9px; }
81
+ #dChat .msg .who { display: block; font-size: 10.5px; letter-spacing: 0.04em; color: #98989f; margin-bottom: 2px; }
82
+ #dChat .msg.user { background: rgba(10,132,255,0.16); }
83
+ #dChat .msg.assistant { background: rgba(255,255,255,0.05); }
84
+ #dChat .evt { font-size: 11.5px; color: #636366; }
85
+ #dChat .empty { color: #636366; font-size: 12px; }
86
+ #dSend { display: flex; gap: 8px; margin-top: 10px; }
87
+ #dSendText { flex: 1; min-width: 0; background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.12); border-radius: 10px; color: #f5f5f7; padding: 8px 10px; font-size: 12.5px; font-family: inherit; }
88
+ #dSendText:focus { outline: none; border-color: rgba(255,255,255,0.3); }
89
+ #dSendText:disabled { opacity: 0.4; }
90
+ #dSendBtn, #dArchive { border: 1px solid rgba(255,255,255,0.14); background: rgba(10,132,255,0.25); color: #f5f5f7; border-radius: 10px; padding: 8px 12px; cursor: pointer; font-family: inherit; font-size: 12.5px; }
91
+ #dSendBtn:disabled, #dArchive:disabled { opacity: 0.4; cursor: default; }
92
+ #dArchive { width: 100%; margin-top: 8px; }
93
+ #dSendMsg { font-size: 11.5px; color: #98989f; margin: 6px 0 0; min-height: 15px; }
72
94
  .node { position: absolute; width: 196px; background: #13161c; border: 1px solid rgba(255,255,255,.09);
73
95
  border-radius: 14px; padding: 11px 13px; pointer-events: auto; cursor: grab; user-select: none;
74
96
  box-shadow: 0 8px 28px rgba(0,0,0,.45); }
@@ -105,8 +127,14 @@ export const CANVAS_PAGE = `<!doctype html>
105
127
  <h2 id="dTitle"></h2>
106
128
  <p class="dsub" id="dSub"></p>
107
129
  <dl id="dRows"></dl>
108
- <h3>HISTÓRICO</h3>
109
- <ul id="dHist"></ul>
130
+ <h3>CONVERSA</h3>
131
+ <div id="dChat"></div>
132
+ <form id="dSend">
133
+ <input id="dSendText" type="text" autocomplete="off" placeholder="mensagem para esta sessão">
134
+ <button id="dSendBtn" type="submit">Enviar</button>
135
+ </form>
136
+ <p id="dSendMsg" role="status"></p>
137
+ <button id="dArchive" type="button" style="display:none">Marcar como concluída</button>
110
138
  </aside>
111
139
  <script>
112
140
  "use strict";
@@ -410,6 +438,11 @@ function reconcile(sessions) {
410
438
  if (Date.now() > n.liveUntil) n.activity = baseActivity(s);
411
439
  paintNode(n);
412
440
  seen[s.id] = true;
441
+ if (detailId === s.id) {
442
+ detailStatus = s.status;
443
+ detailOrigin = s.origin || null;
444
+ paintSendState();
445
+ }
413
446
  });
414
447
  for (var id in nodesById) {
415
448
  if (!seen[id]) {
@@ -538,6 +571,12 @@ function onEvent(n, ev) {
538
571
  touch(n, "respondendo");
539
572
  feed(agentName(n.agent), String(ev.content || "").slice(0, 90));
540
573
  }
574
+ } else if (ev.type === "message.queued") {
575
+ touch(n, "mensagem na fila");
576
+ feed(agentName(n.agent), "mensagem na fila");
577
+ if (n.id && n.id === detailId) { detailPending = ev.prompt || true; paintSendState(); }
578
+ } else if (ev.type === "turn.started") {
579
+ if (n.id && n.id === detailId && detailPending) { detailPending = null; paintSendState(); }
541
580
  } else if (ev.type === "turn.completed") {
542
581
  feed(agentName(n.agent), "etapa concluída");
543
582
  } else if (ev.type === "file.changed") {
@@ -553,6 +592,9 @@ function onEvent(n, ev) {
553
592
  touch(n, ev.type === "session.failed" ? ("falhou: " + (ev.error || "")) : "concluída");
554
593
  paintSummary();
555
594
  feed(agentName(n.agent), ev.type === "session.failed" ? "falhou" : "concluída");
595
+ // Stop/release cancels the queue server-side: drop a stale "na fila"
596
+ // hint on the open detail, mirroring the turn.started branch.
597
+ if (n.id && n.id === detailId && detailPending) { detailPending = null; paintSendState(); }
556
598
  if (n.es) { try { n.es.close(); } catch (e) {} n.es = null; }
557
599
  }
558
600
  }
@@ -754,6 +796,10 @@ window.addEventListener("keydown", function (ev) {
754
796
  if (ev.key === "Escape") closeDetail();
755
797
  });
756
798
  window.addEventListener("wheel", function (ev) {
799
+ // O painel de detalhe tem scroll proprio (#detail e #dChat): o zoom do
800
+ // canvas so vale fora dele, senao rolar o chat daria zoom na cena.
801
+ var t = ev.target;
802
+ if (t && typeof t.closest === "function" && t.closest("#detail")) return;
757
803
  ev.preventDefault();
758
804
  var before = toWorld(ev.clientX, ev.clientY);
759
805
  cam.zoom = Math.min(2.2, Math.max(0.45, cam.zoom * (ev.deltaY < 0 ? 1.1 : 0.9)));
@@ -764,17 +810,20 @@ window.addEventListener("wheel", function (ev) {
764
810
  function syncUrl() {
765
811
  try {
766
812
  var qp = new URLSearchParams(window.location.search);
767
- if (hideDone) qp.set("hide", "1"); else qp.delete("hide");
813
+ // Padrão é esconder concluídas: a URL limpa É o estado escondido, e
814
+ // quem quiser ver tudo marca hide=0 explicitamente.
815
+ if (hideDone) qp.delete("hide"); else qp.set("hide", "0");
768
816
  if (!motion) qp.set("motion", "0"); else qp.delete("motion");
769
817
  var qs = qp.toString();
770
818
  window.history.replaceState(null, "", window.location.pathname + (qs ? "?" + qs : ""));
771
819
  } catch (e) {}
772
820
  }
773
- // Filtros sobrevivem ao F5: o estado mora na query string (?hide=1&motion=0).
774
- var urlHide = false, urlMotion = true;
821
+ // Filtros sobrevivem ao F5: o estado mora na query string (?hide=0&motion=0).
822
+ // hideDone nasce true: concluídas ficam fora até alguém pedir hide=0.
823
+ var urlHide = true, urlMotion = true;
775
824
  try {
776
825
  var qp0 = new URLSearchParams(window.location.search);
777
- urlHide = qp0.get("hide") === "1";
826
+ urlHide = qp0.get("hide") !== "0";
778
827
  urlMotion = qp0.get("motion") !== "0";
779
828
  } catch (e) {}
780
829
  motion = urlMotion;
@@ -808,31 +857,44 @@ document.getElementById("btnMotion").classList.toggle("on", motion);
808
857
  document.getElementById("btnHide").classList.toggle("on", hideDone);
809
858
  document.getElementById("btnHide").title = hideDone ? "Mostrar concluídas" : "Ocultar concluídas";
810
859
 
811
- function fmtTime(iso) {
812
- try {
813
- var d = new Date(iso);
814
- return d.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit", second: "2-digit" });
815
- } catch (e) { return ""; }
816
- }
817
- function fmtEvent(ev) {
818
- if (!ev || !ev.type) return "";
819
- if (ev.type === "text.delta") return null;
820
- if (ev.type === "tool.started") return "Começou: " + toolLabel(ev.tool);
821
- if (ev.type === "tool.completed") {
822
- var bad = ev.tool && ev.tool.success === false;
823
- return "Terminou: " + toolLabel(ev.tool) + (bad ? " (com erro)" : "");
824
- }
825
- if (ev.type === "message" && ev.role === "assistant") return "Respondeu: " + String(ev.content || "").slice(0, 120);
826
- if (ev.type === "message") return null;
827
- if (ev.type === "turn.started") return "Nova etapa";
828
- if (ev.type === "turn.completed") return "Etapa concluída";
829
- if (ev.type === "file.changed") return "Mudou " + (ev.path || "um arquivo");
830
- if (ev.type === "permission.requested") return "Pediu aprovação: " + (ev.tool || "uma ação");
831
- if (ev.type === "permission.resolved") return ev.approved ? "Aprovado" : "Negado";
832
- if (ev.type === "session.completed") return "Sessão concluída";
833
- if (ev.type === "session.failed") return "Sessão falhou: " + (ev.error || "");
834
- if (ev.type === "session.started") return "Sessão iniciada";
835
- return null;
860
+ /* Transcrição da sessão: a mensagem do usuário mora em turn.started.prompt
861
+ (o daemon anexa no turno inicial e a cada send), a resposta em
862
+ message(role=assistant) com content completo. Ferramentas viram linhas
863
+ compactas para a conversa continuar legível. */
864
+ var detailId = null, detailStatus = null, detailOrigin = null, detailPending = null;
865
+ var sendInFlight = false, sendBlockedNote = false;
866
+ function chatMsg(who, text, cls) {
867
+ var d = document.createElement("div");
868
+ d.className = "msg " + cls;
869
+ var w = document.createElement("span");
870
+ w.className = "who";
871
+ w.textContent = who;
872
+ d.appendChild(w);
873
+ d.appendChild(document.createTextNode(text));
874
+ return d;
875
+ }
876
+ function chatEvt(text) {
877
+ var d = document.createElement("div");
878
+ d.className = "evt";
879
+ d.textContent = text;
880
+ return d;
881
+ }
882
+ /* Input e botão seguem o estado do daemon: ocupada trava, origin=open
883
+ (TUI interativa) trava sempre. Mensagem de sucesso não é apagada aqui. */
884
+ function paintSendState() {
885
+ var input = document.getElementById("dSendText");
886
+ var btn = document.getElementById("dSendBtn");
887
+ var msg = document.getElementById("dSendMsg");
888
+ if (!detailId) return;
889
+ // origin=open (TUI interativa) trava sempre; working/starting entra na
890
+ // fila em vez de travar. Mensagem de sucesso não é apagada aqui.
891
+ var reason = "";
892
+ if (detailOrigin === "open") reason = "sessão interativa não aceita mensagens";
893
+ input.disabled = sendInFlight || reason !== "";
894
+ btn.disabled = sendInFlight || reason !== "";
895
+ if (reason) { msg.textContent = reason; sendBlockedNote = true; }
896
+ else if (detailPending) { msg.textContent = "1 mensagem na fila — envia quando o turno terminar"; sendBlockedNote = true; }
897
+ else if (sendBlockedNote) { msg.textContent = ""; sendBlockedNote = false; }
836
898
  }
837
899
  function row(dt, dd) {
838
900
  var d = document.createElement("dt");
@@ -842,13 +904,25 @@ function row(dt, dd) {
842
904
  return [d, v];
843
905
  }
844
906
  function closeDetail() {
907
+ detailId = null;
908
+ detailStatus = null;
909
+ detailOrigin = null;
910
+ detailPending = null;
845
911
  document.getElementById("detail").classList.remove("open");
846
912
  }
847
913
  document.getElementById("detailClose").onclick = closeDetail;
848
914
  function selectSession(id) {
849
- fetch("api/sessions/" + encodeURIComponent(id)).then(function (r) { return r.json(); }).then(function (j) {
915
+ // /logs traz até 1000 eventos; /get limitaria a transcrição aos 10 últimos.
916
+ fetch("api/sessions/" + encodeURIComponent(id) + "/logs").then(function (r) { return r.json(); }).then(function (j) {
850
917
  var s = j.session || j;
851
918
  if (!s || !s.id) return;
919
+ detailId = s.id;
920
+ detailStatus = s.status;
921
+ detailOrigin = s.origin || null;
922
+ detailPending = s.pendingMessage || null;
923
+ // Interrupted rows are actionable again: offer archival. Every other
924
+ // status keeps the button hidden.
925
+ document.getElementById("dArchive").style.display = s.status === "interrupted" ? "" : "none";
852
926
  var title = document.getElementById("dTitle");
853
927
  title.innerHTML = "";
854
928
  var logo = document.createElement("span");
@@ -870,8 +944,9 @@ function selectSession(id) {
870
944
  ];
871
945
  if (s.pid) items.push(["PID", String(s.pid)]);
872
946
  if (s.updatedAt) items.push(["Atualizado", timeAgo(s.updatedAt)]);
947
+ var events = j.events || [];
873
948
  var tools = 0, msgs = 0;
874
- (j.events || []).forEach(function (ev) {
949
+ events.forEach(function (ev) {
875
950
  if (!ev) return;
876
951
  if (ev.type === "tool.completed") tools++;
877
952
  if (ev.type === "message" && ev.role === "assistant") msgs++;
@@ -881,29 +956,102 @@ function selectSession(id) {
881
956
  items.forEach(function (kv) {
882
957
  row(kv[0], kv[1]).forEach(function (el) { rows.appendChild(el); });
883
958
  });
884
- var hist = document.getElementById("dHist");
885
- hist.innerHTML = "";
886
- var shown = 0;
887
- for (var i = (j.events || []).length - 1; i >= 0 && shown < 30; i--) {
888
- var line = fmtEvent(j.events[i]);
889
- if (!line) continue;
890
- var li = document.createElement("li");
891
- var ts = document.createElement("span");
892
- ts.className = "ts";
893
- ts.textContent = fmtTime(j.events[i].timestamp) + " ";
894
- li.appendChild(ts);
895
- li.appendChild(document.createTextNode(line));
896
- hist.appendChild(li);
897
- shown++;
959
+ var chat = document.getElementById("dChat");
960
+ chat.innerHTML = "";
961
+ // Últimos 200 eventos bastam: render além disso só pesa o DOM.
962
+ var start = Math.max(0, events.length - 200);
963
+ for (var i = start; i < events.length; i++) {
964
+ var ev = events[i];
965
+ if (!ev || !ev.type) continue;
966
+ if (ev.type === "turn.started" && ev.prompt) {
967
+ chat.appendChild(chatMsg("você", String(ev.prompt), "user"));
968
+ } else if (ev.type === "message.queued" && ev.prompt) {
969
+ chat.appendChild(chatMsg("você", String(ev.prompt) + " (na fila)", "user"));
970
+ } else if (ev.type === "message" && ev.role === "assistant" && ev.content) {
971
+ chat.appendChild(chatMsg(agentName(s.agent), String(ev.content), "assistant"));
972
+ } else if (ev.type === "tool.started") {
973
+ chat.appendChild(chatEvt("começou: " + toolLabel(ev.tool)));
974
+ } else if (ev.type === "tool.completed") {
975
+ var bad = ev.tool && ev.tool.success === false;
976
+ chat.appendChild(chatEvt("terminou: " + toolLabel(ev.tool) + (bad ? " (com erro)" : "")));
977
+ } else if (ev.type === "permission.requested") {
978
+ chat.appendChild(chatEvt("pediu aprovação: " + (ev.tool || "uma ação")));
979
+ } else if (ev.type === "file.changed") {
980
+ chat.appendChild(chatEvt("mudou " + (ev.path || "um arquivo")));
981
+ }
898
982
  }
899
- if (!shown) {
900
- var empty = document.createElement("li");
901
- empty.textContent = "Sem histórico ainda.";
902
- hist.appendChild(empty);
983
+ if (!chat.children.length) {
984
+ var empty = document.createElement("div");
985
+ empty.className = "empty";
986
+ empty.textContent = "Sem mensagens ainda.";
987
+ chat.appendChild(empty);
903
988
  }
989
+ chat.scrollTop = chat.scrollHeight;
904
990
  document.getElementById("detail").classList.add("open");
991
+ paintSendState();
905
992
  }).catch(function () {});
906
993
  }
994
+ document.getElementById("dSend").addEventListener("submit", function (ev) {
995
+ ev.preventDefault();
996
+ if (!detailId || sendInFlight) return;
997
+ var input = document.getElementById("dSendText");
998
+ var text = input.value.trim();
999
+ if (!text) return;
1000
+ var msg = document.getElementById("dSendMsg");
1001
+ var btn = document.getElementById("dSendBtn");
1002
+ sendInFlight = true;
1003
+ input.disabled = true;
1004
+ btn.disabled = true;
1005
+ msg.textContent = "enviando...";
1006
+ fetch("api/sessions/" + encodeURIComponent(detailId) + "/send", {
1007
+ method: "POST",
1008
+ headers: { "content-type": "application/json" },
1009
+ body: JSON.stringify({ message: text })
1010
+ }).then(function (r) {
1011
+ return r.json().then(function (j) { return { ok: r.ok, body: j }; });
1012
+ }).then(function (out) {
1013
+ if (out.ok) {
1014
+ input.value = "";
1015
+ if (out.body && out.body.queued) {
1016
+ detailPending = text;
1017
+ msg.textContent = "mensagem na fila — envia quando o turno terminar";
1018
+ } else {
1019
+ detailPending = null;
1020
+ msg.textContent = "mensagem enviada — nova etapa começou";
1021
+ }
1022
+ poll();
1023
+ } else {
1024
+ msg.textContent = (out.body && out.body.error) || "não deu para enviar";
1025
+ }
1026
+ }).catch(function () {
1027
+ msg.textContent = "daemon fora do ar?";
1028
+ }).then(function () {
1029
+ sendInFlight = false;
1030
+ paintSendState();
1031
+ });
1032
+ });
1033
+ document.getElementById("dArchive").addEventListener("click", function () {
1034
+ if (!detailId) return;
1035
+ var msg = document.getElementById("dSendMsg");
1036
+ var btn = document.getElementById("dArchive");
1037
+ btn.disabled = true;
1038
+ msg.textContent = "arquivando...";
1039
+ fetch("api/sessions/" + encodeURIComponent(detailId) + "/release", { method: "POST" }).then(function (r) {
1040
+ return r.json().then(function (j) { return { ok: r.ok, body: j }; });
1041
+ }).then(function (out) {
1042
+ if (out.ok) {
1043
+ msg.textContent = "sessão arquivada como concluída";
1044
+ selectSession(detailId);
1045
+ poll();
1046
+ } else {
1047
+ msg.textContent = (out.body && out.body.error) || "não deu para arquivar";
1048
+ }
1049
+ }).catch(function () {
1050
+ msg.textContent = "daemon fora do ar?";
1051
+ }).then(function () {
1052
+ btn.disabled = false;
1053
+ });
1054
+ });
907
1055
 
908
1056
  function poll() {
909
1057
  fetch("api/sessions").then(function (r) { return r.json(); }).then(function (j) {
@@ -1 +1 @@
1
- {"version":3,"file":"canvas-page.js","sourceRoot":"","sources":["../../src/web/canvas-page.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAg5B3B,CAAC"}
1
+ {"version":3,"file":"canvas-page.js","sourceRoot":"","sources":["../../src/web/canvas-page.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAoiC3B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codedeck",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "CodeDeck, a local runtime for coding agents with session management, process supervision, event normalization, and git isolation",
5
5
  "type": "module",
6
6
  "bin": {