gestalt-mobile 0.15.0 → 0.16.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.
@@ -0,0 +1 @@
1
+ .session-path.svelte-t9laac{text-align:center;text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;margin:0;overflow:hidden}.session-model.svelte-t9laac{color:var(--theme-text-muted);font-family:var(--theme-font-code);margin-inline-start:clamp(.75rem,3vw,2rem)}@media (width<=34rem){.session-model.svelte-t9laac{display:none}}.chat-activity.svelte-thyolt,.live-activity.svelte-thyolt{--activity-gap:.4rem;--activity-type-color:var(--theme-text-muted)}.chat-activity.svelte-thyolt>summary:where(.svelte-thyolt){color:var(--activity-type-color);cursor:pointer;font-size:.875em}.live-activity.svelte-thyolt{margin-block-start:.5rem}.live-activity.svelte-thyolt>strong:where(.svelte-thyolt){color:var(--activity-type-color);font-size:.875em}.activity-list.svelte-thyolt{gap:var(--activity-gap);margin-block:.25rem 0;margin-inline:0;padding:0;list-style:none;display:grid}.activity-row.svelte-thyolt{flex-wrap:wrap;align-items:baseline;gap:.25rem .5rem;display:flex}.activity-row[data-activity-status=failed].svelte-thyolt{border-inline-start:.25rem solid var(--theme-error);padding-inline-start:.375rem}.activity-row[data-activity-status=completed].svelte-thyolt{border-inline-start:.25rem solid var(--theme-success);padding-inline-start:.375rem}.activity-row[data-activity-status].svelte-thyolt:not([data-activity-status=completed]):not([data-activity-status=failed]){border-inline-start:.25rem solid var(--theme-info);padding-inline-start:.375rem}.activity-type.svelte-thyolt{color:var(--activity-type-color);font-size:.75em;line-height:1.2}.activity-content.svelte-thyolt{overflow-wrap:anywhere;white-space:pre-wrap;min-inline-size:0}form.svelte-1264sfg>[role=status]:where(.svelte-1264sfg){text-align:start;margin-block:1rem .35rem}.command-menu.svelte-1264sfg{background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:.375rem;gap:.125rem;max-inline-size:min(100%,32rem);margin:0 0 .35rem;padding:.25rem;list-style:none;display:grid}.command-menu.svelte-1264sfg button:where(.svelte-1264sfg){inline-size:100%;color:var(--theme-text);text-align:start;background:0 0;border:0;border-radius:.25rem;align-items:baseline;gap:.5rem;padding:.25rem .375rem;display:flex}.command-menu.svelte-1264sfg .command-selected:where(.svelte-1264sfg) button:where(.svelte-1264sfg),.command-menu.svelte-1264sfg button:where(.svelte-1264sfg):hover{background:var(--theme-control-hover)}.command-menu.svelte-1264sfg small:where(.svelte-1264sfg){color:var(--theme-text-muted);font-size:.8em}.command-menu.svelte-1264sfg button:where(.svelte-1264sfg):focus-visible{outline:2px solid var(--theme-focus);outline-offset:1px}.prompt-row.svelte-1264sfg{align-items:end;gap:.5rem;display:flex}textarea.svelte-1264sfg{resize:vertical;flex:auto;min-block-size:2.75rem}.prompt-row.svelte-1264sfg button:where(.svelte-1264sfg){flex:none;place-items:center;min-block-size:3rem;inline-size:3rem;padding:0;display:grid}.prompt-row.svelte-1264sfg svg:where(.svelte-1264sfg){fill:none;stroke:currentColor;stroke-width:2px;stroke-linecap:round;stroke-linejoin:round;block-size:1.35rem;inline-size:1.35rem}.block-cursor.svelte-1264sfg{vertical-align:-.1em;background:var(--theme-accent);block-size:1em;inline-size:.55ch;animation:1s steps(2,start) infinite svelte-1264sfg-blink;display:inline-block}@media (prefers-reduced-motion:reduce){.block-cursor.svelte-1264sfg{animation:none}}@keyframes svelte-1264sfg-blink{50%{opacity:0}}.quiz-form.svelte-77f3gb{gap:1rem;display:grid}fieldset.svelte-77f3gb{border:0;min-inline-size:0;margin:0;padding:0}.questions.svelte-77f3gb{gap:1rem;display:grid}legend.svelte-77f3gb{gap:.25rem;max-inline-size:100%;display:grid}.choices.svelte-77f3gb{gap:.5rem;margin-block-start:.5rem;display:grid}.choice.svelte-77f3gb{position:relative}.choice.svelte-77f3gb>input:where(.svelte-77f3gb){opacity:0;cursor:pointer;block-size:100%;inline-size:100%;margin:0;position:absolute;inset:0}.choice.svelte-77f3gb>label:where(.svelte-77f3gb){background:var(--theme-surface);border:2px solid var(--theme-border);border-radius:.5rem;gap:.125rem;min-block-size:44px;padding:.75rem;display:grid}.choice.svelte-77f3gb>input:where(.svelte-77f3gb):checked+label:where(.svelte-77f3gb){border-width:3px;border-color:var(--theme-accent);background:var(--theme-surface-subtle)}.choice.svelte-77f3gb>input:where(.svelte-77f3gb):focus-visible+label:where(.svelte-77f3gb){outline:3px solid var(--theme-focus);outline-offset:2px}label.svelte-77f3gb span:where(.svelte-77f3gb),legend.svelte-77f3gb span:where(.svelte-77f3gb){color:var(--theme-text-muted)}.custom.svelte-77f3gb{margin-block-start:.25rem}.custom.svelte-77f3gb input:where(.svelte-77f3gb){box-sizing:border-box;min-block-size:44px;inline-size:100%;font:inherit;margin-block-start:.375rem}article.svelte-ajsqcq{background:var(--theme-surface-subtle);border-inline-start:.25rem solid var(--theme-info);border-radius:.375rem;margin-block:.75rem;padding:.75rem}.interaction-kind.svelte-ajsqcq,.interaction-submitting.svelte-ajsqcq,.interaction-result.svelte-ajsqcq,.interaction-failed.svelte-ajsqcq{margin-block:0 .5rem}.interaction-result.svelte-ajsqcq{font-weight:600}.command-approval-command.svelte-ajsqcq{white-space:pre-wrap;max-inline-size:100%;font-family:var(--theme-font-code);background:var(--theme-code);border:1px solid var(--theme-border);border-radius:.375rem;margin-block:.5rem .75rem;padding:.625rem;overflow-x:auto}.command-approval-missing.svelte-ajsqcq{margin-block:.5rem .75rem}.file-change-approval-targets.svelte-ajsqcq{overflow-wrap:anywhere;margin-block:.5rem .75rem;padding-inline-start:1.5rem}.file-change-approval-missing.svelte-ajsqcq{margin-block:.5rem .75rem}.approval-actions.svelte-ajsqcq{flex-wrap:wrap;gap:clamp(.5rem,2vw,1rem);display:flex}ol.svelte-1im954t{inline-size:100%;margin:0;padding:0;list-style:none}li.svelte-1im954t{box-sizing:border-box;white-space:pre-wrap;overflow-wrap:anywhere;inline-size:100%;margin-block-end:0}li.svelte-1im954t+li:where(.svelte-1im954t){margin-block-start:1rem}.prompt-turn.svelte-1im954t{background:var(--theme-surface-subtle);border-inline-start:.25rem solid var(--theme-accent);border-radius:.375rem;padding:.5rem .625rem}.answer-turn.svelte-1im954t{border-inline-start:.25rem solid var(--theme-border);padding-inline-start:.625rem}.commentary-turn.svelte-1im954t,.orphan-activity-turn.svelte-1im954t,.progress-turn.svelte-1im954t,.commentary-content.svelte-1im954t{background:var(--theme-surface-subtle);border-inline-start:.25rem solid var(--theme-info);border-radius:.375rem;padding:.5rem .625rem}.live-commentary.svelte-1im954t,.progress-turn.svelte-1im954t{background:0 0}.entry-heading.svelte-1im954t{align-items:baseline;gap:.75rem;display:flex}time.svelte-1im954t{color:var(--theme-text-muted);white-space:nowrap;margin-inline-start:auto;font-size:.875em}.commentary-toggle.svelte-1im954t{min-block-size:1.5rem;color:var(--theme-text-muted);background:0 0;border:0;padding:0 .125rem;font-size:.875em}.answer-history.svelte-1im954t{flex-wrap:wrap;align-items:baseline;gap:.25rem .75rem;margin-block:.2rem .35rem;display:flex}.file-changes.svelte-1im954t{background:var(--theme-surface-subtle);border-inline-start:.25rem solid var(--theme-success);border-radius:.375rem;margin-block:.5rem;padding:.5rem .625rem}.file-changes.svelte-1im954t>strong:where(.svelte-1im954t){font-size:.875em}.file-changes.svelte-1im954t ul:where(.svelte-1im954t){gap:.15rem;margin:.25rem 0 0;padding-inline-start:1.25rem;display:grid}.commentary-content.svelte-1im954t{margin-block:.25rem .5rem}.entry-content.svelte-1im954t{margin-block:.125rem 0;margin-inline:0}pre.svelte-1im954t,code.svelte-1im954t{font-family:var(--theme-font-code)}pre.svelte-1im954t{white-space:pre;background:var(--theme-code);border:1px solid var(--theme-border);border-radius:var(--theme-radius);padding:.625rem;overflow-x:auto}.svelte-1im954t:not(pre)>code:where(.svelte-1im954t){background:var(--theme-code);border-radius:.2rem;padding-inline:.2em}a.svelte-1im954t{color:var(--theme-info)}.table-scroll.svelte-1im954t{margin-block:.75rem;overflow-x:auto}table.svelte-1im954t{border-collapse:collapse;white-space:normal;width:max-content;min-width:100%}th.svelte-1im954t,td.svelte-1im954t{text-align:left;vertical-align:top;border:1px solid var(--theme-border);padding:.25rem .5rem}th.svelte-1im954t{background:var(--theme-surface-subtle);font-weight:600}.toast-viewport.svelte-1a0rutm{z-index:10;pointer-events:none;gap:.5rem;max-block-size:calc(100dvh - 7rem);inline-size:min(24rem,100vw - 1.5rem);display:grid;position:fixed;inset-block-start:calc(1rem + env(safe-area-inset-top));inset-inline-end:max(.75rem, env(safe-area-inset-right));overflow-y:auto}.toast.svelte-1a0rutm{box-sizing:border-box;min-inline-size:0;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-info);box-shadow:0 .4rem 1.2rem var(--theme-shadow);pointer-events:auto;border-inline-start-width:.35rem;border-radius:.55rem;grid-template-columns:auto minmax(0,1fr) 44px;align-items:start;gap:.6rem;padding:.65rem;display:grid}.toast.error.svelte-1a0rutm{color:var(--theme-error);border-color:var(--theme-error);border-inline-start-style:double}.toast.success.svelte-1a0rutm{color:var(--theme-success);border-color:var(--theme-success);border-inline-start-style:solid}.toast-symbol.svelte-1a0rutm{box-sizing:border-box;border:2px solid;border-radius:50%;place-items:center;block-size:1.6rem;inline-size:1.6rem;font-weight:800;line-height:1;display:grid}.toast-copy.svelte-1a0rutm{overflow-wrap:anywhere;gap:.15rem;min-inline-size:0;padding-block:.1rem;display:grid}.toast-copy.svelte-1a0rutm strong:where(.svelte-1a0rutm){text-transform:capitalize}.toast-copy.svelte-1a0rutm small:where(.svelte-1a0rutm){opacity:.72}button.svelte-1a0rutm{min-block-size:44px;inline-size:44px;color:var(--theme-text);font:inherit;background:0 0;border:0;border-radius:.35rem;place-items:center;padding:0;font-size:1.5rem;display:grid}button.svelte-1a0rutm:hover{background:var(--theme-control-hover)}@media (width<=24rem){.toast.svelte-1a0rutm{grid-template-columns:minmax(0,1fr) 44px;gap:.25rem;max-block-size:5rem;padding:.35rem;overflow-y:auto}.toast-symbol.svelte-1a0rutm{display:none}.toast-copy.svelte-1a0rutm strong:where(.svelte-1a0rutm){clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;block-size:1px;inline-size:1px;padding:0;position:absolute;overflow:hidden}}@media (forced-colors:active){.toast.svelte-1a0rutm,.toast-symbol.svelte-1a0rutm{border-color:canvastext}}.evidence-page.svelte-1j55xyh{box-sizing:border-box;min-block-size:100dvh;inline-size:100%;padding:1rem 1rem 7rem}h1.svelte-1j55xyh{margin-block:0 .5rem}.evidence-composer.svelte-1j55xyh{position:fixed;inset-block-end:2.75rem;inset-inline:1rem}.evidence-prompt.svelte-1j55xyh{box-sizing:border-box;inline-size:100%;margin-block-start:.35rem;display:block}.evidence-nav.svelte-1j55xyh{padding:.5rem 1rem calc(.5rem + env(safe-area-inset-bottom));background:var(--theme-surface);border-block-start:1px solid var(--theme-border);grid-template-columns:repeat(3,minmax(0,1fr));gap:.5rem;display:grid;position:fixed;inset-block-end:0;inset-inline:0}.evidence-nav.svelte-1j55xyh button:where(.svelte-1j55xyh){text-overflow:ellipsis;min-block-size:44px;min-inline-size:0;overflow:hidden}@media (width<=24rem){.evidence-page.svelte-1j55xyh{padding:.5rem .5rem 7rem}.evidence-composer.svelte-1j55xyh{inset-inline:.5rem}}.filesystem-tree.svelte-6z3q4v{inline-size:100%;max-inline-size:100%;display:grid;overflow-x:clip}.tree-row.svelte-6z3q4v{min-inline-size:0;align-items:stretch;padding-inline-start:min(calc(var(--tree-level) * .75rem), 4.5rem);display:flex}.disclosure.svelte-6z3q4v,.disclosure-spacer.svelte-6z3q4v{flex:0 0 44px;min-block-size:44px;inline-size:44px}.disclosure.svelte-6z3q4v{color:inherit;font:inherit;background:0 0;border:0;border-radius:.35rem;place-items:center;padding:0;font-size:1.25rem;font-weight:700;display:grid}.disclosure.svelte-6z3q4v:hover{background:var(--theme-control-hover)}.tree-item.svelte-6z3q4v{min-block-size:44px;min-inline-size:0;color:inherit;font:inherit;text-align:start;background:0 0;border:1px solid #0000;border-radius:.35rem;flex:auto;align-items:center;gap:.5rem;padding:.3rem .5rem;display:flex;overflow:hidden}.tree-item.svelte-6z3q4v:hover{background:var(--theme-control-hover)}.tree-item.selected.svelte-6z3q4v{color:var(--theme-control-pressed-contrast);background:var(--theme-control-pressed);border-color:var(--theme-accent);font-weight:650}.tree-item[aria-disabled=true].svelte-6z3q4v{opacity:.58}.tree-item.repository.svelte-6z3q4v{border-inline-start-width:3px}.selection-mark.svelte-6z3q4v{text-align:center;flex:0 0 1rem;inline-size:1rem}.node-copy.svelte-6z3q4v{flex:auto;min-inline-size:0;display:grid}.node-name.svelte-6z3q4v,.node-path.svelte-6z3q4v{text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;overflow:hidden}.node-path.svelte-6z3q4v{opacity:.72;font-size:.75em;font-weight:400}.repository-badge.svelte-6z3q4v{border:1px solid;border-radius:999px;flex:none;padding:.1rem .35rem;font-size:.72em;font-weight:700;line-height:1.2}@media (width<=24rem){.tree-row.svelte-6z3q4v{padding-inline-start:min(calc(var(--tree-level) * .25rem), 1.5rem)}.tree-item.svelte-6z3q4v{gap:.35rem;padding-inline:.35rem}.repository-badge.svelte-6z3q4v{display:none}}@media (forced-colors:active){.tree-item.selected.svelte-6z3q4v,.tree-item.repository.svelte-6z3q4v,.repository-badge.svelte-6z3q4v{border-color:canvastext}}.evidence-shell.svelte-1d6w4ti{box-sizing:border-box;inline-size:min(100%,48rem);min-inline-size:0;margin-inline:auto;padding:1rem}.eyebrow.svelte-1d6w4ti{letter-spacing:.08em;text-transform:uppercase;margin:0;font-size:.8rem;font-weight:750}h1.svelte-1d6w4ti{margin-block:.25rem .5rem;font-size:clamp(1.35rem,6vw,2rem);line-height:1.15}.instructions.svelte-1d6w4ti{max-inline-size:42rem;margin-block:0 1rem}.tree-panel.svelte-1d6w4ti{border:1px solid var(--theme-border);border-radius:.6rem;min-inline-size:0;padding:.5rem}@media (width<=24rem){.evidence-shell.svelte-1d6w4ti{padding:.5rem}.tree-panel.svelte-1d6w4ti{padding:.25rem}}.git-view.svelte-pudr3t{gap:1.5rem;inline-size:100%;min-inline-size:0;display:grid}.git-tree.svelte-pudr3t,.repository-details.svelte-pudr3t{min-inline-size:0}.section-heading.svelte-pudr3t h3:where(.svelte-pudr3t),.repository-details.svelte-pudr3t h3:where(.svelte-pudr3t){margin-block:0 .35rem}.section-heading.svelte-pudr3t p:where(.svelte-pudr3t){color:var(--theme-text-muted);margin-block:0 .75rem}.clone-form.svelte-pudr3t{gap:.35rem;min-inline-size:0;display:grid}.clone-controls.svelte-pudr3t{grid-template-columns:minmax(10rem,.45fr) minmax(0,1fr) auto;align-items:end;gap:.5rem;display:grid}.clone-field.svelte-pudr3t{gap:.35rem;min-inline-size:0;display:grid}.field-label.svelte-pudr3t{font-weight:600}.clone-destination.svelte-pudr3t output:where(.svelte-pudr3t){overflow-wrap:anywhere;border:1px solid var(--theme-border);border-radius:.35rem;min-block-size:2.75rem;padding:.65rem .75rem}.clone-controls.svelte-pudr3t button:where(.svelte-pudr3t){min-inline-size:5.75rem}.clone-form.svelte-pudr3t p:where(.svelte-pudr3t){color:var(--theme-text-muted);margin:0;font-size:.875rem}.clone-status.svelte-pudr3t{font-weight:600;color:var(--theme-info)!important}.git-overview.svelte-pudr3t{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:1rem;display:grid}.git-status.svelte-pudr3t p:where(.svelte-pudr3t){margin-block:.5rem}.git-actions.svelte-pudr3t{gap:.5rem;display:grid}.git-actions.svelte-pudr3t button:where(.svelte-pudr3t){min-inline-size:6rem}.commit-history.svelte-pudr3t{margin-block-start:2rem}.commit-history.svelte-pudr3t h3:where(.svelte-pudr3t){margin-block-end:.75rem}.commit-list.svelte-pudr3t{gap:.5rem;margin:0;padding:0;list-style:none;display:grid}.commit-entry.svelte-pudr3t{border-block-end:1px solid var(--theme-border);gap:.35rem;min-inline-size:0;padding:.8rem 0;display:grid}.commit-subject.svelte-pudr3t,.commit-meta.svelte-pudr3t{gap:.65rem;min-inline-size:0;display:flex}.commit-subject.svelte-pudr3t{overflow-wrap:anywhere;align-items:baseline;font-weight:600}.commit-subject.svelte-pudr3t code:where(.svelte-pudr3t){color:var(--theme-text-muted);flex:none;font-size:.875em}.commit-meta.svelte-pudr3t{color:var(--theme-text-muted);flex-wrap:wrap;justify-content:space-between;font-size:.875rem}.commit-meta.svelte-pudr3t time:where(.svelte-pudr3t){overflow-wrap:anywhere;min-inline-size:0;max-inline-size:100%}@media (width<=28rem){.clone-controls.svelte-pudr3t{grid-template-columns:1fr}.clone-controls.svelte-pudr3t button:where(.svelte-pudr3t){inline-size:100%}}.plan.svelte-3rmyvg{overflow-wrap:anywhere;min-inline-size:0}header.svelte-3rmyvg{justify-content:space-between;align-items:start;gap:1rem;display:flex}h2.svelte-3rmyvg,p.svelte-3rmyvg{margin-block:.4rem}progress.svelte-3rmyvg{inline-size:100%}ol.svelte-3rmyvg{padding-inline-start:1.25rem}details.svelte-3rmyvg{margin-block:.5rem}summary.svelte-3rmyvg{cursor:pointer;gap:.2rem;display:grid}.measurement.svelte-3rmyvg{color:var(--muted,currentColor);font-variant-numeric:tabular-nums}.metadata.svelte-3rmyvg{gap:.25rem;display:grid}.metadata.svelte-3rmyvg div:where(.svelte-3rmyvg){gap:.5rem;display:flex}.metadata.svelte-3rmyvg dd:where(.svelte-3rmyvg){margin:0}.close.svelte-3rmyvg{flex:none;block-size:2.75rem;inline-size:2.75rem;font-size:1.5rem}button.svelte-3rmyvg:focus-visible,summary.svelte-3rmyvg:focus-visible{outline-offset:2px;outline:3px solid}@media (prefers-reduced-motion:reduce){.svelte-3rmyvg{scroll-behavior:auto}}.plans.svelte-x2ko9l{overflow-wrap:anywhere;min-inline-size:0}h2.svelte-x2ko9l{margin-block:.4rem}ul.svelte-x2ko9l{gap:.5rem;margin:0;padding:0;list-style:none;display:grid}button.svelte-x2ko9l{text-align:start;gap:.2rem;min-block-size:2.75rem;inline-size:100%;padding:.75rem;display:grid}button.svelte-x2ko9l:focus-visible{outline-offset:2px;outline:3px solid}.session-list.svelte-hxb22h{gap:1rem;inline-size:100%;margin-block:0 1.5rem;padding-inline:0;list-style:none;display:grid}.current-session.svelte-hxb22h{outline:2px solid var(--theme-accent);outline-offset:2px}.session-select.svelte-hxb22h{inline-size:100%;color:inherit;font:inherit;text-align:inherit;cursor:pointer;background:0 0;border:0;padding:0;display:block}.managed-session.svelte-hxb22h{box-sizing:border-box;border:1px solid var(--theme-border);border-radius:.5rem;grid-template-columns:max-content minmax(0,1fr);align-items:start;gap:.75rem;inline-size:100%;padding:.75rem;display:grid}.open-session.svelte-hxb22h{border-inline-start:.3rem solid var(--theme-accent);background:var(--theme-control-hover)}.session-details.svelte-hxb22h{min-inline-size:0}.workspace-path.svelte-hxb22h{overflow-wrap:anywhere}.profile-badge.svelte-hxb22h{background:var(--theme-control-pressed);color:var(--theme-control-pressed-contrast);overflow-wrap:anywhere;border-radius:999px;margin-block-start:.35rem;padding:.15rem .4rem;font-size:.875rem;display:inline-block}.org-plan-metadata.svelte-hxb22h{gap:.125rem;margin-block-start:.35rem;display:grid}.org-plan-title.svelte-hxb22h{font-weight:600}.org-plan-filename.svelte-hxb22h{overflow-wrap:anywhere;color:var(--theme-text-muted)}.session-actions.svelte-hxb22h{flex-wrap:wrap;gap:.5rem;display:flex}@media (width<=30rem){.managed-session.svelte-hxb22h{grid-template-columns:minmax(0,1fr)}}.recent-session.svelte-hxb22h{grid-template-columns:minmax(0,1fr) max-content;align-items:start;gap:.75rem;display:grid}.session-base.svelte-hxb22h{gap:.65rem;min-inline-size:0;margin-block:1rem;display:grid}.session-base-heading.svelte-hxb22h h3:where(.svelte-hxb22h),.session-base-heading.svelte-hxb22h p:where(.svelte-hxb22h){margin:0}.session-base-heading.svelte-hxb22h p:where(.svelte-hxb22h){margin-block-start:.25rem}.tree-panel.svelte-hxb22h{box-sizing:border-box;overscroll-behavior:contain;scrollbar-gutter:stable;border:1px solid var(--theme-border);border-radius:.6rem;max-block-size:min(22rem,48vh);min-inline-size:0;padding:.35rem;overflow-y:auto}.session-settings.svelte-hxb22h{gap:.5rem;min-inline-size:0;margin-block-end:.75rem;display:grid}.session-setting-labels.svelte-hxb22h,.session-setting-controls.svelte-hxb22h{grid-template-columns:repeat(3,minmax(0,1fr));gap:.5rem;min-inline-size:0;display:grid}.session-setting-labels.svelte-hxb22h label:where(.svelte-hxb22h){overflow-wrap:anywhere;min-inline-size:0}.session-setting-controls.svelte-hxb22h select:where(.svelte-hxb22h){text-overflow:ellipsis;inline-size:100%;min-inline-size:0;max-inline-size:100%;overflow:hidden}.session-secondary-actions.svelte-hxb22h,.model-control.svelte-hxb22h{align-items:center;gap:.5rem;min-inline-size:0;display:flex}.session-secondary-actions.svelte-hxb22h{flex-wrap:wrap}.model-control.svelte-hxb22h{margin-inline-start:auto}.skills-profile-error.svelte-hxb22h{color:var(--theme-error);margin:0;font-size:.875rem}.new-session-button.svelte-hxb22h{inline-size:auto;color:var(--theme-accent-contrast);background:var(--theme-accent);border-color:var(--theme-accent);box-shadow:inset 0 .15rem 0 var(--theme-accent-contrast);font-weight:700}@media (width<=28rem){.tree-panel.svelte-hxb22h{padding:.2rem}.model-control.svelte-hxb22h{margin-inline-start:0}}.skills-view.svelte-dg6sjj{min-inline-size:0;padding:1rem max(1rem, env(safe-area-inset-right)) calc(5rem + env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left))}.intro.svelte-dg6sjj,.save-intent.svelte-dg6sjj,.summary.svelte-dg6sjj{max-inline-size:70ch}form.svelte-dg6sjj,.field-grid.svelte-dg6sjj{gap:.65rem;display:grid}.field-grid.svelte-dg6sjj{grid-template-columns:minmax(0,1fr)}label.svelte-dg6sjj{font-weight:650}select.svelte-dg6sjj,input.svelte-dg6sjj,button.svelte-dg6sjj{box-sizing:border-box;min-block-size:3rem;inline-size:100%;font:inherit}dialog.svelte-dg6sjj{max-inline-size:min(100% - 2rem,32rem)}.dialog-actions.svelte-dg6sjj{gap:.65rem;display:grid}input.svelte-dg6sjj,select.svelte-dg6sjj{padding-inline:.7rem;font-size:1rem}button.svelte-dg6sjj{padding-inline:1rem}.profile-actions.svelte-dg6sjj{grid-template-columns:repeat(2,minmax(0,1fr));gap:.65rem;display:grid}.skills-toolbar.svelte-dg6sjj{flex-wrap:wrap;align-items:center;gap:.65rem;margin-block:1rem;display:flex}.refresh-skills.svelte-dg6sjj{flex:none;min-block-size:2.25rem;inline-size:auto;padding-inline:.65rem}.refresh-skills.svelte-dg6sjj span:where(.svelte-dg6sjj):first-child{font-size:1.2em;line-height:1}.summary.svelte-dg6sjj{overflow-wrap:anywhere;min-inline-size:0;margin:0}.notice.svelte-dg6sjj{border-inline-start:.3rem solid var(--theme-warning);color:var(--theme-warning);padding-inline-start:.7rem}.error.svelte-dg6sjj{border-inline-start:.3rem solid var(--theme-error);color:var(--theme-error);padding-inline-start:.7rem}.skill-list.svelte-dg6sjj{gap:.75rem;padding:0;list-style:none;display:grid}.skill-card.svelte-dg6sjj{border:1px solid var(--theme-border);border-radius:.5rem;min-inline-size:0;padding:.75rem;scroll-margin-block:1rem}.skill-toggle.svelte-dg6sjj{flex-wrap:wrap;align-items:center;gap:.65rem;min-block-size:3rem;display:flex}.skill-toggle.svelte-dg6sjj>span:where(.svelte-dg6sjj){overflow-wrap:anywhere;min-inline-size:0}.skill-toggle.svelte-dg6sjj input:where(.svelte-dg6sjj){flex:none;min-block-size:1.25rem;inline-size:1.25rem}.state.svelte-dg6sjj{margin-inline-start:auto;font-weight:500}.skill-details.svelte-dg6sjj{overflow-wrap:anywhere;padding-inline-start:1.9rem}.skill-details.svelte-dg6sjj>:where(.svelte-dg6sjj):first-child{margin-block-start:.25rem}.skill-details.svelte-dg6sjj>:where(.svelte-dg6sjj):last-child{margin-block-end:0}dl.svelte-dg6sjj{grid-template-columns:minmax(7rem,auto) minmax(0,1fr);gap:.45rem .75rem;display:grid}dt.svelte-dg6sjj{font-weight:650}dd.svelte-dg6sjj{min-inline-size:0;margin:0}dd.svelte-dg6sjj ul:where(.svelte-dg6sjj){margin:0;padding-inline-start:1.25rem}.path.svelte-dg6sjj{overflow-wrap:anywhere}.svelte-dg6sjj:where(button:where(.svelte-dg6sjj),input:where(.svelte-dg6sjj),select:where(.svelte-dg6sjj)):focus-visible{outline:3px solid var(--theme-focus);outline-offset:2px}@media (width>=42rem){.field-grid.svelte-dg6sjj{grid-template-columns:minmax(10rem,1fr) minmax(0,2fr);align-items:center}}@media (width<=30rem){dl.svelte-dg6sjj{grid-template-columns:1fr;gap:.2rem}}.devices.svelte-1nygetn{max-inline-size:48rem;color:var(--theme-text);margin:auto;padding:1rem}.devices.svelte-1nygetn header:where(.svelte-1nygetn){align-items:center;gap:1rem;display:flex}.devices.svelte-1nygetn h1:where(.svelte-1nygetn),.devices.svelte-1nygetn h2:where(.svelte-1nygetn){font-family:var(--theme-font-display)}.devices.svelte-1nygetn ul:where(.svelte-1nygetn){padding:0;list-style:none}.devices.svelte-1nygetn li:where(.svelte-1nygetn),section.svelte-1nygetn{background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .25rem .75rem var(--theme-shadow);margin-block:1rem;padding:1rem}.devices.svelte-1nygetn form:where(.svelte-1nygetn){gap:.65rem;display:grid}.devices.svelte-1nygetn p:where(.svelte-1nygetn){overflow-wrap:anywhere;margin:0}.devices.svelte-1nygetn input:where(.svelte-1nygetn),.devices.svelte-1nygetn textarea:where(.svelte-1nygetn),.devices.svelte-1nygetn button:where(.svelte-1nygetn){min-block-size:44px;font:inherit}.devices.svelte-1nygetn input:where(.svelte-1nygetn),.devices.svelte-1nygetn textarea:where(.svelte-1nygetn){min-inline-size:0;padding:.45rem}.devices.svelte-1nygetn textarea:where(.svelte-1nygetn){box-sizing:border-box;overflow-wrap:anywhere;resize:vertical;inline-size:100%;font-family:var(--theme-font-code)}.devices.svelte-1nygetn .actions:where(.svelte-1nygetn){flex-wrap:wrap;gap:.5rem;display:flex}.devices.svelte-1nygetn .actions:where(.svelte-1nygetn) button:where(.svelte-1nygetn){flex:11rem}.devices.svelte-1nygetn img:where(.svelte-1nygetn){background:var(--theme-canvas);inline-size:12rem;max-inline-size:100%;margin-block:.75rem;padding:.5rem;display:block}.feedback.svelte-1nygetn{color:var(--theme-error);font-weight:700}dialog.svelte-1nygetn{max-inline-size:min(32rem,100% - 2rem);color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .5rem 1.5rem var(--theme-shadow)}dialog.svelte-1nygetn h2:where(.svelte-1nygetn){font-family:var(--theme-font-display)}dialog.svelte-1nygetn form:where(.svelte-1nygetn){flex-wrap:wrap;gap:.5rem;display:flex}@media (forced-colors:active){.devices.svelte-1nygetn li:where(.svelte-1nygetn),section.svelte-1nygetn,dialog.svelte-1nygetn{border-color:canvastext}}@media (prefers-reduced-motion:reduce){.svelte-1nygetn{transition-duration:.01ms!important;animation-duration:.01ms!important}}dialog.svelte-eu5bnq{box-sizing:border-box;max-block-size:calc(100dvh - 1rem);inline-size:min(52rem,100vw - 1rem);max-inline-size:none;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .75rem 2rem var(--theme-shadow);padding:0}dialog.svelte-eu5bnq::backdrop{background:color-mix(in srgb, var(--theme-canvas) 70%, transparent)}.scratchpad-shell.svelte-eu5bnq{box-sizing:border-box;min-block-size:min(42rem,100dvh - 1rem);max-block-size:calc(100dvh - 1rem);padding:max(1rem, env(safe-area-inset-top)) max(1rem, env(safe-area-inset-right)) max(1rem, env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left));grid-template-rows:auto auto minmax(12rem,1fr) auto;gap:.75rem;display:grid}header.svelte-eu5bnq{justify-content:space-between;align-items:flex-start;gap:1rem;display:flex}h2.svelte-eu5bnq,p.svelte-eu5bnq{margin:0}h2.svelte-eu5bnq{font-family:var(--theme-font-display)}header.svelte-eu5bnq p:where(.svelte-eu5bnq){color:var(--theme-text-muted)}label.svelte-eu5bnq{font-weight:700}textarea.svelte-eu5bnq{box-sizing:border-box;resize:none;min-block-size:12rem;inline-size:100%;min-inline-size:0;color:var(--theme-text);background:var(--theme-canvas);border:1px solid var(--theme-border);border-radius:calc(var(--theme-radius) / 2);font:1rem/1.45 var(--theme-font-code);tab-size:2;padding:.75rem}button.svelte-eu5bnq{min-block-size:44px;font:inherit}.close.svelte-eu5bnq{inline-size:44px;color:inherit;background:0 0;border:0;flex:none;padding:0;font-size:2rem;line-height:1}.actions.svelte-eu5bnq{flex-wrap:wrap;gap:.5rem;display:flex}.actions.svelte-eu5bnq button:where(.svelte-eu5bnq){flex:7rem}.danger.svelte-eu5bnq{color:var(--theme-error)}.clear-confirmation.svelte-eu5bnq{border:1px solid var(--theme-error);border-radius:calc(var(--theme-radius) / 2);gap:.5rem;padding:.75rem;display:grid}.feedback.svelte-eu5bnq{color:var(--theme-error);font-weight:700}@media (width<=32rem){dialog.svelte-eu5bnq{border-inline:0;border-radius:0;max-block-size:100dvh;inline-size:100vw}.scratchpad-shell.svelte-eu5bnq{min-block-size:100dvh;max-block-size:100dvh}}@media (forced-colors:active){dialog.svelte-eu5bnq,textarea.svelte-eu5bnq,.clear-confirmation.svelte-eu5bnq{border-color:canvastext}}.chat-view.svelte-1d2e8zz{margin-inline:calc(.25rem - var(--page-inline-padding))}.chat-tail.svelte-1d2e8zz{block-size:1px;scroll-margin-block-end:var(--bottom-navigation-clearance)}.swipe-surface.svelte-1d2e8zz{touch-action:pan-y}.evidence-mode.svelte-1d2e8zz{box-sizing:border-box;inline-size:100%;min-inline-size:0;padding:0}.session-profile-manager-header.svelte-1d2e8zz{box-sizing:border-box;inline-size:100%;min-inline-size:0;padding-inline:max(1rem, env(safe-area-inset-left)) max(1rem, env(safe-area-inset-right));justify-content:space-between;align-items:safe center;gap:1rem;display:flex}.session-profile-manager-header.svelte-1d2e8zz h2:where(.svelte-1d2e8zz){overflow-wrap:anywhere;min-inline-size:0;margin:0}.close-profile-manager.svelte-1d2e8zz{min-block-size:2.5rem;inline-size:2.5rem;color:inherit;background:0 0;border:0;flex:none;padding:0;font-size:2rem;line-height:1}.enrollment.svelte-1c5qcm6{box-sizing:border-box;inline-size:100%;max-inline-size:none;background:var(--theme-page);min-height:100dvh;color:var(--theme-text);place-items:center;margin:0;padding:1rem;display:grid}.card.svelte-1c5qcm6{box-sizing:border-box;background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:calc(var(--theme-radius) * 2);width:min(100%,42rem);box-shadow:0 .5rem 1.5rem var(--theme-shadow);padding:clamp(1.25rem,5vw,3rem)}.brand.svelte-1c5qcm6{color:var(--theme-info);font-family:var(--theme-font-display);letter-spacing:.08em;text-transform:uppercase;font-weight:700}h1.svelte-1c5qcm6{font-family:var(--theme-font-display);margin-block:.25rem 1rem;font-size:clamp(1.8rem,7vw,3rem)}.warning.svelte-1c5qcm6{border-left:.3rem solid var(--theme-warning);background:var(--theme-surface-subtle);padding:1rem}label.svelte-1c5qcm6,input.svelte-1c5qcm6{box-sizing:border-box;width:100%;display:block}label.svelte-1c5qcm6{margin-top:1.5rem;font-weight:700}input.svelte-1c5qcm6,button.svelte-1c5qcm6{min-height:44px;font:inherit}input.svelte-1c5qcm6{margin-top:.4rem;padding:.6rem}button.svelte-1c5qcm6{cursor:pointer;border-radius:.4rem;margin-top:1rem;padding:.55rem 1rem}.authorize.svelte-1c5qcm6{background:var(--theme-accent);width:100%;color:var(--theme-accent-contrast);border-color:var(--theme-accent);font-weight:700}.feedback.svelte-1c5qcm6{color:var(--theme-info);font-weight:700}.error.svelte-1c5qcm6{color:var(--theme-error);font-weight:700}.handoff.svelte-1c5qcm6{border-top:1px solid var(--theme-border);grid-template-columns:minmax(0,1fr);gap:1rem;margin-top:2rem;padding-top:1.5rem;display:grid}.handoff.svelte-1c5qcm6 img:where(.svelte-1c5qcm6){background:var(--theme-canvas);width:12rem;max-width:100%;padding:.5rem}a.svelte-1c5qcm6{color:var(--theme-info);overflow-wrap:anywhere}.copy.svelte-1c5qcm6{display:block}@media (width>=48rem){.handoff.svelte-1c5qcm6{grid-template-columns:12rem minmax(0,1fr);align-items:start}}@media (prefers-reduced-motion:reduce){.svelte-1c5qcm6,.svelte-1c5qcm6:before,.svelte-1c5qcm6:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}.login.svelte-1byekj9{box-sizing:border-box;inline-size:100%;max-inline-size:none;background:var(--theme-page);min-height:100dvh;color:var(--theme-text);place-items:center;margin:0;padding:1rem;display:grid}.card.svelte-1byekj9{box-sizing:border-box;background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:calc(var(--theme-radius) * 2);width:min(100%,32rem);box-shadow:0 .5rem 1.5rem var(--theme-shadow);padding:clamp(1.25rem,5vw,3rem)}.brand.svelte-1byekj9{color:var(--theme-info);font-family:var(--theme-font-display);letter-spacing:.08em;text-transform:uppercase;font-weight:700}h1.svelte-1byekj9{font-family:var(--theme-font-display);margin-block:.25rem 1rem;font-size:clamp(1.8rem,7vw,3rem)}.sign-in.svelte-1byekj9{border-radius:var(--theme-radius);background:var(--theme-accent);width:100%;min-height:44px;color:var(--theme-accent-contrast);border-color:var(--theme-accent);font:inherit;margin-top:1rem;padding:.55rem 1rem;font-weight:700}.error.svelte-1byekj9{color:var(--theme-error);font-weight:700}@media (prefers-reduced-motion:reduce){.svelte-1byekj9,.svelte-1byekj9:before,.svelte-1byekj9:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}.auth-gate.svelte-y74n0s{max-width:32rem;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .5rem 1.5rem var(--theme-shadow);margin:12vh auto;padding:2rem}.auth-gate.svelte-y74n0s h1:where(.svelte-y74n0s){font-family:var(--theme-font-display)}button.svelte-y74n0s{margin-right:.75rem}@font-face{font-family:Syne;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/syne-greek-600-normal-BpFII5Ja.woff2)format("woff2"),url(/assets/syne-greek-600-normal-BJs4kvyH.woff)format("woff");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Syne;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/syne-latin-ext-600-normal-ZnizrDKU.woff2)format("woff2"),url(/assets/syne-latin-ext-600-normal-DEzBMW8B.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Syne;font-style:normal;font-display:swap;font-weight:600;src:url(/assets/syne-latin-600-normal-BiwQbQXw.woff2)format("woff2"),url(/assets/syne-latin-600-normal-dRu9QuIh.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-cyrillic-ext-400-normal-C_uLvvQ5.woff2)format("woff2"),url(/assets/jetbrains-mono-cyrillic-ext-400-normal-Bh0R7Dhr.woff)format("woff");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-cyrillic-400-normal-BEIGL1Tu.woff2)format("woff2"),url(/assets/jetbrains-mono-cyrillic-400-normal-ugxPyKxw.woff)format("woff");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-greek-400-normal-C190GLew.woff2)format("woff2"),url(/assets/jetbrains-mono-greek-400-normal-B9oWc5Lo.woff)format("woff");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-vietnamese-400-normal-ByoDsISC.woff2)format("woff2"),url(/assets/jetbrains-mono-vietnamese-400-normal-CqNFfHCs.woff)format("woff");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-latin-ext-400-normal-Bc8Ftmh3.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-ext-400-normal-fXTG6kC5.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:JetBrains Mono;font-style:normal;font-display:swap;font-weight:400;src:url(/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2)format("woff2"),url(/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--theme-font-body:system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--theme-font-display:"Syne", var(--theme-font-body);--theme-font-code:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;--theme-motion-fast:.12s;--theme-radius:.5rem}:root[data-theme=minimal-light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--theme-page:#fff;--theme-canvas:#fff;--theme-surface:#fff;--theme-surface-subtle:#f3f4f6;--theme-text:#171717;--theme-text-muted:#525252;--theme-border:#525252;--theme-focus:#005fcc;--theme-accent:#171717;--theme-accent-contrast:#fff;--theme-control-hover:#e5e7eb;--theme-control-pressed:#262626;--theme-control-pressed-contrast:#fff;--theme-control-disabled:#737373;--theme-success:#166534;--theme-warning:#854d0e;--theme-error:#b91c1c;--theme-info:#1d4ed8;--theme-code:#f3f4f6;--theme-shadow:#17171740}:root[data-theme=minimal-dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--theme-page:#171717;--theme-canvas:#171717;--theme-surface:#262626;--theme-surface-subtle:#404040;--theme-text:#fafafa;--theme-text-muted:#d4d4d4;--theme-border:#a3a3a3;--theme-focus:#93c5fd;--theme-accent:#fafafa;--theme-accent-contrast:#171717;--theme-control-hover:#404040;--theme-control-pressed:#fafafa;--theme-control-pressed-contrast:#171717;--theme-control-disabled:#a3a3a3;--theme-success:#86efac;--theme-warning:#fcd34d;--theme-error:#fca5a5;--theme-info:#93c5fd;--theme-code:#262626;--theme-shadow:#00000080}:root[data-theme=dyne-org]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--theme-page:#f4f0e8;--theme-canvas:#fffdf8;--theme-surface:#fffaf1;--theme-surface-subtle:#eee6d8;--theme-text:#171411;--theme-text-muted:#5f564c;--theme-border:#75695b;--theme-focus:#5e2ca5;--theme-accent:#c7460c;--theme-accent-contrast:#fffdf8;--theme-control-hover:#f4ddcf;--theme-control-pressed:#bd3d0d;--theme-control-pressed-contrast:#fffdf8;--theme-control-disabled:#93887b;--theme-success:#287a50;--theme-warning:#9a5a00;--theme-error:#b42318;--theme-info:#5e2ca5;--theme-code:#f1e9db;--theme-shadow:#17141138}:root{--bottom-navigation-clearance:calc(4rem + env(safe-area-inset-bottom));--sticky-header-clearance:calc(5rem + env(safe-area-inset-top));--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;font-family:var(--theme-font-body)}body{min-block-size:100dvh;color:var(--theme-text);background:var(--theme-page);accent-color:var(--theme-accent);margin:0}.visually-hidden{clip-path:inset(50%)!important;white-space:nowrap!important;border:0!important;block-size:1px!important;inline-size:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}main{--page-inline-padding:1rem;box-sizing:border-box;min-block-size:100dvh;max-inline-size:42rem;padding:calc(1rem + env(safe-area-inset-top)) var(--page-inline-padding) calc(var(--bottom-navigation-clearance) + 1rem);margin-inline:auto}@media (width<=24rem){main{--page-inline-padding:.5rem}}.app-header{z-index:1;margin:calc(-1rem - env(safe-area-inset-top)) calc(.25rem - var(--page-inline-padding)) 1.5rem;padding:calc(1rem + env(safe-area-inset-top)) calc(var(--page-inline-padding) - .25rem) .75rem;background:color-mix(in srgb, var(--theme-page) 78%, transparent);-webkit-backdrop-filter:blur(.75rem);backdrop-filter:blur(.75rem);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:1rem;display:flex;position:sticky;inset-block-start:0}.app-header [role=status]{overflow-wrap:anywhere;text-align:end;min-inline-size:0;margin:0}.brand{align-items:center;gap:.4rem;display:flex}.header-actions{flex:none;align-items:center;gap:.25rem;margin-inline-start:auto;display:flex}.weekly-quota{font-variant-numeric:tabular-nums;white-space:nowrap;font-size:.875rem}.menu-trigger{min-block-size:44px;inline-size:44px;color:inherit;background:0 0;border:0;flex:none;place-items:center;padding:0;display:grid}.menu-lines{gap:.25rem;inline-size:1.25rem;display:grid}.menu-lines span{box-sizing:border-box;background:currentColor;block-size:1px;display:block}.menu-lines span:first-child,.menu-lines span:nth-child(2),.menu-lines span:last-child{block-size:2px}@media (width<=28rem){.app-header [role=status]{flex-basis:100%;margin-inline-start:0}}.brand-icon{block-size:2rem;inline-size:2rem}.brand-logotype{block-size:auto;inline-size:6.25rem}.dark-asset,:root[data-logo-tone=light] .light-asset{display:none}:root[data-logo-tone=light] .dark-asset{display:initial}.configuration-panel{box-sizing:border-box;overscroll-behavior:contain;max-block-size:calc(100dvh - 5.5rem);min-inline-size:min(13rem,100vw - 2rem);max-inline-size:calc(100vw - 2rem);color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);box-shadow:0 .5rem 1.5rem var(--theme-shadow);padding:.75rem;position:fixed;inset:4.5rem 1rem auto auto;overflow-y:auto}.configuration-logo{block-size:auto;inline-size:4.5rem;margin-block-end:.5rem;margin-inline-start:auto;display:block}.appearance-control{align-items:center;gap:.5rem;min-inline-size:0;display:flex}.appearance-control span{font-family:var(--theme-font-display);letter-spacing:-.08em;font-weight:700}.appearance-control select{field-sizing:content;flex:auto;min-block-size:2.25rem;inline-size:auto;min-inline-size:0;max-inline-size:100%;padding-inline:.5rem}.bottom-navigation{padding:.5rem calc(.25rem + env(safe-area-inset-right)) calc(.5rem + env(safe-area-inset-bottom)) calc(.25rem + env(safe-area-inset-left));background:var(--theme-surface);border-block-start:1px solid var(--theme-border);grid-template-columns:repeat(4,minmax(0,1fr));gap:.25rem;display:grid;position:fixed;inset-block-end:0;inset-inline:0}.bottom-navigation button{overflow-wrap:anywhere;word-break:break-word;min-inline-size:0;max-inline-size:100%;transition:color var(--theme-motion-fast) ease, background-color var(--theme-motion-fast) ease, box-shadow var(--theme-motion-fast) ease;padding-inline:0}.bottom-navigation button:hover:not(:disabled){background:var(--theme-control-hover)}.bottom-navigation button:active:not(:disabled){background:var(--theme-control-pressed);color:var(--theme-control-pressed-contrast)}.bottom-navigation button[aria-pressed=true]{color:var(--theme-accent-contrast);background:var(--theme-accent);box-shadow:inset 0 .2rem 0 color-mix(in srgb, var(--theme-accent-contrast) 25%, transparent);font-weight:700}.bottom-navigation button:disabled{color:var(--theme-control-disabled);cursor:not-allowed;opacity:.65}@media (width<=30rem){:root{--bottom-navigation-clearance:calc(10rem + env(safe-area-inset-bottom));--sticky-header-clearance:calc(14rem + env(safe-area-inset-top))}.bottom-navigation{grid-template-columns:repeat(2,minmax(0,1fr))}.bottom-navigation button{text-align:center;padding-inline:.25rem}}button{min-block-size:44px;font:inherit;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);cursor:pointer}button:hover:not(:disabled){background:var(--theme-control-hover)}button:active:not(:disabled){background:var(--theme-control-pressed);color:var(--theme-control-pressed-contrast)}button:disabled{color:var(--theme-control-disabled);cursor:not-allowed}.git-view button{scroll-margin-block:var(--sticky-header-clearance) var(--bottom-navigation-clearance)}input,select,textarea{box-sizing:border-box;min-block-size:44px;inline-size:100%;font:inherit;color:var(--theme-text);background:var(--theme-surface);border:1px solid var(--theme-border);border-radius:var(--theme-radius);font-size:max(1rem,16px)}input::placeholder,textarea::placeholder{color:var(--theme-text-muted)}:focus-visible{outline:3px solid var(--theme-focus);outline-offset:3px}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}@media (forced-colors:active){button,input,select,textarea{border:1px solid canvastext}}
@@ -16,8 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-or-later
16
16
  <link rel="icon" href="/icons/gestalt-mobile-192.png" />
17
17
  <link rel="apple-touch-icon" href="/icons/gestalt-mobile-180.png" />
18
18
  <title>Gestalt Mobile</title>
19
- <script type="module" crossorigin src="/assets/index-Dnm_kS5p.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-2L2KZzwK.css">
19
+ <script type="module" crossorigin src="/assets/index-BgJS9DQH.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-BotpWcLh.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="app"></div>
@@ -112,22 +112,33 @@ export async function composeRelayApp(options) {
112
112
  const runtime = options.startAppServers
113
113
  ? new CodexSessionRuntime(options.launchAppServer ?? launchCodexAppServer, undefined, (sessionId, notification) => {
114
114
  const occurredAt = new Date().toISOString();
115
- const normalized = normalizeCodexNotification(sessionId, 0, occurredAt, notification, sessions.find(sessionId)?.workspacePath);
115
+ const currentSession = sessions.find(sessionId);
116
+ const normalized = normalizeCodexNotification(sessionId, 0, occurredAt, notification, currentSession?.workspacePath, currentSession?.activeTurnId);
116
117
  if (!normalized)
117
118
  return;
119
+ let completedSession;
118
120
  if (normalized.type === 'turnCompleted') {
119
121
  const turnId = normalized.payload.turn?.id;
120
122
  const session = sessions.find(sessionId);
121
- if (session && turnId && session.activeTurnId === turnId)
122
- sessions.save(RelaySession.rehydrate(session).completeTurn(turnId, occurredAt).snapshot);
123
+ if (session && turnId && session.activeTurnId === turnId) {
124
+ completedSession = RelaySession.rehydrate(session).completeTurn(turnId, occurredAt).snapshot;
125
+ sessions.save(completedSession);
126
+ }
123
127
  planMeasurementRefresh?.refreshNow(sessionId);
124
128
  }
125
129
  events.publish(journal.append(sessionId, normalized.type, normalized.payload, normalized.occurredAt));
130
+ if (completedSession)
131
+ events.publish(journal.append(sessionId, 'session.updated', completedSession, occurredAt));
126
132
  }, (sessionId, request) => {
127
- const interaction = toPendingInteraction(request);
133
+ const rawInteraction = toPendingInteraction(request);
128
134
  const session = withPendingInteractions(sessions.find(sessionId));
129
- if (!interaction || !session)
135
+ if (!rawInteraction || !session)
130
136
  return false;
137
+ const interaction = {
138
+ ...rawInteraction,
139
+ turnId: session.activeTurnId,
140
+ requestedAt: new Date().toISOString(),
141
+ };
131
142
  interactions.add(sessionId, interaction);
132
143
  const updated = RelaySession.rehydrate(session).requestInteraction(interaction, new Date().toISOString()).snapshot;
133
144
  sessions.save(updated);
@@ -305,8 +316,11 @@ export async function composeRelayApp(options) {
305
316
  replyInteraction: runtime
306
317
  ? (sessionId, requestId, value) => runtime.resolveServerRequest(sessionId, requestId, value)
307
318
  : undefined,
308
- interactionResolved: (sessionId, requestId, occurredAt) => {
309
- events.publish(journal.append(sessionId, 'interaction.resolved', { requestId }, occurredAt));
319
+ interactionResolved: (sessionId, requestId, occurredAt, outcome) => {
320
+ const interaction = interactions
321
+ .snapshot(sessionId)
322
+ .find((item) => item.requestId === requestId);
323
+ events.publish(journal.append(sessionId, 'interaction.resolved', { requestId, turnId: interaction?.turnId ?? null, resolvedAt: occurredAt, outcome }, occurredAt));
310
324
  },
311
325
  },
312
326
  sessionEvents: {
@@ -341,7 +355,10 @@ export async function composeRelayApp(options) {
341
355
  }
342
356
  : {}),
343
357
  interactions: {
344
- resolve: (sessionId, requestId, resolvedAt) => interactions.resolve(sessionId, requestId, resolvedAt),
358
+ resolve: (sessionId, requestId, resolvedAt, outcome) => interactions.resolve(sessionId, requestId, resolvedAt, outcome),
359
+ alreadyResolved: (sessionId, requestId) => interactions.resolved(sessionId, requestId),
360
+ snapshot: (sessionId) => interactions.snapshot(sessionId),
361
+ pending: (sessionId, requestId) => interactions.find(sessionId, requestId) !== null,
345
362
  validate: (sessionId, requestId, value) => {
346
363
  const interaction = interactions.find(sessionId, requestId);
347
364
  if (!interaction)
@@ -3,12 +3,18 @@
3
3
  * Designed by Denis Roio <jaromil@dyne.org>
4
4
  * SPDX-License-Identifier: AGPL-3.0-or-later
5
5
  */
6
- import { toChatItems } from './history-mapper.js';
6
+ import { toChatItems, toChatTurns } from './history-mapper.js';
7
+ /**
8
+ * ChatSnapshot is a lower-bound cut: baseSequence is sampled before the
9
+ * upstream read, therefore every event with a higher sequence remains eligible
10
+ * for ordered replay even when its effect is already visible in the snapshot.
11
+ */
7
12
  export function registerGetHistory(app, deps) {
8
13
  app.get('/api/sessions/:id/history', async (request, reply) => {
9
14
  const session = deps.find(request.params.id);
10
15
  if (!session)
11
16
  return reply.code(404).send({ code: 'SESSION_NOT_FOUND' });
17
+ const baseSequence = deps.currentSequence(session.id);
12
18
  let history;
13
19
  try {
14
20
  history = await deps.read(session);
@@ -33,10 +39,15 @@ export function registerGetHistory(app, deps) {
33
39
  });
34
40
  }
35
41
  const items = toChatItems(history.turns);
36
- return reply.send({
42
+ const snapshot = {
37
43
  items,
44
+ turns: toChatTurns(history.turns),
38
45
  activeTurnId: history.activeTurnId,
39
- currentSequence: deps.currentSequence(session.id),
40
- });
46
+ interactions: deps.interactions?.(session.id) ?? [],
47
+ baseSequence,
48
+ // Temporary compatibility for clients which have not adopted ChatSnapshot.
49
+ currentSequence: baseSequence,
50
+ };
51
+ return reply.send(snapshot);
41
52
  });
42
53
  }
@@ -3,10 +3,23 @@
3
3
  * Designed by Denis Roio <jaromil@dyne.org>
4
4
  * SPDX-License-Identifier: AGPL-3.0-or-later
5
5
  */
6
+ /** Canonical grouping preserves the upstream Codex turn identity across reloads. */
7
+ export function toChatTurns(turns) {
8
+ return turns.map((turn, index) => {
9
+ const turnId = turn.id ?? `history-turn-${index}`;
10
+ return {
11
+ id: turnId,
12
+ items: turn.items.flatMap((item) => toChatItem(item, occurredAt(item, turn), turnId)),
13
+ startedAt: turn.startedAt,
14
+ completedAt: turn.completedAt,
15
+ };
16
+ });
17
+ }
6
18
  export function toChatItems(turns) {
7
- return turns.flatMap((turn) => turn.items.flatMap((item) => toChatItem(item, occurredAt(item, turn))));
19
+ return turns.flatMap((turn, index) => turn.items.flatMap((item) => toChatItem(item, occurredAt(item, turn), turn.id ?? `history-turn-${index}`)));
8
20
  }
9
- function toChatItem(item, timestamp) {
21
+ function toChatItem(item, timestamp, turnId) {
22
+ const owner = turnId ? { turnId } : {};
10
23
  return [item].flatMap((item) => {
11
24
  const id = typeof item.id === 'string' ? item.id : null;
12
25
  if (!id)
@@ -21,7 +34,7 @@ function toChatItem(item, timestamp) {
21
34
  .join('\n')
22
35
  : '';
23
36
  return text
24
- ? [{ id, kind: 'user', text, ...(timestamp ? { occurredAt: timestamp } : {}) }]
37
+ ? [{ id, kind: 'user', text, ...owner, ...(timestamp ? { occurredAt: timestamp } : {}) }]
25
38
  : [];
26
39
  }
27
40
  case 'agentMessage':
@@ -31,6 +44,7 @@ function toChatItem(item, timestamp) {
31
44
  id,
32
45
  kind: 'agent',
33
46
  text: item.text,
47
+ ...owner,
34
48
  ...(item.phase === 'commentary' || item.phase === 'final_answer'
35
49
  ? { phase: item.phase }
36
50
  : {}),
@@ -42,9 +56,11 @@ function toChatItem(item, timestamp) {
42
56
  if (!Array.isArray(item.summary))
43
57
  return [];
44
58
  const summary = reasoningSummary(item.summary);
45
- return summary.length ? [{ id, kind: 'reasoning', summary }] : [];
59
+ return summary.length ? [{ id, kind: 'reasoning', summary, ...owner }] : [];
46
60
  case 'plan':
47
- return typeof item.text === 'string' ? [{ id, kind: 'plan', text: item.text }] : [];
61
+ return typeof item.text === 'string'
62
+ ? [{ id, kind: 'plan', text: item.text, ...owner }]
63
+ : [];
48
64
  case 'commandExecution':
49
65
  return typeof item.command === 'string' && typeof item.status === 'string'
50
66
  ? [
@@ -53,10 +69,19 @@ function toChatItem(item, timestamp) {
53
69
  kind: 'command',
54
70
  command: item.command,
55
71
  status: item.status,
72
+ ...owner,
56
73
  ...(typeof item.exitCode === 'number' ? { exitCode: item.exitCode } : {}),
57
74
  },
58
75
  ]
59
76
  : [];
77
+ case 'fileChange': {
78
+ if (!Array.isArray(item.changes) || typeof item.status !== 'string')
79
+ return [];
80
+ const paths = item.changes.flatMap((change) => isRecord(change) && typeof change.path === 'string' ? [change.path] : []);
81
+ return paths.length
82
+ ? [{ id, kind: 'fileChange', paths, status: item.status, ...owner }]
83
+ : [];
84
+ }
60
85
  default:
61
86
  return [];
62
87
  }
@@ -54,6 +54,7 @@ export function registerSessionRoutes(app, deps) {
54
54
  find: sessions.find,
55
55
  read: sessions.readHistory,
56
56
  currentSequence: sessions.currentSequence ?? (() => 0),
57
+ interactions: deps.interactions?.snapshot,
57
58
  });
58
59
  if (sessions.startTurn)
59
60
  registerStartTurn(app, {
@@ -61,6 +62,7 @@ export function registerSessionRoutes(app, deps) {
61
62
  start: sessions.startTurn,
62
63
  save: sessions.save,
63
64
  onStarted: sessions.onTurnStarted,
65
+ idempotency: sessions.idempotency,
64
66
  });
65
67
  if (sessions.models)
66
68
  registerSelectModel(app, {
@@ -108,8 +110,10 @@ export function registerSessionRoutes(app, deps) {
108
110
  if (sessions.replyInteraction && deps.interactions)
109
111
  registerRespondInteraction(app, {
110
112
  exists: (id) => sessions.find(id) !== null,
111
- resolve: (sessionId, requestId, resolvedAt) => deps.interactions.resolve(sessionId, requestId, resolvedAt),
113
+ resolve: (sessionId, requestId, resolvedAt, outcome) => deps.interactions.resolve(sessionId, requestId, resolvedAt, outcome),
112
114
  validate: deps.interactions.validate,
115
+ pending: deps.interactions.pending,
116
+ alreadyResolved: deps.interactions.alreadyResolved,
113
117
  reply: sessions.replyInteraction,
114
118
  resolved: sessions.interactionResolved,
115
119
  now: sessions.now,
@@ -11,14 +11,30 @@ export function registerRespondInteraction(app, deps) {
11
11
  const value = request.body;
12
12
  if (typeof value !== 'object' || value === null || Array.isArray(value))
13
13
  return reply.code(400).send({ code: 'INTERACTION_RESPONSE_INVALID' });
14
+ const recorded = deps.alreadyResolved?.(id, requestId);
15
+ if (recorded)
16
+ return reply.code(202).send({ accepted: true, ...recorded });
17
+ if (deps.pending && !deps.pending(id, requestId))
18
+ return reply.code(409).send({ code: 'INTERACTION_NOT_PENDING' });
14
19
  if (deps.validate && !deps.validate(id, requestId, value))
15
20
  return reply.code(400).send({ code: 'INTERACTION_RESPONSE_INVALID' });
16
21
  if (!deps.reply(id, requestId, value))
17
22
  return reply.code(409).send({ code: 'INTERACTION_NOT_PENDING' });
18
23
  const resolvedAt = deps.now();
19
- if (!deps.resolve(id, requestId, resolvedAt))
24
+ const outcome = safeOutcome(value);
25
+ if (!deps.resolve(id, requestId, resolvedAt, outcome))
20
26
  return reply.code(409).send({ code: 'INTERACTION_NOT_PENDING' });
21
- deps.resolved?.(id, requestId, resolvedAt);
22
- return reply.code(202).send({ accepted: true });
27
+ deps.resolved?.(id, requestId, resolvedAt, outcome);
28
+ return reply.code(202).send({ accepted: true, resolvedAt, outcome });
23
29
  });
24
30
  }
31
+ function safeOutcome(value) {
32
+ if (typeof value !== 'object' || value === null)
33
+ return 'answered';
34
+ const decision = value.decision;
35
+ if (decision === 'approved' || decision === 'accept')
36
+ return 'approved';
37
+ if (decision === 'denied' || decision === 'decline')
38
+ return 'denied';
39
+ return 'answered';
40
+ }
@@ -24,25 +24,60 @@ export function registerSessionEvents(app, deps) {
24
24
  }
25
25
  server.handleUpgrade(request, socket, head, (connection) => {
26
26
  const sessionId = match[1];
27
+ let lastSentSequence = after;
27
28
  const send = (event) => {
28
- if (isSlowClient(connection.bufferedAmount))
29
+ // A subscription intentionally overlaps the durable replay. The sequence
30
+ // is the authoritative identity of an event, so a bounded high-water mark
31
+ // de-duplicates both replay overlap and stale live callbacks.
32
+ if (event.sessionId !== sessionId ||
33
+ !Number.isSafeInteger(event.sequence) ||
34
+ event.sequence < 1)
35
+ return;
36
+ if (event.sequence <= lastSentSequence)
37
+ return;
38
+ lastSentSequence = event.sequence;
39
+ if (isSlowClient(deps.bufferedAmount?.(connection) ?? connection.bufferedAmount))
29
40
  return connection.close(1013, 'slow client');
30
41
  connection.send(JSON.stringify({ type: 'relay.event', event }));
31
42
  };
32
- const replay = replayOrResync(deps.since(sessionId, after), after);
33
- if (replay.kind === 'resync') {
34
- const currentSequence = deps.since(sessionId, 0).at(-1)?.sequence ?? after;
35
- connection.send(JSON.stringify({ type: 'relay.resyncRequired', currentSequence }));
43
+ let unsubscribe;
44
+ let removeHeartbeat;
45
+ let replaying = true;
46
+ const liveDuringReplay = [];
47
+ const cleanup = () => {
48
+ removeHeartbeat?.();
49
+ removeHeartbeat = undefined;
50
+ unsubscribe?.();
51
+ unsubscribe = undefined;
52
+ };
53
+ try {
54
+ // Subscribe before reading the journal: every event after this point is
55
+ // either in replay or delivered live, and the sequence de-duplicates it.
56
+ unsubscribe = deps.subscribe(sessionId, (event) => {
57
+ if (replaying)
58
+ liveDuringReplay.push(event);
59
+ else
60
+ send(event);
61
+ });
62
+ const replay = replayOrResync(deps.since(sessionId, after), after);
63
+ if (replay.kind === 'resync') {
64
+ const currentSequence = deps.since(sessionId, 0).at(-1)?.sequence ?? after;
65
+ connection.send(JSON.stringify({ type: 'relay.resyncRequired', currentSequence }));
66
+ cleanup();
67
+ connection.close();
68
+ return;
69
+ }
70
+ replay.events.forEach(send);
71
+ replaying = false;
72
+ liveDuringReplay.sort((left, right) => left.sequence - right.sequence).forEach(send);
73
+ }
74
+ catch {
75
+ cleanup();
36
76
  connection.close();
37
77
  return;
38
78
  }
39
- replay.events.forEach(send);
40
- const unsubscribe = deps.subscribe(sessionId, send);
41
- const removeHeartbeat = installWebSocketHeartbeat(connection, deps.heartbeatIntervalMs ?? 25_000, () => deps.authorized?.(request.headers.cookie) ?? true);
42
- connection.on('close', () => {
43
- removeHeartbeat();
44
- unsubscribe();
45
- });
79
+ removeHeartbeat = installWebSocketHeartbeat(connection, deps.heartbeatIntervalMs ?? 25_000, () => deps.authorized?.(request.headers.cookie) ?? true);
80
+ connection.on('close', cleanup);
46
81
  });
47
82
  });
48
83
  app.addHook('onClose', async () => server.close());
@@ -3,7 +3,11 @@
3
3
  * Designed by Denis Roio <jaromil@dyne.org>
4
4
  * SPDX-License-Identifier: AGPL-3.0-or-later
5
5
  */
6
+ import { createHash } from 'node:crypto';
6
7
  export function registerStartTurn(app, deps) {
8
+ const inflight = new Map();
9
+ // Durable idempotency results are session-scoped and replay for as long as the
10
+ // store retains them; a reused key with a different prompt is rejected.
7
11
  // Text is capped at 100,000 characters; leave room for JSON encoding only.
8
12
  app.post('/api/sessions/:id/turns', { bodyLimit: 128 * 1024 }, async (request, reply) => {
9
13
  const session = deps.find(request.params.id);
@@ -14,9 +18,43 @@ export function registerStartTurn(app, deps) {
14
18
  return reply.code(400).send({ code: 'TURN_INPUT_TOO_LONG' });
15
19
  if (!text || session.state !== 'ready')
16
20
  return reply.code(409).send({ code: 'SESSION_NOT_READY' });
21
+ const key = request.headers['idempotency-key'];
22
+ if (typeof key === 'string' && key && deps.idempotency) {
23
+ const scope = `start-turn:${session.id}`;
24
+ const fingerprint = createHash('sha256').update(text).digest('hex');
25
+ const cached = deps.idempotency.get(scope, key);
26
+ if (cached)
27
+ return replay(cached, fingerprint, reply);
28
+ const inflightKey = `${scope}:${key}`;
29
+ const operation = inflight.get(inflightKey) ??
30
+ (async () => {
31
+ const started = await deps.start(session, text);
32
+ deps.save(started);
33
+ deps.onStarted?.(started);
34
+ const result = {
35
+ statusCode: 202,
36
+ body: JSON.stringify({ fingerprint, response: started }),
37
+ };
38
+ deps.idempotency.put(scope, key, result.statusCode, result.body);
39
+ return result;
40
+ })();
41
+ inflight.set(inflightKey, operation);
42
+ try {
43
+ return replay(await operation, fingerprint, reply);
44
+ }
45
+ finally {
46
+ inflight.delete(inflightKey);
47
+ }
48
+ }
17
49
  const started = await deps.start(session, text);
18
50
  deps.save(started);
19
51
  deps.onStarted?.(started);
20
52
  return reply.code(202).send(started);
21
53
  });
22
54
  }
55
+ function replay(result, fingerprint, reply) {
56
+ const cached = JSON.parse(result.body);
57
+ if (cached.fingerprint !== fingerprint)
58
+ return reply.code(409).send({ code: 'IDEMPOTENCY_KEY_REUSED' });
59
+ return reply.code(result.statusCode).send(cached.response);
60
+ }
@@ -4,7 +4,7 @@
4
4
  * SPDX-License-Identifier: AGPL-3.0-or-later
5
5
  */
6
6
  import { relative } from 'node:path';
7
- export function normalizeCodexNotification(sessionId, sequence, occurredAt, notification, workspacePath) {
7
+ export function normalizeCodexNotification(sessionId, sequence, occurredAt, notification, workspacePath, activeTurnId) {
8
8
  const decoded = decodeNotification(notification);
9
9
  if (!decoded)
10
10
  return null;
@@ -14,7 +14,13 @@ export function normalizeCodexNotification(sessionId, sequence, occurredAt, noti
14
14
  sequence,
15
15
  occurredAt,
16
16
  type: 'agentMessageDelta',
17
- payload: { text: decoded.params.delta },
17
+ payload: {
18
+ text: decoded.params.delta,
19
+ ...(decoded.params.itemId ? { itemId: decoded.params.itemId } : {}),
20
+ ...((decoded.params.turnId ?? activeTurnId)
21
+ ? { turnId: decoded.params.turnId ?? activeTurnId }
22
+ : {}),
23
+ },
18
24
  };
19
25
  if (decoded.method === 'turn/completed')
20
26
  return {
@@ -25,7 +31,16 @@ export function normalizeCodexNotification(sessionId, sequence, occurredAt, noti
25
31
  payload: decoded.params,
26
32
  };
27
33
  if (decoded.method === 'item/started' || decoded.method === 'item/completed') {
28
- const activity = safeActivity(decoded.params.item, workspacePath);
34
+ const message = safeAgentMessage(decoded.params.item, activeTurnId, decoded.params.turnId);
35
+ if (message)
36
+ return {
37
+ sessionId,
38
+ sequence,
39
+ occurredAt,
40
+ type: decoded.method === 'item/started' ? 'agentMessageStarted' : 'agentMessageCompleted',
41
+ payload: message,
42
+ };
43
+ const activity = safeActivity(decoded.params.item, workspacePath, activeTurnId, decoded.params.turnId);
29
44
  if (activity)
30
45
  return { sessionId, sequence, occurredAt, type: 'activity.updated', payload: activity };
31
46
  }
@@ -36,7 +51,14 @@ export function decodeNotification(input) {
36
51
  if (input.method === 'item/agentMessage/delta') {
37
52
  const params = record(input.params);
38
53
  return typeof params?.delta === 'string' && params.delta.length <= 64_000
39
- ? { method: input.method, params: { delta: params.delta } }
54
+ ? {
55
+ method: input.method,
56
+ params: {
57
+ delta: params.delta,
58
+ ...(safeId(params.itemId) ? { itemId: params.itemId } : {}),
59
+ ...(safeId(params.turnId) ? { turnId: params.turnId } : {}),
60
+ },
61
+ }
40
62
  : null;
41
63
  }
42
64
  if (input.method === 'turn/completed') {
@@ -62,30 +84,43 @@ export function decodeNotification(input) {
62
84
  if (input.method === 'item/started' || input.method === 'item/completed') {
63
85
  const params = record(input.params);
64
86
  return params && 'item' in params
65
- ? { method: input.method, params: { item: params.item } }
87
+ ? {
88
+ method: input.method,
89
+ params: {
90
+ item: params.item,
91
+ ...(safeId(params.turnId) ? { turnId: params.turnId } : {}),
92
+ },
93
+ }
66
94
  : null;
67
95
  }
68
96
  return null;
69
97
  }
70
- function safeActivity(item, workspacePath) {
98
+ function safeActivity(item, workspacePath, activeTurnId, notificationTurnId) {
71
99
  if (!item || typeof item !== 'object')
72
100
  return null;
73
101
  const value = item;
74
102
  if (typeof value.id !== 'string' || typeof value.type !== 'string')
75
103
  return null;
104
+ const owner = notificationTurnId
105
+ ? { turnId: notificationTurnId }
106
+ : safeId(value.turnId)
107
+ ? { turnId: value.turnId }
108
+ : activeTurnId
109
+ ? { turnId: activeTurnId }
110
+ : {};
76
111
  const status = typeof value.status === 'string' ? ` · ${value.status}` : '';
77
112
  if (value.type === 'commandExecution' && typeof value.command === 'string')
78
- return { id: value.id, label: `Command${status}`, detail: value.command };
113
+ return { id: value.id, label: `Command${status}`, detail: value.command, ...owner };
79
114
  if (value.type === 'plan' && typeof value.text === 'string')
80
- return { id: value.id, label: 'Plan', detail: value.text };
115
+ return { id: value.id, label: 'Plan', detail: value.text, ...owner };
81
116
  if (value.type === 'reasoning' && Array.isArray(value.summary)) {
82
117
  const detail = reasoningSummary(value.summary).join('\n');
83
- return detail ? { id: value.id, label: 'Reasoning summary', detail } : null;
118
+ return detail ? { id: value.id, label: 'Reasoning summary', detail, ...owner } : null;
84
119
  }
85
120
  if (value.type === 'fileChange' && Array.isArray(value.changes))
86
121
  return {
87
122
  id: value.id,
88
- label: `fileChange${status}`,
123
+ label: `File change${status}`,
89
124
  detail: value.changes
90
125
  .map((change) => change &&
91
126
  typeof change === 'object' &&
@@ -94,12 +129,36 @@ function safeActivity(item, workspacePath) {
94
129
  : '')
95
130
  .filter(Boolean)
96
131
  .join('\n'),
132
+ ...owner,
97
133
  };
98
134
  if ((value.type === 'mcpToolCall' || value.type === 'dynamicToolCall') &&
99
135
  typeof value.tool === 'string')
100
- return { id: value.id, label: `Tool${status}`, detail: value.tool };
136
+ return { id: value.id, label: `Tool${status}`, detail: value.tool, ...owner };
101
137
  return null;
102
138
  }
139
+ function safeAgentMessage(item, activeTurnId, notificationTurnId) {
140
+ const value = record(item);
141
+ if (!value || value.type !== 'agentMessage' || !safeId(value.id))
142
+ return null;
143
+ if (value.text !== undefined && typeof value.text !== 'string')
144
+ return null;
145
+ const phase = value.phase === 'commentary' || value.phase === 'final_answer' ? value.phase : null;
146
+ return {
147
+ itemId: value.id,
148
+ text: typeof value.text === 'string' ? value.text : '',
149
+ ...(phase ? { phase } : {}),
150
+ ...(notificationTurnId
151
+ ? { turnId: notificationTurnId }
152
+ : safeId(value.turnId)
153
+ ? { turnId: value.turnId }
154
+ : activeTurnId
155
+ ? { turnId: activeTurnId }
156
+ : {}),
157
+ };
158
+ }
159
+ function safeId(value) {
160
+ return typeof value === 'string' && value.length > 0 && value.length <= 256;
161
+ }
103
162
  function relativePath(path, workspacePath) {
104
163
  if (!workspacePath)
105
164
  return path;
@@ -180,6 +180,7 @@ export class CodexSessionRuntime {
180
180
  const activeTurn = rawTurns.find((turn) => turn.status === 'inProgress' && typeof turn.id === 'string');
181
181
  return {
182
182
  turns: rawTurns.map((turn) => ({
183
+ ...(typeof turn.id === 'string' ? { id: turn.id } : {}),
183
184
  items: turn.items ?? [],
184
185
  startedAt: typeof turn.startedAt === 'number' ? turn.startedAt : null,
185
186
  completedAt: typeof turn.completedAt === 'number' ? turn.completedAt : null,