@mjasnikovs/pi-task 0.14.19 → 0.14.21
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/dist/remote/ui-script.js
CHANGED
|
@@ -872,5 +872,32 @@ export function clientScript(wsUrl) {
|
|
|
872
872
|
window.addEventListener('online', connectNow);
|
|
873
873
|
window.addEventListener('focus', connectNow);
|
|
874
874
|
|
|
875
|
+
// Pin the column to window.innerHeight (a stable px value) instead of letting
|
|
876
|
+
// it ride 100dvh, which iOS Safari interpolates DURING the rotation animation
|
|
877
|
+
// and makes the whole layout resize repeatedly ("spazzing out"). A rotation
|
|
878
|
+
// also fires several resize events and changes scrollHeight, so coalesce the
|
|
879
|
+
// updates in one rAF and re-pin to the bottom when the user was already there.
|
|
880
|
+
let appHeightRaf = null;
|
|
881
|
+
let appHeightPin = false;
|
|
882
|
+
function setAppHeight() {
|
|
883
|
+
const h = (window.visualViewport && window.visualViewport.height) || window.innerHeight;
|
|
884
|
+
document.documentElement.style.setProperty('--app-h', h + 'px');
|
|
885
|
+
// Snapshot autoScroll synchronously, before the post-rotation reflow can fire
|
|
886
|
+
// a scroll event that flips it. When several resize events coalesce into one
|
|
887
|
+
// frame, the LATEST snapshot must win — otherwise a stale "was at bottom"
|
|
888
|
+
// capture (e.g. the load-time call, when the empty log counts as at-bottom)
|
|
889
|
+
// would yank a since-scrolled-up reader to the bottom on the next rotate.
|
|
890
|
+
appHeightPin = autoScroll;
|
|
891
|
+
if (appHeightRaf) return;
|
|
892
|
+
appHeightRaf = requestAnimationFrame(function () {
|
|
893
|
+
appHeightRaf = null;
|
|
894
|
+
if (appHeightPin) chatLog.scrollTop = chatLog.scrollHeight;
|
|
895
|
+
updateScrollBtn();
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
setAppHeight();
|
|
899
|
+
window.addEventListener('resize', setAppHeight);
|
|
900
|
+
window.addEventListener('orientationchange', setAppHeight);
|
|
901
|
+
|
|
875
902
|
connect();`;
|
|
876
903
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const STYLES = " :root {\n --base: #1e1e2e; --mantle: #181825; --crust: #11111b;\n --surface0: #313244; --surface1: #45475a; --surface2: #585b70;\n --text: #cdd6f4; --subtext1: #a6adc8; --subtext0: #7f849c;\n --mauve: #cba6f7; --blue: #89b4fa; --green: #a6e3a1; --red: #f38ba8;\n --yellow: #f9e2af; --peach: #fab387; --teal: #94e2d5;\n }\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: var(--base); color: var(--text);\n font-family: ui-monospace, monospace; height: 100dvh;\n display: flex; flex-direction: column; overflow: hidden;\n padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px)\n 0px env(safe-area-inset-left, 0px);\n }\n #context-bar { height: 4px; background: var(--surface0); flex-shrink: 0; }\n #context-bar-fill { height: 100%; background: var(--mauve); width: 0%; transition: width 0.4s ease; }\n #header {\n background: var(--mantle); padding: 8px 16px;\n display: flex; justify-content: space-between; align-items: center;\n font-size: 13px; flex-shrink: 0; border-bottom: 1px solid var(--surface0);\n }\n #header .title { font-weight: bold; color: var(--mauve); letter-spacing: 0.05em;\n position: relative; animation: glitch 5s steps(1) infinite; }\n @keyframes glitch {\n 0%, 88%, 100% { text-shadow: none; transform: translate(0, 0); }\n 90% { text-shadow: -1px 0 var(--red), 1px 0 var(--teal); transform: translate(1px, -1px); }\n 92% { text-shadow: 1px 0 var(--red), -1px 0 var(--blue); transform: translate(-1px, 1px); }\n 94% { text-shadow: -1px 0 var(--blue), 1px 0 var(--red); transform: translate(1px, 0); }\n 96% { text-shadow: 1px 0 var(--teal), -1px 0 var(--red); transform: translate(-1px, 0); }\n }\n @media (prefers-reduced-motion: reduce) { #header .title { animation: none; } }\n #header .hgroup { display: flex; align-items: center; gap: 10px; }\n #bell {\n background: none; border: none; color: var(--subtext1); cursor: pointer;\n font-size: 15px; line-height: 1; padding: 2px; font-family: inherit;\n }\n #bell:hover { color: var(--text); }\n #bell.on { color: var(--mauve); }\n #chat-wrap { position: relative; flex: 1; min-height: 0; display: flex; }\n #chat-log {\n flex: 1; min-width: 0; overflow-y: auto; overflow-x: hidden; padding: 16px;\n display: flex; flex-direction: column; gap: 8px;\n }\n /* Floating jump-to-latest button \u2014 only shown when scrolled away from the\n bottom (toggled via .visible from the scroll handler). */\n #scroll-bottom {\n display: none; position: absolute; bottom: 16px; right: 16px; z-index: 40;\n width: 36px; height: 36px; border-radius: 50%; cursor: pointer;\n background: var(--surface1); color: var(--text); border: 1px solid var(--surface2);\n font-family: inherit; font-size: 18px; line-height: 1; padding: 0;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n #scroll-bottom:hover { background: var(--surface2); color: var(--mauve); }\n #scroll-bottom.visible { display: block; }\n #chat-log::-webkit-scrollbar { width: 6px; }\n #chat-log::-webkit-scrollbar-track { background: transparent; }\n #chat-log::-webkit-scrollbar-thumb { background: var(--surface2); border-radius: 3px; }\n .bubble {\n max-width: 82%; padding: 8px 12px; border-radius: 8px;\n line-height: 1.6; white-space: pre-wrap; word-break: break-word; font-size: 13px;\n }\n .bubble.user { background: var(--surface1); color: var(--text); align-self: flex-end; }\n .bubble.assistant { background: var(--surface0); color: var(--text); align-self: flex-start; }\n .bubble.error {\n background: var(--crust); color: var(--red); align-self: stretch;\n max-width: 100%; border: 1px solid var(--red); font-size: 12px;\n }\n /* Persistent inline system note (e.g. context compaction) \u2014 a muted centered\n divider, distinct from chat bubbles. */\n .sysnote {\n align-self: center; color: var(--subtext0); font-size: 11px;\n font-family: ui-monospace, monospace; letter-spacing: 0.5px;\n padding: 2px 10px; opacity: 0.85;\n }\n .bubble.thinking {\n display: flex; gap: 5px; align-items: center; padding: 10px 14px;\n }\n .bubble.thinking .spinner {\n color: var(--mauve); font-size: 15px; line-height: 1;\n font-family: ui-monospace, monospace;\n }\n .tool-call {\n background: var(--crust); border-radius: 6px; align-self: flex-start;\n max-width: 90%; font-size: 12px; border: 1px solid var(--surface0);\n }\n .tool-call summary {\n padding: 6px 10px; color: var(--subtext1); cursor: pointer;\n user-select: none; list-style: none;\n overflow-wrap: anywhere; word-break: break-word;\n }\n .tool-call summary::-webkit-details-marker { display: none; }\n .tool-call summary::before { content: \"\u25B6 \"; }\n .tool-call[open] > summary::before { content: \"\u25BC \"; }\n .tool-call.error > summary { color: var(--red); }\n .tool-call pre {\n padding: 8px 12px; overflow-y: auto;\n color: var(--subtext1); font-size: 11px; max-height: 280px;\n border-top: 1px solid var(--surface0);\n white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;\n }\n .tool-spin { color: var(--mauve); margin-left: 6px; font-family: ui-monospace, monospace; font-size: 13px; }\n .code-block {\n background: var(--crust); border: 1px solid var(--surface0);\n border-radius: 6px; overflow: hidden; margin: 4px 0;\n align-self: stretch; max-width: 100%; font-size: 12px;\n }\n .code-lang {\n background: var(--surface0); color: var(--subtext0);\n font-size: 10px; padding: 3px 10px; letter-spacing: 0.05em;\n }\n .code-block code {\n display: block; padding: 10px 12px; overflow-x: auto;\n color: var(--text); white-space: pre; line-height: 1.55;\n }\n .hl-kw { color: var(--mauve); }\n .hl-str { color: var(--green); }\n .hl-cmt { color: var(--subtext0); font-style: italic; }\n .hl-num { color: var(--blue); }\n .hl-fn { color: var(--yellow); }\n #input-bar {\n background: var(--mantle); padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));\n display: flex; gap: 8px; flex-shrink: 0;\n border-top: 1px solid var(--surface0);\n position: relative;\n }\n #cmd-suggestions {\n display: none; position: absolute; bottom: 100%; left: 16px; right: 16px;\n background: var(--mantle); border: 1px solid var(--surface1);\n border-bottom: none; border-radius: 8px 8px 0 0;\n overflow: hidden; z-index: 10;\n }\n .cmd-item {\n display: flex; align-items: baseline; gap: 10px;\n padding: 7px 12px; cursor: pointer; font-size: 12px;\n border-bottom: 1px solid var(--surface0);\n }\n .cmd-item:last-child { border-bottom: none; }\n .cmd-item:hover, .cmd-item.active { background: var(--surface0); }\n .cmd-item .cmd-name { color: var(--blue); font-weight: bold; flex-shrink: 0; }\n .cmd-item .cmd-desc { color: var(--subtext0); }\n #input {\n flex: 1; background: var(--surface0); color: var(--text);\n border: none; border-radius: 6px; padding: 8px 12px;\n font-family: inherit; font-size: 13px; resize: none;\n outline: none; line-height: 1.5; min-height: 36px; max-height: 120px;\n }\n #input::placeholder { color: var(--subtext0); }\n #input:focus { box-shadow: 0 0 0 1px var(--mauve); }\n #send {\n background: var(--blue); color: var(--crust); border: none;\n border-radius: 6px; padding: 8px 16px; font-weight: bold;\n cursor: pointer; font-size: 13px; font-family: inherit;\n white-space: nowrap; align-self: flex-end;\n }\n #send:disabled, #input:disabled { opacity: 0.45; cursor: not-allowed; }\n #reconnect-overlay {\n display: none; position: fixed; inset: 0;\n background: rgba(30,30,46,0.88); color: var(--subtext1);\n justify-content: center; align-items: center;\n font-size: 13px; z-index: 100; letter-spacing: 0.03em;\n }\n #reconnect-overlay.visible { display: flex; }\n /* Trailing stream indicator: the same braille spinner as the thinking bubble,\n inline at the end of the streaming text (not a green blinking block). */\n .cursor {\n color: var(--mauve); margin-left: 2px;\n font-family: ui-monospace, monospace;\n }\n #status-panel { padding: 6px 12px; border-bottom: 1px solid var(--surface1);\n color: var(--subtext1); white-space: pre-wrap; font-size: 13px; display: none; }\n #prompt-card { position: fixed; left: 0; right: 0; bottom: 0; background: var(--mantle);\n border-top: 2px solid var(--mauve); padding: 16px 14px calc(16px + env(safe-area-inset-bottom, 0px));\n display: none; z-index: 50; max-height: 80dvh; overflow-y: auto; }\n #prompt-card .q-label { color: var(--mauve); font-size: 11px; font-weight: 700;\n text-transform: uppercase; letter-spacing: .6px; margin-bottom: 6px; }\n #prompt-card .q { color: var(--text); margin-bottom: 12px; white-space: pre-wrap;\n font-size: 15px; line-height: 1.5; }\n #prompt-card .rec-panel { background: var(--surface0); border-left: 3px solid var(--green);\n border-radius: 6px; padding: 10px 12px; margin-bottom: 12px; }\n #prompt-card .rec-label { color: var(--green); font-size: 11px; font-weight: 700;\n text-transform: uppercase; letter-spacing: .5px; margin-bottom: 4px; }\n #prompt-card .rec-text { color: var(--text); font-size: 15px; line-height: 1.5;\n white-space: pre-wrap; overflow-wrap: anywhere; }\n #prompt-card textarea { width: 100%; background: var(--surface0); color: var(--text);\n border: 1px solid var(--surface2); border-radius: 6px; padding: 10px; font-size: 15px;\n font-family: inherit; line-height: 1.5; resize: vertical; margin-bottom: 4px; }\n #prompt-card .row { display: flex; gap: 8px; margin-top: 12px; align-items: stretch;\n flex-wrap: wrap; }\n /* Recommendation answers can be long sentences, so stack them as a readable list. */\n #prompt-card .row.stacked { flex-direction: column; align-items: stretch; }\n #prompt-card .row.stacked button { flex: none; text-align: left; }\n #prompt-card .row.stacked button.cancel { align-self: center; text-align: center; }\n #prompt-card button { padding: 11px 16px; border-radius: 8px; border: none; cursor: pointer;\n font-family: inherit; font-size: 14px; font-weight: 600; transition: filter .15s ease; }\n #prompt-card button:hover { filter: brightness(1.08); }\n #prompt-card button.primary { background: var(--green); color: var(--crust);\n font-weight: 700; flex: 1; min-width: 160px; }\n #prompt-card button.secondary { background: var(--surface1); color: var(--text);\n flex: 1; min-width: 160px; }\n #prompt-card button.cancel { margin-left: auto; align-self: center; background: transparent;\n color: var(--subtext0); font-size: 12px; font-weight: 500; padding: 8px 10px; }\n #prompt-card button.cancel:hover { color: var(--red); filter: none; }\n #prompt-card button.cancel.armed { background: var(--red); color: var(--crust); font-weight: 700; }\n .toast { position: fixed; top: calc(env(safe-area-inset-top, 0px) + 12px);\n right: calc(env(safe-area-inset-right, 0px) + 12px); max-width: calc(100vw - 24px);\n padding: 8px 12px; border-radius: 6px; overflow-wrap: anywhere; word-break: break-word;\n background: var(--surface1); color: var(--text); z-index: 60; }\n .toast.warning { background: var(--peach); color: var(--crust); }\n .toast.error { background: var(--red); color: var(--crust); }\n #viewer { position: fixed; inset: 24px; background: var(--mantle); border: 1px solid var(--surface2);\n border-radius: 8px; padding: 16px; overflow: auto; white-space: pre-wrap;\n overflow-wrap: anywhere; word-break: break-word; display: none; z-index: 70; }\n #viewer .close { position: absolute; top: 8px; right: 12px; cursor: pointer; color: var(--subtext0); }";
|
|
1
|
+
export declare const STYLES = " :root {\n --base: #1e1e2e; --mantle: #181825; --crust: #11111b;\n --surface0: #313244; --surface1: #45475a; --surface2: #585b70;\n --text: #cdd6f4; --subtext1: #a6adc8; --subtext0: #7f849c;\n --mauve: #cba6f7; --blue: #89b4fa; --green: #a6e3a1; --red: #f38ba8;\n --yellow: #f9e2af; --peach: #fab387; --teal: #94e2d5;\n }\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: var(--base); color: var(--text);\n font-family: ui-monospace, monospace;\n /* --app-h is set from window.innerHeight (see setAppHeight) so the column\n height is a stable pixel value across an orientation change. 100dvh is a\n fallback for first paint / no-JS: iOS Safari interpolates dvh during the\n rotation animation, which makes the whole flex column resize repeatedly\n (\"spazzing out\") \u2014 a fixed px height does not. */\n height: var(--app-h, 100dvh);\n display: flex; flex-direction: column; overflow: hidden;\n padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px)\n 0px env(safe-area-inset-left, 0px);\n }\n #context-bar { height: 4px; background: var(--surface0); flex-shrink: 0; }\n #context-bar-fill { height: 100%; background: var(--mauve); width: 0%; transition: width 0.4s ease; }\n #header {\n background: var(--mantle); padding: 8px 16px;\n display: flex; justify-content: space-between; align-items: center;\n font-size: 13px; flex-shrink: 0; border-bottom: 1px solid var(--surface0);\n }\n #header .title { font-weight: bold; color: var(--mauve); letter-spacing: 0.05em;\n position: relative; animation: glitch 5s steps(1) infinite; }\n @keyframes glitch {\n 0%, 88%, 100% { text-shadow: none; transform: translate(0, 0); }\n 90% { text-shadow: -1px 0 var(--red), 1px 0 var(--teal); transform: translate(1px, -1px); }\n 92% { text-shadow: 1px 0 var(--red), -1px 0 var(--blue); transform: translate(-1px, 1px); }\n 94% { text-shadow: -1px 0 var(--blue), 1px 0 var(--red); transform: translate(1px, 0); }\n 96% { text-shadow: 1px 0 var(--teal), -1px 0 var(--red); transform: translate(-1px, 0); }\n }\n @media (prefers-reduced-motion: reduce) { #header .title { animation: none; } }\n #header .hgroup { display: flex; align-items: center; gap: 10px; }\n #bell {\n background: none; border: none; color: var(--subtext1); cursor: pointer;\n font-size: 15px; line-height: 1; padding: 2px; font-family: inherit;\n }\n #bell:hover { color: var(--text); }\n #bell.on { color: var(--mauve); }\n #chat-wrap { position: relative; flex: 1; min-height: 0; display: flex; }\n #chat-log {\n flex: 1; min-width: 0; overflow-y: auto; overflow-x: hidden; padding: 16px;\n display: flex; flex-direction: column; gap: 8px;\n }\n /* Floating jump-to-latest button \u2014 only shown when scrolled away from the\n bottom (toggled via .visible from the scroll handler). */\n #scroll-bottom {\n display: none; position: absolute; bottom: 16px; right: 16px; z-index: 40;\n width: 36px; height: 36px; border-radius: 50%; cursor: pointer;\n background: var(--surface1); color: var(--text); border: 1px solid var(--surface2);\n font-family: inherit; font-size: 18px; line-height: 1; padding: 0;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);\n }\n #scroll-bottom:hover { background: var(--surface2); color: var(--mauve); }\n #scroll-bottom.visible { display: block; }\n #chat-log::-webkit-scrollbar { width: 6px; }\n #chat-log::-webkit-scrollbar-track { background: transparent; }\n #chat-log::-webkit-scrollbar-thumb { background: var(--surface2); border-radius: 3px; }\n .bubble {\n max-width: 82%; padding: 8px 12px; border-radius: 8px;\n line-height: 1.6; white-space: pre-wrap; word-break: break-word; font-size: 13px;\n }\n .bubble.user { background: var(--surface1); color: var(--text); align-self: flex-end; }\n .bubble.assistant { background: var(--surface0); color: var(--text); align-self: flex-start; }\n .bubble.error {\n background: var(--crust); color: var(--red); align-self: stretch;\n max-width: 100%; border: 1px solid var(--red); font-size: 12px;\n }\n /* Persistent inline system note (e.g. context compaction) \u2014 a muted centered\n divider, distinct from chat bubbles. */\n .sysnote {\n align-self: center; color: var(--subtext0); font-size: 11px;\n font-family: ui-monospace, monospace; letter-spacing: 0.5px;\n padding: 2px 10px; opacity: 0.85;\n }\n .bubble.thinking {\n display: flex; gap: 5px; align-items: center; padding: 10px 14px;\n }\n .bubble.thinking .spinner {\n color: var(--mauve); font-size: 15px; line-height: 1;\n font-family: ui-monospace, monospace;\n }\n .tool-call {\n background: var(--crust); border-radius: 6px; align-self: flex-start;\n max-width: 90%; font-size: 12px; border: 1px solid var(--surface0);\n }\n .tool-call summary {\n padding: 6px 10px; color: var(--subtext1); cursor: pointer;\n user-select: none; list-style: none;\n overflow-wrap: anywhere; word-break: break-word;\n }\n .tool-call summary::-webkit-details-marker { display: none; }\n .tool-call summary::before { content: \"\u25B6 \"; }\n .tool-call[open] > summary::before { content: \"\u25BC \"; }\n .tool-call.error > summary { color: var(--red); }\n .tool-call pre {\n padding: 8px 12px; overflow-y: auto;\n color: var(--subtext1); font-size: 11px; max-height: 280px;\n border-top: 1px solid var(--surface0);\n white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word;\n }\n .tool-spin { color: var(--mauve); margin-left: 6px; font-family: ui-monospace, monospace; font-size: 13px; }\n .code-block {\n background: var(--crust); border: 1px solid var(--surface0);\n border-radius: 6px; overflow: hidden; margin: 4px 0;\n align-self: stretch; max-width: 100%; font-size: 12px;\n }\n .code-lang {\n background: var(--surface0); color: var(--subtext0);\n font-size: 10px; padding: 3px 10px; letter-spacing: 0.05em;\n }\n .code-block code {\n display: block; padding: 10px 12px; overflow-x: auto;\n color: var(--text); white-space: pre; line-height: 1.55;\n }\n .hl-kw { color: var(--mauve); }\n .hl-str { color: var(--green); }\n .hl-cmt { color: var(--subtext0); font-style: italic; }\n .hl-num { color: var(--blue); }\n .hl-fn { color: var(--yellow); }\n #input-bar {\n background: var(--mantle); padding: 10px 16px calc(10px + env(safe-area-inset-bottom, 0px));\n display: flex; gap: 8px; flex-shrink: 0;\n border-top: 1px solid var(--surface0);\n position: relative;\n }\n #cmd-suggestions {\n display: none; position: absolute; bottom: 100%; left: 16px; right: 16px;\n background: var(--mantle); border: 1px solid var(--surface1);\n border-bottom: none; border-radius: 8px 8px 0 0;\n overflow: hidden; z-index: 10;\n }\n .cmd-item {\n display: flex; align-items: baseline; gap: 10px;\n padding: 7px 12px; cursor: pointer; font-size: 12px;\n border-bottom: 1px solid var(--surface0);\n }\n .cmd-item:last-child { border-bottom: none; }\n .cmd-item:hover, .cmd-item.active { background: var(--surface0); }\n .cmd-item .cmd-name { color: var(--blue); font-weight: bold; flex-shrink: 0; }\n .cmd-item .cmd-desc { color: var(--subtext0); }\n #input {\n flex: 1; background: var(--surface0); color: var(--text);\n border: none; border-radius: 6px; padding: 8px 12px;\n font-family: inherit; font-size: 13px; resize: none;\n outline: none; line-height: 1.5; min-height: 36px; max-height: 120px;\n }\n #input::placeholder { color: var(--subtext0); }\n #input:focus { box-shadow: 0 0 0 1px var(--mauve); }\n #send {\n background: var(--blue); color: var(--crust); border: none;\n border-radius: 6px; padding: 8px 16px; font-weight: bold;\n cursor: pointer; font-size: 13px; font-family: inherit;\n white-space: nowrap; align-self: flex-end;\n }\n #send:disabled, #input:disabled { opacity: 0.45; cursor: not-allowed; }\n #reconnect-overlay {\n display: none; position: fixed; inset: 0;\n background: rgba(30,30,46,0.88); color: var(--subtext1);\n justify-content: center; align-items: center;\n font-size: 13px; z-index: 100; letter-spacing: 0.03em;\n }\n #reconnect-overlay.visible { display: flex; }\n /* Trailing stream indicator: the same braille spinner as the thinking bubble,\n inline at the end of the streaming text (not a green blinking block). */\n .cursor {\n color: var(--mauve); margin-left: 2px;\n font-family: ui-monospace, monospace;\n }\n #status-panel { padding: 6px 12px; border-bottom: 1px solid var(--surface1);\n color: var(--subtext1); white-space: pre-wrap; font-size: 13px; display: none; }\n #prompt-card { position: fixed; left: 0; right: 0; bottom: 0; background: var(--mantle);\n border-top: 2px solid var(--mauve); padding: 16px 14px calc(16px + env(safe-area-inset-bottom, 0px));\n display: none; z-index: 50; max-height: 80dvh; overflow-y: auto; }\n #prompt-card .q-label { color: var(--mauve); font-size: 11px; font-weight: 700;\n text-transform: uppercase; letter-spacing: .6px; margin-bottom: 6px; }\n #prompt-card .q { color: var(--text); margin-bottom: 12px; white-space: pre-wrap;\n font-size: 15px; line-height: 1.5; }\n #prompt-card .rec-panel { background: var(--surface0); border-left: 3px solid var(--green);\n border-radius: 6px; padding: 10px 12px; margin-bottom: 12px; }\n #prompt-card .rec-label { color: var(--green); font-size: 11px; font-weight: 700;\n text-transform: uppercase; letter-spacing: .5px; margin-bottom: 4px; }\n #prompt-card .rec-text { color: var(--text); font-size: 15px; line-height: 1.5;\n white-space: pre-wrap; overflow-wrap: anywhere; }\n #prompt-card textarea { width: 100%; background: var(--surface0); color: var(--text);\n border: 1px solid var(--surface2); border-radius: 6px; padding: 10px; font-size: 15px;\n font-family: inherit; line-height: 1.5; resize: vertical; margin-bottom: 4px; }\n #prompt-card .row { display: flex; gap: 8px; margin-top: 12px; align-items: stretch;\n flex-wrap: wrap; }\n /* Recommendation answers can be long sentences, so stack them as a readable list. */\n #prompt-card .row.stacked { flex-direction: column; align-items: stretch; }\n #prompt-card .row.stacked button { flex: none; text-align: left; }\n #prompt-card .row.stacked button.cancel { align-self: center; text-align: center; }\n #prompt-card button { padding: 11px 16px; border-radius: 8px; border: none; cursor: pointer;\n font-family: inherit; font-size: 14px; font-weight: 600; transition: filter .15s ease; }\n #prompt-card button:hover { filter: brightness(1.08); }\n #prompt-card button.primary { background: var(--green); color: var(--crust);\n font-weight: 700; flex: 1; min-width: 160px; }\n #prompt-card button.secondary { background: var(--surface1); color: var(--text);\n flex: 1; min-width: 160px; }\n #prompt-card button.cancel { margin-left: auto; align-self: center; background: transparent;\n color: var(--subtext0); font-size: 12px; font-weight: 500; padding: 8px 10px; }\n #prompt-card button.cancel:hover { color: var(--red); filter: none; }\n #prompt-card button.cancel.armed { background: var(--red); color: var(--crust); font-weight: 700; }\n .toast { position: fixed; top: calc(env(safe-area-inset-top, 0px) + 12px);\n right: calc(env(safe-area-inset-right, 0px) + 12px); max-width: calc(100vw - 24px);\n padding: 8px 12px; border-radius: 6px; overflow-wrap: anywhere; word-break: break-word;\n background: var(--surface1); color: var(--text); z-index: 60; }\n .toast.warning { background: var(--peach); color: var(--crust); }\n .toast.error { background: var(--red); color: var(--crust); }\n #viewer { position: fixed; inset: 24px; background: var(--mantle); border: 1px solid var(--surface2);\n border-radius: 8px; padding: 16px; overflow: auto; white-space: pre-wrap;\n overflow-wrap: anywhere; word-break: break-word; display: none; z-index: 70; }\n #viewer .close { position: absolute; top: 8px; right: 12px; cursor: pointer; color: var(--subtext0); }";
|
package/dist/remote/ui-styles.js
CHANGED
|
@@ -8,7 +8,13 @@ export const STYLES = ` :root {
|
|
|
8
8
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
9
|
body {
|
|
10
10
|
background: var(--base); color: var(--text);
|
|
11
|
-
font-family: ui-monospace, monospace;
|
|
11
|
+
font-family: ui-monospace, monospace;
|
|
12
|
+
/* --app-h is set from window.innerHeight (see setAppHeight) so the column
|
|
13
|
+
height is a stable pixel value across an orientation change. 100dvh is a
|
|
14
|
+
fallback for first paint / no-JS: iOS Safari interpolates dvh during the
|
|
15
|
+
rotation animation, which makes the whole flex column resize repeatedly
|
|
16
|
+
("spazzing out") — a fixed px height does not. */
|
|
17
|
+
height: var(--app-h, 100dvh);
|
|
12
18
|
display: flex; flex-direction: column; overflow: hidden;
|
|
13
19
|
padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px)
|
|
14
20
|
0px env(safe-area-inset-left, 0px);
|
|
@@ -113,6 +113,16 @@ export interface RunSingleTaskOptions {
|
|
|
113
113
|
*/
|
|
114
114
|
fixInstruction?: string;
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* The slice of the replacement-session context the steer loop needs.
|
|
118
|
+
* `sendUserMessage` lives on ReplacedSessionContext (not the base command ctx,
|
|
119
|
+
* and not re-exported from the package), so we narrow to just what we call.
|
|
120
|
+
*/
|
|
121
|
+
export type SteerCtx = ExtensionCommandContext & {
|
|
122
|
+
sendUserMessage(content: string, options?: {
|
|
123
|
+
deliverAs?: 'steer' | 'followUp';
|
|
124
|
+
}): Promise<void>;
|
|
125
|
+
};
|
|
116
126
|
export interface RunSingleTaskResult {
|
|
117
127
|
taskId: string;
|
|
118
128
|
ok: boolean;
|
|
@@ -145,6 +155,55 @@ export interface RunSingleTaskResult {
|
|
|
145
155
|
*/
|
|
146
156
|
reason?: string;
|
|
147
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* True when the implementation turn went idle right after a context compaction —
|
|
160
|
+
* the most recent entry in the branch is a `compaction` boundary sitting after the
|
|
161
|
+
* last assistant message.
|
|
162
|
+
*
|
|
163
|
+
* A *threshold* auto-compaction (the runtime's "context is getting large" path)
|
|
164
|
+
* compacts and then deliberately does NOT auto-continue: it returns to idle and
|
|
165
|
+
* expects a manual continue (`_runAutoCompaction("threshold", false)` →
|
|
166
|
+
* `hasQueuedMessages()` is false → the agent loop stops). Our implementation wait
|
|
167
|
+
* resolves at exactly that idle. Without this check it reads as "the model
|
|
168
|
+
* finished" (the last assistant message is a normal `stop`, not `aborted`/`error`),
|
|
169
|
+
* so the run jumps straight to the verify gate and abandons a half-done task at the
|
|
170
|
+
* compaction boundary — the failure this detector closes.
|
|
171
|
+
*
|
|
172
|
+
* Position-based, not timestamp-based: the runtime APPENDS the compaction entry to
|
|
173
|
+
* the tail of the branch after the assistant message that triggered it
|
|
174
|
+
* (`appendCompaction` → `_appendEntry` push), so a `compaction` after the last
|
|
175
|
+
* assistant message means we are parked on a compaction with no continuation. A
|
|
176
|
+
* genuinely finished turn ends on an assistant message with no trailing compaction;
|
|
177
|
+
* an *overflow* compaction self-retries, so it never leaves us idle here.
|
|
178
|
+
*/
|
|
179
|
+
export declare function endedAtCompactionBoundary(ctx: ExtensionCommandContext): boolean;
|
|
180
|
+
/**
|
|
181
|
+
* Nudge that resumes an implementation turn the runtime parked at a compaction
|
|
182
|
+
* boundary. It must let a turn that was genuinely finished (then tipped over the
|
|
183
|
+
* threshold by its own final message) confirm completion without inventing busywork
|
|
184
|
+
* — we cannot tell "paused mid-task by compaction" from "finished, then compacted"
|
|
185
|
+
* from the boundary alone, so the wording lets a done turn end in one line.
|
|
186
|
+
*/
|
|
187
|
+
export declare const CONTINUE_AFTER_COMPACTION: string;
|
|
188
|
+
/**
|
|
189
|
+
* Safety cap on compaction-driven resumes for a single implementation turn. Each
|
|
190
|
+
* resume follows a real compaction (which only fires after the model produced a
|
|
191
|
+
* turn large enough to cross the threshold), so a legitimately large task may
|
|
192
|
+
* resume a handful of times; the cap exists only to stop a pathological loop from
|
|
193
|
+
* auto-sending forever with no user in the loop. Hitting it stops resuming and lets
|
|
194
|
+
* the verify gate / `/task-auto-resume` catch any leftover incompleteness.
|
|
195
|
+
*/
|
|
196
|
+
export declare const MAX_COMPACTION_RESUMES = 20;
|
|
197
|
+
/**
|
|
198
|
+
* Resume an implementation turn that went idle at a threshold-compaction boundary.
|
|
199
|
+
* The runtime compacts and parks at idle without auto-continuing; we send a
|
|
200
|
+
* continue and wait again, repeating across successive compactions until the turn
|
|
201
|
+
* ends on a real assistant message (genuine completion). A user ESC takes priority
|
|
202
|
+
* (it is not a compaction boundary, and `wasInterrupted` guards the loop so the
|
|
203
|
+
* steer loop handles it), and the safety cap bounds a runaway. Returns the number
|
|
204
|
+
* of resumes performed (0 when the turn did not end on a compaction).
|
|
205
|
+
*/
|
|
206
|
+
export declare function resumeAcrossCompactions(ctx: SteerCtx): Promise<number>;
|
|
148
207
|
/**
|
|
149
208
|
* Run one prompt through the full single-task pipeline in a fresh session and
|
|
150
209
|
* deliver its spec. With waitForImplementation, block until the agent finishes
|
|
@@ -353,6 +353,82 @@ function implementationError(ctx) {
|
|
|
353
353
|
}
|
|
354
354
|
return undefined;
|
|
355
355
|
}
|
|
356
|
+
/**
|
|
357
|
+
* True when the implementation turn went idle right after a context compaction —
|
|
358
|
+
* the most recent entry in the branch is a `compaction` boundary sitting after the
|
|
359
|
+
* last assistant message.
|
|
360
|
+
*
|
|
361
|
+
* A *threshold* auto-compaction (the runtime's "context is getting large" path)
|
|
362
|
+
* compacts and then deliberately does NOT auto-continue: it returns to idle and
|
|
363
|
+
* expects a manual continue (`_runAutoCompaction("threshold", false)` →
|
|
364
|
+
* `hasQueuedMessages()` is false → the agent loop stops). Our implementation wait
|
|
365
|
+
* resolves at exactly that idle. Without this check it reads as "the model
|
|
366
|
+
* finished" (the last assistant message is a normal `stop`, not `aborted`/`error`),
|
|
367
|
+
* so the run jumps straight to the verify gate and abandons a half-done task at the
|
|
368
|
+
* compaction boundary — the failure this detector closes.
|
|
369
|
+
*
|
|
370
|
+
* Position-based, not timestamp-based: the runtime APPENDS the compaction entry to
|
|
371
|
+
* the tail of the branch after the assistant message that triggered it
|
|
372
|
+
* (`appendCompaction` → `_appendEntry` push), so a `compaction` after the last
|
|
373
|
+
* assistant message means we are parked on a compaction with no continuation. A
|
|
374
|
+
* genuinely finished turn ends on an assistant message with no trailing compaction;
|
|
375
|
+
* an *overflow* compaction self-retries, so it never leaves us idle here.
|
|
376
|
+
*/
|
|
377
|
+
export function endedAtCompactionBoundary(ctx) {
|
|
378
|
+
const entries = ctx.sessionManager.getEntries();
|
|
379
|
+
let lastAssistant = -1;
|
|
380
|
+
let lastCompaction = -1;
|
|
381
|
+
for (let i = 0; i < entries.length; i++) {
|
|
382
|
+
const e = entries[i];
|
|
383
|
+
if ('message' in e && 'role' in e.message && e.message.role === 'assistant') {
|
|
384
|
+
lastAssistant = i;
|
|
385
|
+
}
|
|
386
|
+
else if (e.type === 'compaction') {
|
|
387
|
+
lastCompaction = i;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return lastCompaction > lastAssistant;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Nudge that resumes an implementation turn the runtime parked at a compaction
|
|
394
|
+
* boundary. It must let a turn that was genuinely finished (then tipped over the
|
|
395
|
+
* threshold by its own final message) confirm completion without inventing busywork
|
|
396
|
+
* — we cannot tell "paused mid-task by compaction" from "finished, then compacted"
|
|
397
|
+
* from the boundary alone, so the wording lets a done turn end in one line.
|
|
398
|
+
*/
|
|
399
|
+
export const CONTINUE_AFTER_COMPACTION = 'Your context was automatically compacted. Continue implementing this task from '
|
|
400
|
+
+ 'exactly where you left off, and keep going until it is fully done. If the '
|
|
401
|
+
+ 'implementation is already complete, say so in one line and stop — do not invent '
|
|
402
|
+
+ 'extra work or restart the task.';
|
|
403
|
+
/**
|
|
404
|
+
* Safety cap on compaction-driven resumes for a single implementation turn. Each
|
|
405
|
+
* resume follows a real compaction (which only fires after the model produced a
|
|
406
|
+
* turn large enough to cross the threshold), so a legitimately large task may
|
|
407
|
+
* resume a handful of times; the cap exists only to stop a pathological loop from
|
|
408
|
+
* auto-sending forever with no user in the loop. Hitting it stops resuming and lets
|
|
409
|
+
* the verify gate / `/task-auto-resume` catch any leftover incompleteness.
|
|
410
|
+
*/
|
|
411
|
+
export const MAX_COMPACTION_RESUMES = 20;
|
|
412
|
+
/**
|
|
413
|
+
* Resume an implementation turn that went idle at a threshold-compaction boundary.
|
|
414
|
+
* The runtime compacts and parks at idle without auto-continuing; we send a
|
|
415
|
+
* continue and wait again, repeating across successive compactions until the turn
|
|
416
|
+
* ends on a real assistant message (genuine completion). A user ESC takes priority
|
|
417
|
+
* (it is not a compaction boundary, and `wasInterrupted` guards the loop so the
|
|
418
|
+
* steer loop handles it), and the safety cap bounds a runaway. Returns the number
|
|
419
|
+
* of resumes performed (0 when the turn did not end on a compaction).
|
|
420
|
+
*/
|
|
421
|
+
export async function resumeAcrossCompactions(ctx) {
|
|
422
|
+
let resumes = 0;
|
|
423
|
+
while (resumes < MAX_COMPACTION_RESUMES
|
|
424
|
+
&& !wasInterrupted(ctx)
|
|
425
|
+
&& endedAtCompactionBoundary(ctx)) {
|
|
426
|
+
await ctx.sendUserMessage(CONTINUE_AFTER_COMPACTION);
|
|
427
|
+
await ctx.waitForIdle();
|
|
428
|
+
resumes++;
|
|
429
|
+
}
|
|
430
|
+
return resumes;
|
|
431
|
+
}
|
|
356
432
|
/**
|
|
357
433
|
* After the implementation turn settles, honour a user ESC by letting them steer.
|
|
358
434
|
*
|
|
@@ -404,6 +480,14 @@ export async function runSingleTask(ctx, cwd, rawPrompt, opts = {}) {
|
|
|
404
480
|
await newCtx.sendUserMessage(spec);
|
|
405
481
|
if (opts.waitForImplementation) {
|
|
406
482
|
await newCtx.waitForIdle();
|
|
483
|
+
// A threshold auto-compaction parks the turn at idle WITHOUT
|
|
484
|
+
// auto-continuing (the runtime expects a manual continue). Our
|
|
485
|
+
// single waitForIdle resolves there, so without this the run
|
|
486
|
+
// would treat a compaction mid-task as completion and jump
|
|
487
|
+
// straight to the verify gate. Resume across any compaction
|
|
488
|
+
// boundaries first, so steering/error inspection below see the
|
|
489
|
+
// turn's REAL end, not a compaction pause.
|
|
490
|
+
await resumeAcrossCompactions(newCtx);
|
|
407
491
|
interrupted = await steerUntilDone(newCtx, opts.promptSteer);
|
|
408
492
|
// A user-declined steer (interrupted) is its own paused
|
|
409
493
|
// path; otherwise inspect how the turn actually ended.
|
|
@@ -62,7 +62,14 @@ function escapeRegex(s) {
|
|
|
62
62
|
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
63
63
|
}
|
|
64
64
|
export function sectionRegex(heading) {
|
|
65
|
-
|
|
65
|
+
// Group 1 captures ONLY the heading line — `[ \t]*\n`, not `\s*\n`. A greedy
|
|
66
|
+
// `\s*` swallows every blank line after the heading into the capture, and
|
|
67
|
+
// setTaskSection re-emits that capture verbatim plus one more `\n`, so each
|
|
68
|
+
// rewrite grew the gap by a blank line (the "big empty gap" under `## tasks`
|
|
69
|
+
// after a long /task-auto run). Keeping blanks in group 2 lets the `.trim()`
|
|
70
|
+
// every reader already applies collapse them, which also self-heals files
|
|
71
|
+
// that already accumulated the gap.
|
|
72
|
+
return new RegExp(`(^## ${escapeRegex(heading)}[ \\t]*\\n)([\\s\\S]*?)(?=^## |$(?![\\s\\S]))`, 'm');
|
|
66
73
|
}
|
|
67
74
|
export function extractSection(body, heading) {
|
|
68
75
|
const m = sectionRegex(heading).exec(body);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mjasnikovs/pi-task",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.21",
|
|
4
4
|
"description": "Deterministic spec-orchestration for local models, with a bundled real-time remote web view and web/docs/fetch/worker subagent tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|