gestalt-mobile 0.16.4 → 0.17.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.
- package/dist/client/assets/index-DgD-0I3M.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 +11 -1
- package/dist/server/server/features/plans/get-plan/endpoint.js +2 -2
- package/dist/server/server/platform/git/git-summary-cache.js +15 -9
- package/dist/server/server/platform/plans/filesystem-plan-status-source.js +19 -12
- 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-D9v-aHYI.css +0 -1
- package/dist/client/assets/index-bKzoif3G.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-DgD-0I3M.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)
|
|
@@ -342,6 +346,12 @@ export async function composeRelayApp(options) {
|
|
|
342
346
|
planRoutes: {
|
|
343
347
|
exists: (id) => sessions.find(id) !== null,
|
|
344
348
|
find: (id) => supervisedPlans.find(id),
|
|
349
|
+
refresh: async (id) => {
|
|
350
|
+
const refreshed = await planStatusSource.refresh(id);
|
|
351
|
+
if (refreshed?.kind === 'updated')
|
|
352
|
+
return refreshed.plan;
|
|
353
|
+
return refreshed ? null : supervisedPlans.find(id);
|
|
354
|
+
},
|
|
345
355
|
removeStatus: (id) => planStatusSource.remove(id, supervisedPlans.identity(id) ?? undefined),
|
|
346
356
|
clear: (id) => supervisedPlans.clear(id),
|
|
347
357
|
closed: (id) => {
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
export function registerGetPlan(app, deps) {
|
|
7
|
-
app.get('/api/sessions/:id/plan', (request, reply) => {
|
|
7
|
+
app.get('/api/sessions/:id/plan', async (request, reply) => {
|
|
8
8
|
const id = request.params.id;
|
|
9
9
|
if (!deps.exists(id))
|
|
10
10
|
return reply.code(404).send({ code: 'SESSION_NOT_FOUND' });
|
|
11
|
-
const plan = deps.
|
|
11
|
+
const plan = await deps.refresh(id);
|
|
12
12
|
return plan ? reply.send(plan) : reply.code(204).send();
|
|
13
13
|
});
|
|
14
14
|
}
|
|
@@ -4,23 +4,29 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
|
-
/**
|
|
7
|
+
/** Coalesces concurrent inspections without retaining results across refreshes. */
|
|
8
8
|
export class GitSummaryCache {
|
|
9
9
|
inspectWorkspace;
|
|
10
|
-
|
|
10
|
+
inspections = new Map();
|
|
11
11
|
constructor(inspectWorkspace) {
|
|
12
12
|
this.inspectWorkspace = inspectWorkspace;
|
|
13
13
|
}
|
|
14
14
|
async inspect(workspacePath) {
|
|
15
15
|
const key = resolve(workspacePath);
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
return
|
|
19
|
-
const
|
|
20
|
-
this.
|
|
21
|
-
|
|
16
|
+
const pending = this.inspections.get(key);
|
|
17
|
+
if (pending)
|
|
18
|
+
return pending;
|
|
19
|
+
const inspection = this.inspectWorkspace(workspacePath);
|
|
20
|
+
this.inspections.set(key, inspection);
|
|
21
|
+
try {
|
|
22
|
+
return await inspection;
|
|
23
|
+
}
|
|
24
|
+
finally {
|
|
25
|
+
if (this.inspections.get(key) === inspection)
|
|
26
|
+
this.inspections.delete(key);
|
|
27
|
+
}
|
|
22
28
|
}
|
|
23
29
|
invalidate(workspacePath) {
|
|
24
|
-
this.
|
|
30
|
+
this.inspections.delete(resolve(workspacePath));
|
|
25
31
|
}
|
|
26
32
|
}
|
|
@@ -44,6 +44,9 @@ export class FilesystemPlanStatusSource {
|
|
|
44
44
|
lease.close();
|
|
45
45
|
this.leases.clear();
|
|
46
46
|
}
|
|
47
|
+
async refresh(sessionId) {
|
|
48
|
+
return (await this.leases.get(sessionId)?.refreshCurrent()) ?? null;
|
|
49
|
+
}
|
|
47
50
|
async remove(sessionId, identity) {
|
|
48
51
|
const statusPath = this.activeStatusPaths.get(sessionId);
|
|
49
52
|
const signal = statusPath ? await this.readStatusForRollback(statusPath) : undefined;
|
|
@@ -220,9 +223,12 @@ class ActiveLease {
|
|
|
220
223
|
this.debounce = undefined;
|
|
221
224
|
const pendingStatusPath = this.pendingStatusPath;
|
|
222
225
|
this.pendingStatusPath = undefined;
|
|
223
|
-
void (pendingStatusPath ? this.
|
|
226
|
+
void (pendingStatusPath ? this.refreshPath(pendingStatusPath) : this.refreshLatest());
|
|
224
227
|
}, 25);
|
|
225
228
|
}
|
|
229
|
+
async refreshCurrent() {
|
|
230
|
+
return this.activeStatusPath ? this.refreshPath(this.activeStatusPath) : this.refreshLatest();
|
|
231
|
+
}
|
|
226
232
|
async refreshLatest() {
|
|
227
233
|
try {
|
|
228
234
|
const candidates = (await readdir(this.statusDirectory))
|
|
@@ -239,15 +245,13 @@ class ActiveLease {
|
|
|
239
245
|
.filter((candidate) => candidate.signal !== null)
|
|
240
246
|
.sort((left, right) => right.signal.updatedAt.localeCompare(left.signal.updatedAt) ||
|
|
241
247
|
right.modifiedAt - left.modifiedAt)[0];
|
|
242
|
-
|
|
243
|
-
await this.refresh(latest.statusPath, latest.signal);
|
|
248
|
+
return latest ? this.refreshPath(latest.statusPath, latest.signal) : null;
|
|
244
249
|
}
|
|
245
250
|
catch {
|
|
246
|
-
|
|
247
|
-
this.emitUnavailable();
|
|
251
|
+
return !this.closed ? this.emitUnavailable() : null;
|
|
248
252
|
}
|
|
249
253
|
}
|
|
250
|
-
async
|
|
254
|
+
async refreshPath(statusPath, parsedSignal) {
|
|
251
255
|
try {
|
|
252
256
|
const signal = parsedSignal ?? parseSignal(await this.planReadFilesystem.readFile(statusPath, 'utf8'));
|
|
253
257
|
if (!signal)
|
|
@@ -260,7 +264,7 @@ class ActiveLease {
|
|
|
260
264
|
throw new Error('PATH_OUTSIDE_WORKSPACE');
|
|
261
265
|
const identity = createHash('sha256').update(planPath).digest('hex');
|
|
262
266
|
if (await this.isDismissed(identity))
|
|
263
|
-
return;
|
|
267
|
+
return null;
|
|
264
268
|
const result = parseSupervisedPlan({
|
|
265
269
|
source: await this.planReadFilesystem.readFile(planPath, 'utf8'),
|
|
266
270
|
planPath,
|
|
@@ -285,24 +289,27 @@ class ActiveLease {
|
|
|
285
289
|
}
|
|
286
290
|
if (previousStatusPath && previousStatusPath !== statusPath)
|
|
287
291
|
await rm(previousStatusPath, { force: true }).catch(() => { });
|
|
292
|
+
return update;
|
|
288
293
|
}
|
|
289
294
|
}
|
|
290
295
|
else if (!this.closed) {
|
|
291
|
-
this.emitUnavailable();
|
|
296
|
+
return this.emitUnavailable();
|
|
292
297
|
}
|
|
298
|
+
return null;
|
|
293
299
|
}
|
|
294
300
|
catch (error) {
|
|
295
301
|
if (error.code === 'ENOENT' &&
|
|
296
302
|
this.activeStatusPath !== undefined &&
|
|
297
303
|
statusPath !== this.activeStatusPath)
|
|
298
|
-
return;
|
|
299
|
-
|
|
300
|
-
this.emitUnavailable();
|
|
304
|
+
return null;
|
|
305
|
+
return !this.closed ? this.emitUnavailable() : null;
|
|
301
306
|
}
|
|
302
307
|
}
|
|
303
308
|
emitUnavailable() {
|
|
304
309
|
this.lastEmittedUpdate = undefined;
|
|
305
|
-
|
|
310
|
+
const update = { kind: 'unavailable', code: 'PLAN_STATUS_UNAVAILABLE' };
|
|
311
|
+
this.listener(update);
|
|
312
|
+
return update;
|
|
306
313
|
}
|
|
307
314
|
}
|
|
308
315
|
function parseSignal(source) {
|
|
@@ -4,86 +4,120 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
import { lstat, readdir, readFile, realpath, stat } from 'node:fs/promises';
|
|
7
|
-
import {
|
|
7
|
+
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
8
8
|
import { parseSupervisedPlan } from '../../features/plans/application/parse-supervised-plan.js';
|
|
9
|
-
const maximumPlans = 100;
|
|
10
9
|
const maximumBytes = 1_048_576;
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
* Passive access to every regular `.org` file below a workspace. Symlinks are
|
|
12
|
+
* never followed, and every candidate must pass the installed Org Plan helper
|
|
13
|
+
* before it is projected for the browser.
|
|
15
14
|
*/
|
|
16
15
|
export class FilesystemWorkspacePlanCatalog {
|
|
16
|
+
validator;
|
|
17
17
|
filesystem;
|
|
18
|
-
constructor(filesystem = { lstat, readdir, readFile, realpath, stat }) {
|
|
18
|
+
constructor(validator, filesystem = { lstat, readdir, readFile, realpath, stat }) {
|
|
19
|
+
this.validator = validator;
|
|
19
20
|
this.filesystem = filesystem;
|
|
20
21
|
}
|
|
21
22
|
async list(workspacePath) {
|
|
22
|
-
const
|
|
23
|
-
if (!
|
|
23
|
+
const workspace = await this.workspace(workspacePath);
|
|
24
|
+
if (!workspace)
|
|
24
25
|
return [];
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
names = (await this.filesystem.readdir(directory, { encoding: 'utf8' }))
|
|
28
|
-
.filter((name) => isPlanName(name))
|
|
29
|
-
.sort((left, right) => left.localeCompare(right));
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return [];
|
|
33
|
-
}
|
|
26
|
+
const planNames = await this.discover(workspace);
|
|
34
27
|
const entries = [];
|
|
35
|
-
for (const
|
|
36
|
-
const result = await this.
|
|
28
|
+
for (const planName of planNames) {
|
|
29
|
+
const result = await this.readFromWorkspace(workspace, planName);
|
|
37
30
|
if (result.kind !== 'available')
|
|
38
31
|
continue;
|
|
39
|
-
entries.push(toEntry(
|
|
32
|
+
entries.push(toEntry(planName, result.plan));
|
|
40
33
|
}
|
|
41
34
|
return entries;
|
|
42
35
|
}
|
|
43
36
|
async read(workspacePath, planName) {
|
|
44
|
-
if (!
|
|
37
|
+
if (!isPlanPath(planName))
|
|
45
38
|
return { kind: 'missing' };
|
|
46
|
-
const
|
|
47
|
-
if (!
|
|
39
|
+
const workspace = await this.workspace(workspacePath);
|
|
40
|
+
if (!workspace)
|
|
48
41
|
return { kind: 'missing' };
|
|
49
|
-
return this.
|
|
42
|
+
return this.readFromWorkspace(workspace, planName);
|
|
50
43
|
}
|
|
51
|
-
async
|
|
44
|
+
async workspace(workspacePath) {
|
|
52
45
|
if (!isAbsolute(workspacePath))
|
|
53
46
|
return null;
|
|
54
|
-
const workspace = resolve(workspacePath);
|
|
55
|
-
const directory = join(workspace, '.gestalt');
|
|
56
47
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
const [canonicalWorkspace, canonicalDirectory] = await Promise.all([
|
|
61
|
-
this.filesystem.realpath(workspace),
|
|
62
|
-
this.filesystem.realpath(directory),
|
|
63
|
-
]);
|
|
64
|
-
return dirname(canonicalDirectory) === canonicalWorkspace ? canonicalDirectory : null;
|
|
48
|
+
const canonical = await this.filesystem.realpath(resolve(workspacePath));
|
|
49
|
+
const metadata = await this.filesystem.stat(canonical);
|
|
50
|
+
return metadata.isDirectory() ? canonical : null;
|
|
65
51
|
}
|
|
66
52
|
catch {
|
|
67
53
|
return null;
|
|
68
54
|
}
|
|
69
55
|
}
|
|
70
|
-
async
|
|
71
|
-
const
|
|
56
|
+
async discover(workspace) {
|
|
57
|
+
const pending = [workspace];
|
|
58
|
+
const visited = new Set();
|
|
59
|
+
const plans = [];
|
|
60
|
+
while (pending.length > 0) {
|
|
61
|
+
const directory = pending.pop();
|
|
62
|
+
try {
|
|
63
|
+
const before = await this.filesystem.lstat(directory);
|
|
64
|
+
if (!before.isDirectory() || before.isSymbolicLink())
|
|
65
|
+
continue;
|
|
66
|
+
const canonical = await this.filesystem.realpath(directory);
|
|
67
|
+
if (!isWithin(workspace, canonical))
|
|
68
|
+
continue;
|
|
69
|
+
const metadata = await this.filesystem.stat(canonical);
|
|
70
|
+
if (!metadata.isDirectory() || visited.has(canonical))
|
|
71
|
+
continue;
|
|
72
|
+
visited.add(canonical);
|
|
73
|
+
const children = await this.filesystem.readdir(canonical, {
|
|
74
|
+
encoding: 'utf8',
|
|
75
|
+
withFileTypes: true,
|
|
76
|
+
});
|
|
77
|
+
for (const child of children) {
|
|
78
|
+
if (child.isSymbolicLink())
|
|
79
|
+
continue;
|
|
80
|
+
const childPath = join(canonical, child.name);
|
|
81
|
+
if (child.isDirectory())
|
|
82
|
+
pending.push(childPath);
|
|
83
|
+
else if (child.isFile() && isPlanFilename(child.name)) {
|
|
84
|
+
const planName = toPlanName(relative(workspace, childPath));
|
|
85
|
+
if (isPlanPath(planName))
|
|
86
|
+
plans.push(planName);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
// A disappearing or unreadable branch does not hide plans elsewhere.
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return plans.sort((left, right) => left.localeCompare(right));
|
|
95
|
+
}
|
|
96
|
+
async readFromWorkspace(workspace, planName) {
|
|
97
|
+
if (!isPlanPath(planName))
|
|
98
|
+
return { kind: 'missing' };
|
|
99
|
+
const path = resolve(workspace, ...planName.split('/'));
|
|
100
|
+
if (!isWithin(workspace, path))
|
|
101
|
+
return { kind: 'missing' };
|
|
72
102
|
try {
|
|
73
103
|
const before = await this.filesystem.lstat(path);
|
|
74
104
|
if (!before.isFile() || before.isSymbolicLink() || before.size > maximumBytes)
|
|
75
105
|
return { kind: 'unavailable' };
|
|
76
106
|
const canonical = await this.filesystem.realpath(path);
|
|
77
|
-
if (
|
|
107
|
+
if (!isWithin(workspace, canonical) ||
|
|
108
|
+
toPlanName(relative(workspace, canonical)) !== planName)
|
|
78
109
|
return { kind: 'unavailable' };
|
|
79
110
|
const source = await this.filesystem.readFile(canonical, 'utf8');
|
|
111
|
+
if (!(await this.validator.validate(workspace, canonical)))
|
|
112
|
+
return { kind: 'unavailable' };
|
|
80
113
|
const after = await this.filesystem.stat(canonical);
|
|
81
114
|
if (!after.isFile() ||
|
|
82
115
|
after.size !== before.size ||
|
|
83
116
|
after.ino !== before.ino ||
|
|
84
|
-
after.dev !== before.dev
|
|
117
|
+
after.dev !== before.dev ||
|
|
118
|
+
after.mtimeMs !== before.mtimeMs)
|
|
85
119
|
return { kind: 'unavailable' };
|
|
86
|
-
const parsed = parseSupervisedPlan({ source, planPath: canonical, workspacePath });
|
|
120
|
+
const parsed = parseSupervisedPlan({ source, planPath: canonical, workspacePath: workspace });
|
|
87
121
|
return parsed.kind === 'available' ? parsed : { kind: 'unavailable' };
|
|
88
122
|
}
|
|
89
123
|
catch (error) {
|
|
@@ -93,14 +127,24 @@ export class FilesystemWorkspacePlanCatalog {
|
|
|
93
127
|
}
|
|
94
128
|
}
|
|
95
129
|
}
|
|
96
|
-
function
|
|
97
|
-
return
|
|
98
|
-
|
|
130
|
+
function isPlanFilename(value) {
|
|
131
|
+
return value.endsWith('.org') && value !== '.org';
|
|
132
|
+
}
|
|
133
|
+
function isPlanPath(value) {
|
|
134
|
+
return (isPlanFilename(value) &&
|
|
99
135
|
!isAbsolute(value) &&
|
|
100
|
-
basename(value) === value &&
|
|
101
136
|
!value.includes('\\') &&
|
|
102
|
-
value !== '.' &&
|
|
103
|
-
|
|
137
|
+
value.split('/').every((part) => part !== '' && part !== '.' && part !== '..'));
|
|
138
|
+
}
|
|
139
|
+
function toPlanName(path) {
|
|
140
|
+
return sep === '/' ? path : path.split(sep).join('/');
|
|
141
|
+
}
|
|
142
|
+
function isWithin(workspace, candidate) {
|
|
143
|
+
const pathWithinWorkspace = relative(workspace, candidate);
|
|
144
|
+
return (pathWithinWorkspace === '' ||
|
|
145
|
+
(!pathWithinWorkspace.startsWith(`..${sep}`) &&
|
|
146
|
+
pathWithinWorkspace !== '..' &&
|
|
147
|
+
!isAbsolute(pathWithinWorkspace)));
|
|
104
148
|
}
|
|
105
149
|
function toEntry(planName, plan) {
|
|
106
150
|
return {
|
|
@@ -0,0 +1,62 @@
|
|
|
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
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
8
|
+
import { promisify } from 'node:util';
|
|
9
|
+
const execute = promisify(execFile);
|
|
10
|
+
export const ORG_PLAN_VALIDATION_TIMEOUT_MS = 5_000;
|
|
11
|
+
/** Runs the Org Plan skill's own validator without a shell or mutable command text. */
|
|
12
|
+
export class OrgPlanCommandValidator {
|
|
13
|
+
options;
|
|
14
|
+
helpers = new Map();
|
|
15
|
+
constructor(options = {}) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
}
|
|
18
|
+
async validate(workspacePath, planPath) {
|
|
19
|
+
const helper = await this.helper(workspacePath);
|
|
20
|
+
if (!helper)
|
|
21
|
+
return false;
|
|
22
|
+
try {
|
|
23
|
+
await (this.options.execute ?? execute)(helper, ['validate', planPath], {
|
|
24
|
+
shell: false,
|
|
25
|
+
timeout: ORG_PLAN_VALIDATION_TIMEOUT_MS,
|
|
26
|
+
maxBuffer: 64 * 1024,
|
|
27
|
+
});
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async helper(workspacePath) {
|
|
35
|
+
const workspace = resolve(workspacePath);
|
|
36
|
+
const cached = this.helpers.get(workspace);
|
|
37
|
+
if (cached)
|
|
38
|
+
return cached;
|
|
39
|
+
const discovered = this.resolveHelper(workspace);
|
|
40
|
+
this.helpers.set(workspace, discovered);
|
|
41
|
+
const helper = await discovered;
|
|
42
|
+
if (!helper)
|
|
43
|
+
this.helpers.delete(workspace);
|
|
44
|
+
return helper;
|
|
45
|
+
}
|
|
46
|
+
async resolveHelper(workspacePath) {
|
|
47
|
+
if (this.options.helperPath)
|
|
48
|
+
return isAbsolute(this.options.helperPath) ? resolve(this.options.helperPath) : null;
|
|
49
|
+
if (!this.options.discoverSkills)
|
|
50
|
+
return null;
|
|
51
|
+
try {
|
|
52
|
+
const skills = await this.options.discoverSkills(workspacePath);
|
|
53
|
+
const skill = skills.find(({ name }) => name === 'gestalt:org-plan' || name === 'org-plan');
|
|
54
|
+
return skill && isAbsolute(skill.path)
|
|
55
|
+
? join(dirname(resolve(skill.path)), 'scripts', 'org-plan')
|
|
56
|
+
: null;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|