gestalt-mobile 0.16.3 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/index-Cfo4jnPb.js +25 -0
- package/dist/client/assets/index-e2lJ5XqL.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/server/composition.js +25 -17
- package/dist/server/server/features/sessions/application/writer-acquisition.js +59 -0
- package/dist/server/server/features/sessions/model/relay-session.js +8 -0
- package/dist/server/server/features/sessions/promote-recent-thread/endpoint.js +8 -2
- package/dist/server/server/features/sessions/promote-recent-thread/use-case.js +38 -6
- package/dist/server/server/features/sessions/register-routes.js +2 -0
- package/dist/server/server/features/sessions/start-turn/endpoint.js +130 -12
- package/dist/server/server/platform/codex/json-rpc-client.js +13 -3
- package/dist/server/server/platform/codex/session-runtime.js +94 -8
- package/dist/server/server/platform/plans/filesystem-workspace-plan-catalog.js +90 -46
- package/dist/server/server/platform/plans/org-plan-command-validator.js +62 -0
- package/package.json +1 -1
- package/dist/client/assets/index-BotpWcLh.css +0 -1
- package/dist/client/assets/index-DWHYFaoK.js +0 -25
|
@@ -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}.writer-hint.svelte-1264sfg{color:var(--theme-text-muted);margin:.35rem 0}.writer-feedback.svelte-1264sfg{border-inline-start:.25rem solid var(--theme-danger,#b42318);background:var(--theme-control-hover);flex-wrap:wrap;align-items:center;gap:.5rem;margin-block:.5rem;padding:.5rem;display:flex}.writer-feedback.svelte-1264sfg p:where(.svelte-1264sfg){flex:16rem;margin:0}.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}.scope.svelte-x2ko9l{margin-block:0 .75rem}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}code.svelte-x2ko9l{font:inherit}.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}}
|
package/dist/client/index.html
CHANGED
|
@@ -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-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-Cfo4jnPb.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-e2lJ5XqL.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="app"></div>
|
|
@@ -42,6 +42,7 @@ import { compileSkillOverride } from './features/skills/model/skill-profile.js';
|
|
|
42
42
|
import { SupervisedPlanRegistry } from './features/plans/application/supervised-plan-registry.js';
|
|
43
43
|
import { FilesystemPlanStatusSource } from './platform/plans/filesystem-plan-status-source.js';
|
|
44
44
|
import { FilesystemWorkspacePlanCatalog } from './platform/plans/filesystem-workspace-plan-catalog.js';
|
|
45
|
+
import { OrgPlanCommandValidator } from './platform/plans/org-plan-command-validator.js';
|
|
45
46
|
import { checkpointPlanMeasurement } from './platform/plans/plan-measurement-command.js';
|
|
46
47
|
import { PlanMeasurementRefresh } from './platform/plans/plan-measurement-refresh.js';
|
|
47
48
|
import { createRelyingPartyConfig } from './config.js';
|
|
@@ -74,7 +75,6 @@ export async function composeRelayApp(options) {
|
|
|
74
75
|
const idempotency = new SqliteIdempotencyStore(database);
|
|
75
76
|
const supervisedPlans = new SupervisedPlanRegistry();
|
|
76
77
|
const planStatusSource = new FilesystemPlanStatusSource(join(dirname(databasePath), 'plans'));
|
|
77
|
-
const workspacePlanCatalog = new FilesystemWorkspacePlanCatalog();
|
|
78
78
|
const planMeasurementHelperPath = options.planMeasurementHelperPath ?? process.env.GESTALT_MOBILE_ORG_PLAN_HELPER;
|
|
79
79
|
const withPendingInteractions = (session) => (session ? { ...session, pendingInteractions: interactions.list(session.id) } : null);
|
|
80
80
|
const events = new SessionEventBus();
|
|
@@ -83,6 +83,10 @@ export async function composeRelayApp(options) {
|
|
|
83
83
|
const skillProfiles = new FilesystemSkillProfileStore(options.homeDirectory ?? homedir());
|
|
84
84
|
const skillCatalog = (profile) => new CodexSkillCatalog(profile, options.launchAppServer ?? launchCodexAppServer);
|
|
85
85
|
const editorSkillCatalog = new CachedSkillCatalog((profile, workspace) => skillCatalog(profile).list(workspace));
|
|
86
|
+
const workspacePlanCatalog = new FilesystemWorkspacePlanCatalog(new OrgPlanCommandValidator({
|
|
87
|
+
...(planMeasurementHelperPath ? { helperPath: planMeasurementHelperPath } : {}),
|
|
88
|
+
discoverSkills: async (workspace) => (await skillCatalog('default').list(workspace)).skills,
|
|
89
|
+
}));
|
|
86
90
|
const resolveSkills = async (session) => {
|
|
87
91
|
const catalog = await skillCatalog(session.profile).list(session.workspacePath);
|
|
88
92
|
if (session.effectiveSkillSelection)
|
|
@@ -162,7 +166,7 @@ export async function composeRelayApp(options) {
|
|
|
162
166
|
}
|
|
163
167
|
events.publish(journal.append(sessionId, 'plan.updated', { plan: update.plan, reason: update.reason }, occurredAt));
|
|
164
168
|
}
|
|
165
|
-
}, options.planMeasurementBaseUrl)
|
|
169
|
+
}, options.planMeasurementBaseUrl, 30_000, 64, root)
|
|
166
170
|
: null;
|
|
167
171
|
if (runtime && planMeasurementHelperPath) {
|
|
168
172
|
planMeasurementRefresh = new PlanMeasurementRefresh(async (sessionId) => {
|
|
@@ -189,7 +193,14 @@ export async function composeRelayApp(options) {
|
|
|
189
193
|
if (session)
|
|
190
194
|
saveSession(RelaySession.rehydrate(session).requireAttention(new Date().toISOString()).snapshot);
|
|
191
195
|
});
|
|
192
|
-
|
|
196
|
+
// An exited child has released our writer. Do not repeatedly resume a
|
|
197
|
+
// durable thread: another Codex client may acquire it between callbacks.
|
|
198
|
+
recoverExitedSession = (sessionId) => {
|
|
199
|
+
supervisor.cancel(sessionId);
|
|
200
|
+
const session = sessions.find(sessionId);
|
|
201
|
+
if (session)
|
|
202
|
+
saveSession(RelaySession.rehydrate(session).stop(new Date().toISOString()).snapshot);
|
|
203
|
+
};
|
|
193
204
|
}
|
|
194
205
|
let authorization;
|
|
195
206
|
if (passkeyAuthEnabled) {
|
|
@@ -285,6 +296,10 @@ export async function composeRelayApp(options) {
|
|
|
285
296
|
startTurn: runtime
|
|
286
297
|
? async (session, text, clientUserMessageId) => runtime.startTurn(session, text, clientUserMessageId, new Date().toISOString())
|
|
287
298
|
: undefined,
|
|
299
|
+
ensureWriter: runtime
|
|
300
|
+
? (session) => runtime.ensureWriter(session, new Date().toISOString())
|
|
301
|
+
: undefined,
|
|
302
|
+
releaseWriter: runtime ? (id) => runtime.release(id) : undefined,
|
|
288
303
|
onTurnStarted: (session) => planMeasurementRefresh?.refreshNow(session.id),
|
|
289
304
|
models,
|
|
290
305
|
readHistory: runtime ? (session) => runtime.readHistory(session) : undefined,
|
|
@@ -301,7 +316,7 @@ export async function composeRelayApp(options) {
|
|
|
301
316
|
now: () => new Date().toISOString(),
|
|
302
317
|
list: () => sessions.list(),
|
|
303
318
|
save: saveSession,
|
|
304
|
-
|
|
319
|
+
read: (session) => runtime.readHistory(session),
|
|
305
320
|
})
|
|
306
321
|
: undefined,
|
|
307
322
|
release: (session) => RelaySession.rehydrate(session).release(new Date().toISOString()).snapshot,
|
|
@@ -402,25 +417,18 @@ export async function composeRelayApp(options) {
|
|
|
402
417
|
database.close();
|
|
403
418
|
throw error;
|
|
404
419
|
}
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
.filter((session) => session.desiredState === 'active' && session.threadId !== null), 2, async (session) => {
|
|
411
|
-
try {
|
|
412
|
-
saveSession(await runtime.restore(session, new Date().toISOString()));
|
|
413
|
-
}
|
|
414
|
-
catch {
|
|
415
|
-
saveSession(RelaySession.rehydrate(session).requireAttention(new Date().toISOString()).snapshot);
|
|
416
|
-
}
|
|
420
|
+
const detachActiveSessions = async () => {
|
|
421
|
+
await mapWithConcurrency(sessions.list().filter((session) => session.threadId !== null), 2, async (session) => {
|
|
422
|
+
if (session.desiredState === 'active')
|
|
423
|
+
saveSession(RelaySession.rehydrate(session).stop(new Date().toISOString()).snapshot);
|
|
424
|
+
await runtime?.watchPlanStatus(session);
|
|
417
425
|
});
|
|
418
426
|
};
|
|
419
427
|
app.addHook('onListen', async () => {
|
|
420
428
|
const profile = (await options.profiles.list()).find((item) => item.state === 'ok')?.name;
|
|
421
429
|
if (profile)
|
|
422
430
|
await editorSkillCatalog.refresh(profile, root);
|
|
423
|
-
await
|
|
431
|
+
await detachActiveSessions();
|
|
424
432
|
});
|
|
425
433
|
app.addHook('onClose', async () => {
|
|
426
434
|
planMeasurementRefresh?.stopAll();
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Dyne.org foundation
|
|
3
|
+
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
*/
|
|
6
|
+
export class WriterAcquisitionError extends Error {
|
|
7
|
+
kind;
|
|
8
|
+
constructor(kind) {
|
|
9
|
+
super(kind);
|
|
10
|
+
this.kind = kind;
|
|
11
|
+
this.name = 'WriterAcquisitionError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
export function writerAcquisitionProblem(kind) {
|
|
15
|
+
switch (kind) {
|
|
16
|
+
case 'writerBusy':
|
|
17
|
+
return {
|
|
18
|
+
status: 409,
|
|
19
|
+
code: 'SESSION_WRITER_BUSY',
|
|
20
|
+
retryable: true,
|
|
21
|
+
detail: 'This thread is active in another Codex client. Release it there, then retry.',
|
|
22
|
+
};
|
|
23
|
+
case 'rolloutMissing':
|
|
24
|
+
return {
|
|
25
|
+
status: 409,
|
|
26
|
+
code: 'SESSION_ROLLOUT_MISSING',
|
|
27
|
+
retryable: false,
|
|
28
|
+
detail: 'This stored thread is no longer available.',
|
|
29
|
+
};
|
|
30
|
+
case 'workspaceUnavailable':
|
|
31
|
+
return {
|
|
32
|
+
status: 409,
|
|
33
|
+
code: 'SESSION_WORKSPACE_UNAVAILABLE',
|
|
34
|
+
retryable: true,
|
|
35
|
+
detail: 'The session workspace is unavailable.',
|
|
36
|
+
};
|
|
37
|
+
case 'runtimeDependencyFailed':
|
|
38
|
+
return {
|
|
39
|
+
status: 502,
|
|
40
|
+
code: 'SESSION_RUNTIME_DEPENDENCY_FAILED',
|
|
41
|
+
retryable: true,
|
|
42
|
+
detail: 'A required Codex runtime dependency is unavailable.',
|
|
43
|
+
};
|
|
44
|
+
case 'protocolIncompatible':
|
|
45
|
+
return {
|
|
46
|
+
status: 503,
|
|
47
|
+
code: 'CODEX_PROTOCOL_INCOMPATIBLE',
|
|
48
|
+
retryable: false,
|
|
49
|
+
detail: 'The installed Codex runtime is incompatible with this relay.',
|
|
50
|
+
};
|
|
51
|
+
case 'runtimeUnavailable':
|
|
52
|
+
return {
|
|
53
|
+
status: 503,
|
|
54
|
+
code: 'SESSION_RUNTIME_UNAVAILABLE',
|
|
55
|
+
retryable: true,
|
|
56
|
+
detail: 'The Codex runtime is unavailable. Retry shortly.',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
import { DomainError } from './errors.js';
|
|
7
7
|
import { createSkillSelection, normalizeSkillProfileName, } from '../../skills/model/skill-profile.js';
|
|
8
8
|
import { interactionId, profileName, sessionId, threadId, turnId, workspaceId, workspacePath, } from './value-objects.js';
|
|
9
|
+
/** A durable thread can be read even when this relay has no live writer. */
|
|
10
|
+
export function isSessionReadable(session) {
|
|
11
|
+
return session.threadId !== null;
|
|
12
|
+
}
|
|
13
|
+
/** Writer ownership is deliberately transient: only a live relay state implies it. */
|
|
14
|
+
export function relayOwnsWriter(session) {
|
|
15
|
+
return session.state === 'ready' || session.state === 'turnActive';
|
|
16
|
+
}
|
|
9
17
|
export function createEffectiveSkillSelection(input) {
|
|
10
18
|
return {
|
|
11
19
|
...(input.selectedProfileName === undefined
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
+
import { RecentThreadHistoryUnavailable } from './use-case.js';
|
|
7
8
|
const requestSchema = z.object({
|
|
8
9
|
threadId: z.string().min(1),
|
|
9
10
|
cwd: z.string().startsWith('/'),
|
|
@@ -20,9 +21,14 @@ export function registerPromoteRecentThread(app, deps) {
|
|
|
20
21
|
return reply.code(202).send(await deps.promote(thread));
|
|
21
22
|
}
|
|
22
23
|
catch (error) {
|
|
24
|
+
const historyUnavailable = error instanceof RecentThreadHistoryUnavailable;
|
|
23
25
|
return reply.code(409).send({
|
|
24
|
-
code:
|
|
25
|
-
|
|
26
|
+
code: historyUnavailable
|
|
27
|
+
? 'RECENT_THREAD_HISTORY_UNAVAILABLE'
|
|
28
|
+
: 'RECENT_THREAD_OPEN_FAILED',
|
|
29
|
+
detail: historyUnavailable
|
|
30
|
+
? 'The selected thread history is currently unavailable. Retry after Codex is available.'
|
|
31
|
+
: 'The selected thread could not be opened. Retry shortly.',
|
|
26
32
|
});
|
|
27
33
|
}
|
|
28
34
|
});
|
|
@@ -4,10 +4,38 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
import { RelaySession } from '../model/relay-session.js';
|
|
7
|
+
export class RecentThreadHistoryUnavailable extends Error {
|
|
8
|
+
constructor() {
|
|
9
|
+
super('RECENT_THREAD_HISTORY_UNAVAILABLE');
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const inFlightPromotions = new Map();
|
|
7
13
|
export async function promoteRecentThread(thread, deps) {
|
|
14
|
+
const key = `${thread.profile}:${thread.cwd}:${thread.id}`;
|
|
15
|
+
const ongoing = inFlightPromotions.get(key);
|
|
16
|
+
if (ongoing)
|
|
17
|
+
return ongoing;
|
|
18
|
+
const operation = promote(thread, deps);
|
|
19
|
+
inFlightPromotions.set(key, operation);
|
|
20
|
+
try {
|
|
21
|
+
return await operation;
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
if (inFlightPromotions.get(key) === operation)
|
|
25
|
+
inFlightPromotions.delete(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function promote(thread, deps) {
|
|
8
29
|
const existing = deps.list().find((session) => session.threadId === thread.id);
|
|
9
|
-
if (existing
|
|
30
|
+
if (existing) {
|
|
31
|
+
try {
|
|
32
|
+
await deps.read(existing);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
throw new RecentThreadHistoryUnavailable();
|
|
36
|
+
}
|
|
10
37
|
return existing;
|
|
38
|
+
}
|
|
11
39
|
const now = deps.now();
|
|
12
40
|
const imported = existing ??
|
|
13
41
|
RelaySession.fromExistingThread({
|
|
@@ -18,9 +46,13 @@ export async function promoteRecentThread(thread, deps) {
|
|
|
18
46
|
threadId: thread.id,
|
|
19
47
|
now,
|
|
20
48
|
}).snapshot;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
49
|
+
// Validate first: an unavailable thread must never leave an imported stub.
|
|
50
|
+
try {
|
|
51
|
+
await deps.read(imported);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new RecentThreadHistoryUnavailable();
|
|
55
|
+
}
|
|
56
|
+
deps.save(imported);
|
|
57
|
+
return imported;
|
|
26
58
|
}
|
|
@@ -60,6 +60,8 @@ export function registerSessionRoutes(app, deps) {
|
|
|
60
60
|
registerStartTurn(app, {
|
|
61
61
|
find: sessions.find,
|
|
62
62
|
start: sessions.startTurn,
|
|
63
|
+
ensureWriter: sessions.ensureWriter,
|
|
64
|
+
releaseWriter: sessions.releaseWriter,
|
|
63
65
|
save: sessions.save,
|
|
64
66
|
onStarted: sessions.onTurnStarted,
|
|
65
67
|
idempotency: sessions.idempotency,
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
|
+
import { RelaySession } from '../model/relay-session.js';
|
|
8
|
+
import { WriterAcquisitionError, writerAcquisitionProblem, } from '../application/writer-acquisition.js';
|
|
7
9
|
export function registerStartTurn(app, deps) {
|
|
8
10
|
const inflight = new Map();
|
|
11
|
+
const sessionOperations = new Map();
|
|
9
12
|
// Durable idempotency results are session-scoped and replay for as long as the
|
|
10
13
|
// store retains them; a reused key with a different prompt is rejected.
|
|
11
14
|
// Text is capped at 100,000 characters; leave room for JSON encoding only.
|
|
@@ -16,7 +19,7 @@ export function registerStartTurn(app, deps) {
|
|
|
16
19
|
const text = request.body.text?.trim() ?? '';
|
|
17
20
|
if (text.length > 100_000)
|
|
18
21
|
return reply.code(400).send({ code: 'TURN_INPUT_TOO_LONG' });
|
|
19
|
-
if (!text
|
|
22
|
+
if (!text)
|
|
20
23
|
return reply.code(409).send({ code: 'SESSION_NOT_READY' });
|
|
21
24
|
const key = request.headers['idempotency-key'];
|
|
22
25
|
if (typeof key === 'string' && key && deps.idempotency) {
|
|
@@ -28,15 +31,31 @@ export function registerStartTurn(app, deps) {
|
|
|
28
31
|
const inflightKey = `${scope}:${key}`;
|
|
29
32
|
const operation = inflight.get(inflightKey) ??
|
|
30
33
|
(async () => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
return serializeSession(sessionOperations, session.id, async () => {
|
|
35
|
+
const current = deps.find(session.id);
|
|
36
|
+
if (!current)
|
|
37
|
+
return { statusCode: 404, body: JSON.stringify({ code: 'SESSION_NOT_FOUND' }) };
|
|
38
|
+
const durable = deps.idempotency.get(scope, key);
|
|
39
|
+
if (durable)
|
|
40
|
+
return durable;
|
|
41
|
+
if (current.state === 'turnActive')
|
|
42
|
+
return { statusCode: 409, body: JSON.stringify({ code: 'SESSION_TURN_ACTIVE' }) };
|
|
43
|
+
let started;
|
|
44
|
+
try {
|
|
45
|
+
started = await startWithWriter(deps, current, text, key);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error instanceof StartTurnProblem)
|
|
49
|
+
return error.result;
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
const result = {
|
|
53
|
+
statusCode: 202,
|
|
54
|
+
body: JSON.stringify({ fingerprint, response: started }),
|
|
55
|
+
};
|
|
56
|
+
deps.idempotency.put(scope, key, result.statusCode, result.body);
|
|
57
|
+
return result;
|
|
58
|
+
});
|
|
40
59
|
})();
|
|
41
60
|
inflight.set(inflightKey, operation);
|
|
42
61
|
try {
|
|
@@ -46,14 +65,113 @@ export function registerStartTurn(app, deps) {
|
|
|
46
65
|
inflight.delete(inflightKey);
|
|
47
66
|
}
|
|
48
67
|
}
|
|
49
|
-
|
|
68
|
+
return serializeSession(sessionOperations, session.id, async () => {
|
|
69
|
+
const current = deps.find(session.id);
|
|
70
|
+
if (!current)
|
|
71
|
+
return reply.code(404).send({ code: 'SESSION_NOT_FOUND' });
|
|
72
|
+
if (current.state === 'turnActive')
|
|
73
|
+
return reply.code(409).send({ code: 'SESSION_TURN_ACTIVE' });
|
|
74
|
+
try {
|
|
75
|
+
return reply
|
|
76
|
+
.code(202)
|
|
77
|
+
.send(await startWithWriter(deps, current, text, typeof key === 'string' ? key : undefined));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error instanceof StartTurnProblem)
|
|
81
|
+
return reply.code(error.result.statusCode).send(JSON.parse(error.result.body));
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
async function startWithWriter(deps, session, text, key) {
|
|
88
|
+
const writable = await ensureWriter(deps, session);
|
|
89
|
+
if ('body' in writable)
|
|
90
|
+
throw new StartTurnProblem(writable);
|
|
91
|
+
try {
|
|
92
|
+
if (writable.session !== session)
|
|
93
|
+
deps.save(writable.session);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
await cleanupAcquiredWriter(deps, writable.session, session);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const started = await deps.start(writable.session, text, key);
|
|
50
101
|
deps.save(started);
|
|
51
102
|
deps.onStarted?.(started);
|
|
52
|
-
return
|
|
103
|
+
return started;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (writable.session !== session)
|
|
107
|
+
await cleanupAcquiredWriter(deps, writable.session, session);
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
class StartTurnProblem extends Error {
|
|
112
|
+
result;
|
|
113
|
+
constructor(result) {
|
|
114
|
+
super('START_TURN_PROBLEM');
|
|
115
|
+
this.result = result;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function serializeSession(operations, sessionId, operation) {
|
|
119
|
+
const prior = operations.get(sessionId) ?? Promise.resolve();
|
|
120
|
+
let release;
|
|
121
|
+
const current = new Promise((resolve) => {
|
|
122
|
+
release = resolve;
|
|
53
123
|
});
|
|
124
|
+
operations.set(sessionId, current);
|
|
125
|
+
await prior;
|
|
126
|
+
try {
|
|
127
|
+
return await operation();
|
|
128
|
+
}
|
|
129
|
+
finally {
|
|
130
|
+
release();
|
|
131
|
+
if (operations.get(sessionId) === current)
|
|
132
|
+
operations.delete(sessionId);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Cleanup is best effort: never replace the original start/persistence error. */
|
|
136
|
+
async function cleanupAcquiredWriter(deps, acquired, original) {
|
|
137
|
+
try {
|
|
138
|
+
await deps.releaseWriter?.(acquired.id);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// Process loss is already a valid release outcome.
|
|
142
|
+
}
|
|
143
|
+
try {
|
|
144
|
+
deps.save(original);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// The original I/O failure remains the observable failure.
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function ensureWriter(deps, session) {
|
|
151
|
+
if (!deps.ensureWriter) {
|
|
152
|
+
if (session.state === 'ready')
|
|
153
|
+
return { session, replacementCreated: false };
|
|
154
|
+
return failure('runtimeUnavailable');
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
return await deps.ensureWriter(session);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (error instanceof WriterAcquisitionError &&
|
|
161
|
+
error.kind === 'writerBusy' &&
|
|
162
|
+
session.state !== 'stopped')
|
|
163
|
+
deps.save(RelaySession.rehydrate(session).stop(new Date().toISOString()).snapshot);
|
|
164
|
+
return failure(error instanceof WriterAcquisitionError ? error.kind : 'runtimeUnavailable');
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
function failure(kind) {
|
|
168
|
+
const problem = writerAcquisitionProblem(kind);
|
|
169
|
+
return { statusCode: problem.status, body: JSON.stringify(problem) };
|
|
54
170
|
}
|
|
55
171
|
function replay(result, fingerprint, reply) {
|
|
56
172
|
const cached = JSON.parse(result.body);
|
|
173
|
+
if (cached.fingerprint === undefined)
|
|
174
|
+
return reply.code(result.statusCode).send(cached);
|
|
57
175
|
if (cached.fingerprint !== fingerprint)
|
|
58
176
|
return reply.code(409).send({ code: 'IDEMPOTENCY_KEY_REUSED' });
|
|
59
177
|
return reply.code(result.statusCode).send(cached.response);
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { createInterface } from 'node:readline';
|
|
7
7
|
export const CODEX_THREAD_NOT_FOUND = 'CODEX_THREAD_NOT_FOUND';
|
|
8
8
|
export const CODEX_JSON_RPC_ERROR = 'CODEX_JSON_RPC_ERROR';
|
|
9
|
+
export const CODEX_THREAD_WRITER_BUSY = 'CODEX_THREAD_WRITER_BUSY';
|
|
9
10
|
/** A bounded representation of an app-server JSON-RPC failure. */
|
|
10
11
|
export class CodexJsonRpcError extends Error {
|
|
11
12
|
code;
|
|
@@ -25,10 +26,19 @@ export class CodexJsonRpcError extends Error {
|
|
|
25
26
|
export function isMissingCodexThreadRollout(error) {
|
|
26
27
|
return error instanceof CodexJsonRpcError && error.kind === CODEX_THREAD_NOT_FOUND;
|
|
27
28
|
}
|
|
29
|
+
/** Compatibility shim for the confirmed Codex 0.146 active-writer response. */
|
|
30
|
+
export function isCodexThreadWriterBusy(error) {
|
|
31
|
+
return error instanceof CodexJsonRpcError && error.kind === CODEX_THREAD_WRITER_BUSY;
|
|
32
|
+
}
|
|
28
33
|
function classifyCodexJsonRpcError(code, message) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
34
|
+
if (code !== -32600)
|
|
35
|
+
return CODEX_JSON_RPC_ERROR;
|
|
36
|
+
if (/^no rollout found for thread id\b/i.test(message))
|
|
37
|
+
return CODEX_THREAD_NOT_FOUND;
|
|
38
|
+
// Fixture-backed exact protocol wording: do not broaden this into a heuristic.
|
|
39
|
+
if (/^thread .* already has an active writer$/i.test(message))
|
|
40
|
+
return CODEX_THREAD_WRITER_BUSY;
|
|
41
|
+
return CODEX_JSON_RPC_ERROR;
|
|
32
42
|
}
|
|
33
43
|
function boundMessage(message) {
|
|
34
44
|
return message
|