agentgui 1.0.1060 → 1.0.1062

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.
@@ -91,8 +91,16 @@ export async function probeBackend(base) {
91
91
  // An empty path lands on the server's first allow-root. The server confines
92
92
  // every path to an allowlist root, so a 403 means the path is out of bounds.
93
93
  export async function listDir(base, dirPath) {
94
- const seg = dirPath ? '/' + encodeURIComponent(dirPath) : '';
95
- const r = await authedFetch(base + '/api/list' + seg);
94
+ // A path SEGMENT (encoded as %2F for its own slashes) is silently mangled by
95
+ // some reverse-proxy configs that normalize proxy_pass URIs - a nginx
96
+ // decode-then-reencode of the request path collapses %2F back into a literal
97
+ // slash before forwarding, so the app receives extra path segments instead
98
+ // of one opaque one, loses its leading slash on strip, and 403s every
99
+ // absolute-path listing that isn't the bare root. A query param is never
100
+ // subject to that URI-path normalization, so it survives any proxy in front
101
+ // of this app untouched.
102
+ const qs = dirPath ? '?dir=' + encodeURIComponent(dirPath) : '';
103
+ const r = await authedFetch(base + '/api/list' + qs);
96
104
  const j = await r.json().catch(() => ({}));
97
105
  if (!r.ok) {
98
106
  // Carry the HTTP status so the app can map 403/404 to plain copy (W9).
@@ -108,7 +116,10 @@ export async function listDir(base, dirPath) {
108
116
  // a 403 means out-of-bounds or unsupported type, 404 missing. Returns
109
117
  // { content, truncated }.
110
118
  export async function readFile(base, filePath) {
111
- const r = await authedFetch(base + '/api/file/' + encodeURIComponent(filePath));
119
+ // Query param, not a path segment - see listDir()'s comment above for why:
120
+ // a reverse proxy's URI normalization can silently collapse an encoded %2F
121
+ // path-segment slash back into a literal slash before forwarding.
122
+ const r = await authedFetch(base + '/api/file?path=' + encodeURIComponent(filePath));
112
123
  if (!r.ok) {
113
124
  const t = await r.text().catch(() => '');
114
125
  throw new Error(t || ('file: ' + r.status));
@@ -119,13 +130,13 @@ export async function readFile(base, filePath) {
119
130
 
120
131
  // Same-origin URL for an image file (served by /api/image, token-threaded).
121
132
  export function imageUrl(base, filePath) {
122
- return withToken(base + '/api/image/' + encodeURIComponent(filePath));
133
+ return withToken(base + '/api/image?path=' + encodeURIComponent(filePath));
123
134
  }
124
135
 
125
136
  // Same-origin download URL (served by /api/download, attachment disposition,
126
137
  // confined + token-threaded). Used by the Files row download action.
127
138
  export function downloadUrl(base, filePath) {
128
- return withToken(base + '/api/download/' + encodeURIComponent(filePath));
139
+ return withToken(base + '/api/download?path=' + encodeURIComponent(filePath));
129
140
  }
130
141
 
131
142
  // ---------- File mutations (confined server endpoints) ----------
@@ -143,10 +154,13 @@ async function mutateJSON(base, route, body) {
143
154
  return j;
144
155
  }
145
156
 
146
- // Stat a path (GET /api/stat/<encoded>) for cwd validation. Returns {ok, dir}.
157
+ // Stat a path (GET /api/stat?path=<encoded>) for cwd validation. Returns {ok, dir}.
147
158
  // Throws an Error carrying .status (403 outside roots, 404 missing).
148
159
  export async function statPath(base, p) {
149
- const r = await authedFetch(base + '/api/stat/' + encodeURIComponent(p));
160
+ // Query param, not a path segment - see the comment in listDir() above for
161
+ // why: a reverse proxy's URI normalization can silently collapse an encoded
162
+ // %2F path-segment slash back into a literal slash before forwarding.
163
+ const r = await authedFetch(base + '/api/stat?path=' + encodeURIComponent(p));
150
164
  const j = await r.json().catch(() => ({}));
151
165
  if (!r.ok) { const e = new Error(j.error || ('stat: ' + r.status)); e.status = r.status; throw e; }
152
166
  return j;
@@ -60,8 +60,14 @@
60
60
  --sky: #3A6EFF;
61
61
  /* --warn = interactive danger controls (buttons, banners, menus, error
62
62
  inputs); --danger below = passive error/status text and metrics
63
- (kpi/sparkline deltas, auth-error text). Two tokens, one convention. */
64
- --warn: #E0241A;
63
+ (kpi/sparkline deltas, auth-error text). Two tokens, one convention.
64
+ #E0241A measured 4.19:1 on --paper — fails WCAG AA 4.5:1 for the many
65
+ TEXT usages (.field-error, .ds-btn-warn, .ds-stat-val.err-rate, toast/
66
+ session error labels), not just the interactive-control (>=3:1) usages.
67
+ Darkened to clear AA on --paper (5.29:1), --paper-2 (4.78:1), and the
68
+ thebird warm-paper preset (5.25:1) while staying visually a clear
69
+ warm red. */
70
+ --warn: #C41C0C;
65
71
  /* Syntax-token aliases: light values pass 4.5:1 on paper; the dark blocks
66
72
  restore the bright dark-tuned originals. Components reference ONLY these. */
67
73
  --code-string: var(--green);
@@ -477,7 +483,7 @@
477
483
  --accent: var(--acid); --accent-fg: var(--ink); --accent-ink: var(--acid-deep);
478
484
  --accent-tint: color-mix(in oklab, var(--accent-ink) 22%, var(--bg));
479
485
  /* A paper island under a dark ancestor must not inherit the dark signal pair. */
480
- --flame: #C53E00; --amber: #7C570F; --warn: #E0241A; --sky: #3A6EFF;
486
+ --flame: #C53E00; --amber: #7C570F; --warn: #C41C0C; --sky: #3A6EFF;
481
487
  }
482
488
 
483
489
  /* thebird — warm-paper brand preset. A first-class swappable theme: it lives
@@ -951,6 +957,22 @@
951
957
  .ds-247420 .app-main > .ds-files-stack,
952
958
  .ds-247420 .app-main > .grow { flex: 1 1 auto; min-height: 0; }
953
959
  .ds-247420 .app-main.narrow { max-width: var(--measure-narrow); margin: 0 auto; }
960
+ /* Article-body heading rhythm and lede spacing, consumed by flatspace-rendered
961
+ long-form pages (docs, papers) that drop headings/panels directly into .app-main. */
962
+ .ds-247420 .app-main > h1,
963
+ .ds-247420 .app-main > h2,
964
+ .ds-247420 .app-main > h3 { margin-top: 36px; margin-bottom: 12px; }
965
+ .ds-247420 .app-main > h1:first-child { margin-top: 8px; }
966
+ .ds-247420 .app-main > .panel,
967
+ .ds-247420 .app-main > .work-detail-chips,
968
+ .ds-247420 .app-main > .cli { margin-top: 18px; margin-bottom: 18px; }
969
+ .ds-247420 .app-main .ds-lede { margin-top: 4px; margin-bottom: 18px; max-width: 64ch; line-height: 1.6; }
970
+ .ds-247420 .work-detail-chips { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; row-gap: 10px; }
971
+ @media (max-width: 720px) {
972
+ .ds-247420 .app-main > h1,
973
+ .ds-247420 .app-main > h2,
974
+ .ds-247420 .app-main > h3 { margin-top: 28px; }
975
+ }
954
976
 
955
977
  @media (min-width: 1400px) {
956
978
  /* Fluid ceiling, not a hard stop: scales from 1400px up to 1920px across the
@@ -2664,6 +2686,17 @@
2664
2686
  @media (hover: none), (pointer: coarse) {
2665
2687
  .ds-247420 .ds-modal-input { font-size: 16px; min-height: 44px; }
2666
2688
  }
2689
+ /* PromptDialog's optional roots-chip row (destination-path prompts with more
2690
+ than one allowed root) - same chip idiom the cwd editor's roots row uses. */
2691
+ .ds-247420 .ds-prompt-roots { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
2692
+ .ds-247420 .ds-prompt-root-chip {
2693
+ background: var(--bg-2); border: var(--bw-hair) solid var(--rule); color: var(--fg-2);
2694
+ border-radius: var(--r-pill); padding: 3px 10px; font: inherit; font-size: var(--fs-tiny);
2695
+ cursor: pointer;
2696
+ }
2697
+ .ds-247420 .ds-prompt-root-chip:hover { border-color: var(--accent); color: var(--fg); }
2698
+ .ds-247420 .ds-prompt-root-chip:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
2699
+ @media (pointer: coarse) { .ds-247420 .ds-prompt-root-chip { min-height: 44px; } }
2667
2700
  /* In-body modal error (role=alert): mutation failures (409/403) surface INSIDE
2668
2701
  the dialog, not behind the fixed overlay. */
2669
2702
  .ds-247420 .ds-modal-error {
@@ -7077,6 +7110,37 @@
7077
7110
  .ds-247420 .agentchat-cwd-input[aria-invalid='true'] { border-color: var(--flame); box-shadow: inset 0 0 0 var(--bw-hair) var(--flame); }
7078
7111
  .ds-247420 .agentchat-cwd-input[aria-busy='true'] { border-color: var(--fg-3); box-shadow: inset 0 0 0 var(--bw-hair) var(--fg-3); }
7079
7112
 
7113
+ /* cwd practicality upgrade: editing mode stacks the input row plus any
7114
+ optional roots/recent/browse rows the host wires, instead of a single flat
7115
+ row - each sub-row wraps independently at narrow widths. */
7116
+ .ds-247420 .agentchat-cwd-editing { flex-direction: column; align-items: stretch; }
7117
+ .ds-247420 .agentchat-cwd-row { display: flex; align-items: center; gap: var(--space-2, 8px); flex-wrap: wrap; }
7118
+ .ds-247420 .agentchat-cwd-roots, .ds-247420 .agentchat-cwd-recent {
7119
+ display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
7120
+ }
7121
+ .ds-247420 .agentchat-cwd-recent-label { font-size: var(--fs-tiny); color: var(--fg-3); }
7122
+ .ds-247420 .agentchat-cwd-chip {
7123
+ background: var(--bg-2); border: var(--bw-hair) solid var(--rule); color: var(--fg-2);
7124
+ border-radius: var(--r-pill); padding: 3px 10px; font: inherit; font-size: var(--fs-tiny);
7125
+ cursor: pointer;
7126
+ }
7127
+ .ds-247420 .agentchat-cwd-chip:hover { border-color: var(--accent); color: var(--fg); }
7128
+ .ds-247420 .agentchat-cwd-chip:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: var(--focus-offset); }
7129
+ .ds-247420 .agentchat-cwd-browse {
7130
+ border: var(--bw-hair) solid var(--rule); border-radius: var(--r-1);
7131
+ padding: var(--space-2, 8px); background: var(--bg-2);
7132
+ display: flex; flex-direction: column; gap: 6px;
7133
+ max-height: 220px;
7134
+ }
7135
+ .ds-247420 .agentchat-cwd-browse-list { overflow-y: auto; display: flex; flex-direction: column; max-height: 160px; }
7136
+ .ds-247420 .agentchat-cwd-browse-item {
7137
+ background: none; border: none; color: var(--fg-2); text-align: left;
7138
+ padding: 6px 8px; border-radius: var(--r-1); font: inherit; font-size: var(--fs-tiny);
7139
+ cursor: pointer;
7140
+ }
7141
+ .ds-247420 .agentchat-cwd-browse-item:hover, .ds-247420 .agentchat-cwd-browse-item:focus-visible { background: var(--bg-3, var(--bg)); color: var(--fg); }
7142
+ .ds-247420 .agentchat-cwd-browse-loading, .ds-247420 .agentchat-cwd-browse-empty { padding: 6px 8px; font-size: var(--fs-tiny); color: var(--fg-3); }
7143
+
7080
7144
  /* head + thread */
7081
7145
  .ds-247420 .agentchat-head {
7082
7146
  display: flex;
@@ -7277,7 +7341,7 @@
7277
7341
  }
7278
7342
  /* Coarse-pointer (touch): raise cwd button to 44px minimum tap target */
7279
7343
  @media (pointer: coarse) {
7280
- .ds-247420 .agentchat-cwd-btn { min-height: 44px; }
7344
+ .ds-247420 .agentchat-cwd-btn, .ds-247420 .agentchat-cwd-chip, .ds-247420 .agentchat-cwd-browse-item { min-height: 44px; }
7281
7345
  }
7282
7346
 
7283
7347
  /* ----------------------------------------------------------------------------
@@ -7363,7 +7427,7 @@
7363
7427
  label sticks to the top of the scroll area for Claude-Desktop-style headers. */
7364
7428
  .ds-247420 .ds-session-group { display: flex; flex-direction: column; }
7365
7429
  .ds-247420 .ds-session-group-rows { display: flex; flex-direction: column; gap: 2px; }
7366
- .ds-247420 .ds-session-group-label { position: sticky; top: 0; z-index: 1; background: var(--bg-1, var(--bg)); font-size: var(--fs-tiny); font-weight: 600; color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-caps); padding: var(--space-2) var(--space-2) var(--space-1); }
7430
+ .ds-247420 .ds-session-group-label { position: sticky; top: 0; z-index: 1; background: var(--bg-1, var(--bg)); font-size: var(--fs-tiny); font-weight: 600; color: var(--fg-3); text-transform: uppercase; letter-spacing: var(--tr-label); padding: var(--space-2) var(--space-2) var(--space-1); }
7367
7431
  .ds-247420 .ds-session-row {
7368
7432
  position: relative; display: flex; align-items: center; gap: var(--space-2);
7369
7433
  width: 100%; padding: var(--space-2); min-height: 52px; margin-bottom: 2px;
@@ -7660,15 +7724,15 @@
7660
7724
  .ds-247420 .ds-session-meta-item + .ds-session-meta-item::before { content: '\00B7'; color: var(--fg-3); margin-right: var(--space-1); }
7661
7725
  .ds-247420 .ds-session-meta-label { color: var(--fg-3); }
7662
7726
  .ds-247420 .ds-session-meta-value { font-family: var(--ff-mono); color: var(--fg-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
7663
- .ds-247420 .ds-session-meta-copy {
7727
+ .ds-247420 .ds-session-meta-copy, .ds-247420 .ds-session-meta-action {
7664
7728
  padding: 2px 8px; min-height: 24px; cursor: pointer;
7665
7729
  background: var(--bg-2); border: var(--bw-hair) solid var(--bg-3); border-radius: var(--r-1);
7666
7730
  color: var(--fg-3); font-family: var(--ff-body); font-size: var(--fs-micro);
7667
7731
  }
7668
- .ds-247420 .ds-session-meta-copy:hover { background: var(--bg-3); color: var(--fg); }
7669
- .ds-247420 .ds-session-meta-copy:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 1px; }
7732
+ .ds-247420 .ds-session-meta-copy:hover, .ds-247420 .ds-session-meta-action:hover { background: var(--bg-3); color: var(--fg); }
7733
+ .ds-247420 .ds-session-meta-copy:focus-visible, .ds-247420 .ds-session-meta-action:focus-visible { outline: var(--focus-w) solid var(--focus-color); outline-offset: 1px; }
7670
7734
  @media (pointer: coarse), (max-width: 640px) {
7671
- .ds-247420 .ds-session-meta-copy { min-height: 44px; min-width: 44px; }
7735
+ .ds-247420 .ds-session-meta-copy, .ds-247420 .ds-session-meta-action { min-height: 44px; min-width: 44px; }
7672
7736
  }
7673
7737
 
7674
7738
  /* ============================================================================
@@ -7917,7 +7981,7 @@
7917
7981
 
7918
7982
  /* Live command-center: status-bucket groups + header breakdown + heartbeat. */
7919
7983
  .ds-247420 .ds-dash-group { display: flex; flex-direction: column; gap: var(--space-2); }
7920
- .ds-247420 .ds-dash-group-label { font-size: var(--fs-tiny); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--fg-3); padding: 0 var(--space-1); }
7984
+ .ds-247420 .ds-dash-group-label { font-size: var(--fs-tiny); font-weight: 600; text-transform: uppercase; letter-spacing: var(--tr-label); color: var(--fg-3); padding: 0 var(--space-1); }
7921
7985
  .ds-247420 .ds-dash-breakdown { display: flex; align-items: center; gap: var(--space-1); font-size: var(--fs-sm); color: var(--fg-2); }
7922
7986
  /* Shape-channel discs mirror the .status-dot-disc vocabulary (solid /
7923
7987
  solid+halo / hollow ring) so a colour-blind user can scan the breakdown by