@poncho-ai/cli 0.37.0 → 0.38.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/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +264 -0
- package/dist/{chunk-GUGBKAIM.js → chunk-W7SQVUB4.js} +6166 -4694
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +197 -128
- package/dist/index.js +111 -5
- package/dist/run-interactive-ink-UKPUGCDW.js +679 -0
- package/package.json +4 -4
- package/src/cron-helpers.ts +183 -0
- package/src/http-utils.ts +220 -0
- package/src/index.ts +1071 -4754
- package/src/logger.ts +9 -0
- package/src/mcp-commands.ts +283 -0
- package/src/project-init.ts +150 -0
- package/src/run-commands.ts +145 -0
- package/src/scaffolding.ts +528 -0
- package/src/skills.ts +372 -0
- package/src/templates.ts +563 -0
- package/src/testing.ts +108 -0
- package/src/web-ui-client.ts +845 -94
- package/src/web-ui-styles.ts +269 -1
- package/src/web-ui.ts +23 -0
- package/test/cli.test.ts +52 -1
- package/dist/run-interactive-ink-75GKYSEC.js +0 -2115
- package/test/run-orchestration.test.ts +0 -171
package/src/web-ui-styles.ts
CHANGED
|
@@ -631,7 +631,7 @@ export const WEB_UI_STYLES = `
|
|
|
631
631
|
/* Messages */
|
|
632
632
|
.messages { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 24px 24px; }
|
|
633
633
|
.messages-column { max-width: 680px; margin: 0 auto; }
|
|
634
|
-
.message-row { margin-bottom: 24px; display: flex; max-width: 100%; }
|
|
634
|
+
.message-row { margin-bottom: 24px; display: flex; max-width: 100%; position: relative; }
|
|
635
635
|
.message-row.user { justify-content: flex-end; }
|
|
636
636
|
.assistant-wrap { display: flex; gap: 12px; width: 100%; min-width: 0; }
|
|
637
637
|
.assistant-avatar {
|
|
@@ -1990,6 +1990,274 @@ export const WEB_UI_STYLES = `
|
|
|
1990
1990
|
background: var(--surface-1);
|
|
1991
1991
|
}
|
|
1992
1992
|
|
|
1993
|
+
/* Thread affordances rendered as their own block in the messages column,
|
|
1994
|
+
tucked tight beneath the parent message — Slack-style indicator. */
|
|
1995
|
+
.thread-affordance-block {
|
|
1996
|
+
display: flex;
|
|
1997
|
+
flex-direction: column;
|
|
1998
|
+
gap: 0;
|
|
1999
|
+
/* Negative top margin compensates for .message-row's 24px bottom
|
|
2000
|
+
margin so the indicator sits right under the message it belongs to. */
|
|
2001
|
+
margin: -18px 0 8px 30px;
|
|
2002
|
+
}
|
|
2003
|
+
.message-row.user + .thread-affordance-block {
|
|
2004
|
+
margin-left: 0;
|
|
2005
|
+
margin-right: 12px;
|
|
2006
|
+
align-items: flex-end;
|
|
2007
|
+
}
|
|
2008
|
+
.thread-row {
|
|
2009
|
+
display: flex;
|
|
2010
|
+
align-items: center;
|
|
2011
|
+
gap: 6px;
|
|
2012
|
+
padding: 4px 6px 4px 8px;
|
|
2013
|
+
border: 0;
|
|
2014
|
+
background: transparent;
|
|
2015
|
+
font: inherit;
|
|
2016
|
+
color: var(--fg-3);
|
|
2017
|
+
cursor: pointer;
|
|
2018
|
+
text-align: left;
|
|
2019
|
+
border-radius: 8px;
|
|
2020
|
+
transition: color 0.1s, background 0.1s;
|
|
2021
|
+
width: 100%;
|
|
2022
|
+
max-width: 100%;
|
|
2023
|
+
}
|
|
2024
|
+
.thread-row:hover {
|
|
2025
|
+
color: var(--fg);
|
|
2026
|
+
background: var(--bg-bubble-user, rgba(0,0,0,0.04));
|
|
2027
|
+
}
|
|
2028
|
+
/* Under user messages, reuse the "Reply in thread" pill design. */
|
|
2029
|
+
.message-row.user + .thread-affordance-block .thread-row {
|
|
2030
|
+
width: auto;
|
|
2031
|
+
max-width: 100%;
|
|
2032
|
+
padding: 4px 6px 4px 12px;
|
|
2033
|
+
border: 1px solid var(--border, rgba(0,0,0,0.12));
|
|
2034
|
+
background: var(--bg, #fff);
|
|
2035
|
+
border-radius: 999px;
|
|
2036
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
|
2037
|
+
}
|
|
2038
|
+
.message-row.user + .thread-affordance-block .thread-row:hover {
|
|
2039
|
+
background: var(--bg, #fff);
|
|
2040
|
+
border-color: var(--accent, var(--border));
|
|
2041
|
+
color: var(--accent, var(--fg));
|
|
2042
|
+
}
|
|
2043
|
+
.message-row.user + .thread-affordance-block .thread-row-count {
|
|
2044
|
+
color: inherit;
|
|
2045
|
+
}
|
|
2046
|
+
.thread-row-count {
|
|
2047
|
+
font-size: 13px;
|
|
2048
|
+
font-weight: 500;
|
|
2049
|
+
color: var(--accent, #3b82f6);
|
|
2050
|
+
}
|
|
2051
|
+
.thread-row-meta {
|
|
2052
|
+
font-size: 12px;
|
|
2053
|
+
color: var(--fg-3);
|
|
2054
|
+
}
|
|
2055
|
+
.thread-row-delete {
|
|
2056
|
+
margin-left: auto;
|
|
2057
|
+
padding: 0 6px;
|
|
2058
|
+
border: 0;
|
|
2059
|
+
background: transparent;
|
|
2060
|
+
color: var(--fg-7, var(--fg-3));
|
|
2061
|
+
cursor: pointer;
|
|
2062
|
+
font-size: 16px;
|
|
2063
|
+
line-height: 1;
|
|
2064
|
+
opacity: 0;
|
|
2065
|
+
transition: opacity 0.15s, color 0.15s;
|
|
2066
|
+
}
|
|
2067
|
+
.thread-row:hover .thread-row-delete,
|
|
2068
|
+
.thread-pill-pair:hover .thread-row-delete,
|
|
2069
|
+
.reply-pill-wrap:hover .thread-row-delete {
|
|
2070
|
+
opacity: 1;
|
|
2071
|
+
}
|
|
2072
|
+
.thread-row-delete:hover {
|
|
2073
|
+
color: var(--fg-2);
|
|
2074
|
+
background: transparent;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
/* Reply-in-thread pill — absolutely positioned so it floats below the
|
|
2078
|
+
message row without pushing content. Position differs by role. */
|
|
2079
|
+
.reply-pill-wrap {
|
|
2080
|
+
position: absolute;
|
|
2081
|
+
z-index: 5;
|
|
2082
|
+
display: inline-flex;
|
|
2083
|
+
align-items: center;
|
|
2084
|
+
gap: 4px;
|
|
2085
|
+
opacity: 0;
|
|
2086
|
+
visibility: hidden;
|
|
2087
|
+
/* Hide is delayed so the user has time to move the mouse from the
|
|
2088
|
+
message bubble down to the pill without the pill disappearing or
|
|
2089
|
+
losing interactivity in the gap between them. */
|
|
2090
|
+
transition: opacity 0.12s 0.2s, visibility 0s 0.32s;
|
|
2091
|
+
/* Default: assistant placement (overridden for user below). */
|
|
2092
|
+
bottom: -36px;
|
|
2093
|
+
left: 36px;
|
|
2094
|
+
right: auto;
|
|
2095
|
+
}
|
|
2096
|
+
.message-row.user .reply-pill-wrap {
|
|
2097
|
+
bottom: -20px;
|
|
2098
|
+
right: 0;
|
|
2099
|
+
left: auto;
|
|
2100
|
+
}
|
|
2101
|
+
.message-row:hover .reply-pill-wrap,
|
|
2102
|
+
.reply-pill-wrap:hover {
|
|
2103
|
+
opacity: 1;
|
|
2104
|
+
visibility: visible;
|
|
2105
|
+
transition: opacity 0.12s 0s, visibility 0s 0s;
|
|
2106
|
+
}
|
|
2107
|
+
/* When the message has at least one thread, the badge stays visible at
|
|
2108
|
+
all times (no hover required) and stacks vertically when there are
|
|
2109
|
+
multiple threads. */
|
|
2110
|
+
.reply-pill-wrap.has-threads {
|
|
2111
|
+
opacity: 1;
|
|
2112
|
+
visibility: visible;
|
|
2113
|
+
flex-direction: column;
|
|
2114
|
+
align-items: flex-start;
|
|
2115
|
+
gap: 4px;
|
|
2116
|
+
transition: none;
|
|
2117
|
+
}
|
|
2118
|
+
.message-row.user .reply-pill-wrap.has-threads {
|
|
2119
|
+
align-items: flex-end;
|
|
2120
|
+
}
|
|
2121
|
+
.thread-pill-pair {
|
|
2122
|
+
position: relative;
|
|
2123
|
+
display: inline-flex;
|
|
2124
|
+
align-items: center;
|
|
2125
|
+
}
|
|
2126
|
+
.thread-pill-pair .thread-row-delete {
|
|
2127
|
+
position: absolute;
|
|
2128
|
+
left: 100%;
|
|
2129
|
+
top: 0;
|
|
2130
|
+
bottom: 0;
|
|
2131
|
+
margin: 0 0 0 4px;
|
|
2132
|
+
padding: 0 6px;
|
|
2133
|
+
display: grid;
|
|
2134
|
+
place-items: center;
|
|
2135
|
+
background: transparent;
|
|
2136
|
+
}
|
|
2137
|
+
.reply-icon-btn {
|
|
2138
|
+
display: inline-flex;
|
|
2139
|
+
align-items: center;
|
|
2140
|
+
gap: 4px;
|
|
2141
|
+
padding: 4px 10px;
|
|
2142
|
+
border: 1px solid var(--border, rgba(0,0,0,0.12));
|
|
2143
|
+
background: var(--bg, #fff);
|
|
2144
|
+
cursor: pointer;
|
|
2145
|
+
color: var(--fg-3);
|
|
2146
|
+
font-size: 12px;
|
|
2147
|
+
border-radius: 999px;
|
|
2148
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
|
|
2149
|
+
transition: color 0.1s, background 0.1s, border-color 0.1s;
|
|
2150
|
+
}
|
|
2151
|
+
.reply-icon-btn:hover {
|
|
2152
|
+
color: var(--accent, var(--fg));
|
|
2153
|
+
border-color: var(--accent, var(--border));
|
|
2154
|
+
/* Keep an opaque background — the var(--bg-bubble-user) fallback can
|
|
2155
|
+
be transparent in some themes which makes the pill see-through. */
|
|
2156
|
+
background: var(--bg, #fff);
|
|
2157
|
+
}
|
|
2158
|
+
.reply-icon-btn svg { width: 13px; height: 13px; }
|
|
2159
|
+
.thread-pill .thread-pill-count {
|
|
2160
|
+
font-weight: 500;
|
|
2161
|
+
}
|
|
2162
|
+
.thread-pill .thread-pill-meta {
|
|
2163
|
+
margin-left: 2px;
|
|
2164
|
+
color: var(--fg-3);
|
|
2165
|
+
font-weight: 400;
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
/* Thread panel — flex sibling matching the browser-panel pattern */
|
|
2169
|
+
.thread-panel-resize {
|
|
2170
|
+
width: 1px;
|
|
2171
|
+
cursor: col-resize;
|
|
2172
|
+
background: var(--border-1);
|
|
2173
|
+
flex-shrink: 0;
|
|
2174
|
+
position: relative;
|
|
2175
|
+
z-index: 10;
|
|
2176
|
+
}
|
|
2177
|
+
.thread-panel-resize::after {
|
|
2178
|
+
content: "";
|
|
2179
|
+
position: absolute;
|
|
2180
|
+
inset: 0 -3px;
|
|
2181
|
+
}
|
|
2182
|
+
.thread-panel-resize:hover,
|
|
2183
|
+
.thread-panel-resize.dragging {
|
|
2184
|
+
background: var(--fg-5);
|
|
2185
|
+
}
|
|
2186
|
+
.thread-panel {
|
|
2187
|
+
flex: 1 1 0%;
|
|
2188
|
+
min-width: 320px;
|
|
2189
|
+
background: var(--bg);
|
|
2190
|
+
display: flex;
|
|
2191
|
+
flex-direction: column;
|
|
2192
|
+
overflow: hidden;
|
|
2193
|
+
}
|
|
2194
|
+
.main-chat.has-thread {
|
|
2195
|
+
flex: 1 1 0%;
|
|
2196
|
+
min-width: 280px;
|
|
2197
|
+
}
|
|
2198
|
+
.thread-panel-header {
|
|
2199
|
+
display: flex;
|
|
2200
|
+
align-items: center;
|
|
2201
|
+
justify-content: space-between;
|
|
2202
|
+
gap: 8px;
|
|
2203
|
+
padding: 8px 12px;
|
|
2204
|
+
border-bottom: 1px solid var(--border);
|
|
2205
|
+
min-height: 40px;
|
|
2206
|
+
}
|
|
2207
|
+
.thread-panel-title {
|
|
2208
|
+
font-size: 12px;
|
|
2209
|
+
font-weight: 600;
|
|
2210
|
+
text-transform: uppercase;
|
|
2211
|
+
letter-spacing: 0.06em;
|
|
2212
|
+
color: var(--fg-tool);
|
|
2213
|
+
white-space: nowrap;
|
|
2214
|
+
}
|
|
2215
|
+
.thread-panel-close {
|
|
2216
|
+
background: none;
|
|
2217
|
+
border: none;
|
|
2218
|
+
color: var(--fg-3);
|
|
2219
|
+
font-size: 18px;
|
|
2220
|
+
cursor: pointer;
|
|
2221
|
+
width: 28px;
|
|
2222
|
+
height: 28px;
|
|
2223
|
+
padding: 0;
|
|
2224
|
+
display: grid;
|
|
2225
|
+
place-items: center;
|
|
2226
|
+
line-height: 1;
|
|
2227
|
+
border-radius: 4px;
|
|
2228
|
+
}
|
|
2229
|
+
.thread-panel-close:hover { color: var(--fg); }
|
|
2230
|
+
.thread-panel-messages {
|
|
2231
|
+
flex: 1 1 0;
|
|
2232
|
+
/* min-height: 0 is required so this flex item can shrink below its
|
|
2233
|
+
content size — without it, overflow-y:auto never engages because
|
|
2234
|
+
the element grows to fit all messages instead of scrolling. */
|
|
2235
|
+
min-height: 0;
|
|
2236
|
+
overflow-y: auto;
|
|
2237
|
+
padding: 12px 16px;
|
|
2238
|
+
}
|
|
2239
|
+
/* Match the main composer's vertical padding so both chatboxes align
|
|
2240
|
+
at the same baseline. Horizontal padding is reduced because the panel
|
|
2241
|
+
is narrower than the main pane. */
|
|
2242
|
+
.thread-composer {
|
|
2243
|
+
padding: 12px 12px 24px;
|
|
2244
|
+
}
|
|
2245
|
+
.thread-composer .composer-inner {
|
|
2246
|
+
margin: 0;
|
|
2247
|
+
}
|
|
2248
|
+
/* Thread-row delete button — matches the sidebar conversation-item delete UX */
|
|
2249
|
+
.thread-row-delete.confirming {
|
|
2250
|
+
opacity: 1 !important;
|
|
2251
|
+
padding: 0 8px;
|
|
2252
|
+
font-size: 11px;
|
|
2253
|
+
color: var(--error, #e44);
|
|
2254
|
+
background: transparent;
|
|
2255
|
+
}
|
|
2256
|
+
.thread-row-delete.confirming:hover {
|
|
2257
|
+
color: var(--error-alt, #c33);
|
|
2258
|
+
background: transparent;
|
|
2259
|
+
}
|
|
2260
|
+
|
|
1993
2261
|
/* Reduced motion */
|
|
1994
2262
|
@media (prefers-reduced-motion: reduce) {
|
|
1995
2263
|
*, *::before, *::after {
|
package/src/web-ui.ts
CHANGED
|
@@ -205,6 +205,29 @@ ${WEB_UI_STYLES}
|
|
|
205
205
|
<div id="browser-panel-placeholder" class="browser-panel-placeholder">No active browser session</div>
|
|
206
206
|
</div>
|
|
207
207
|
</aside>
|
|
208
|
+
<div id="thread-panel-resize" class="thread-panel-resize" style="display:none"></div>
|
|
209
|
+
<aside id="thread-panel" class="thread-panel" style="display:none">
|
|
210
|
+
<div class="thread-panel-header">
|
|
211
|
+
<span class="thread-panel-title">Thread</span>
|
|
212
|
+
<button id="thread-panel-close" class="thread-panel-close" title="Close thread">×</button>
|
|
213
|
+
</div>
|
|
214
|
+
<div id="thread-panel-messages" class="thread-panel-messages messages"></div>
|
|
215
|
+
<form id="thread-composer" class="composer thread-composer">
|
|
216
|
+
<div class="composer-inner">
|
|
217
|
+
<div id="thread-attachment-preview" class="attachment-preview" style="display:none"></div>
|
|
218
|
+
<div class="composer-shell">
|
|
219
|
+
<button id="thread-attach-btn" class="attach-btn" type="button" title="Attach files">
|
|
220
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
|
|
221
|
+
</button>
|
|
222
|
+
<input id="thread-file-input" type="file" multiple accept="image/*,video/*,application/pdf,.txt,.csv,.json,.html" style="display:none" />
|
|
223
|
+
<textarea id="thread-prompt" class="composer-input" placeholder="Reply in thread..." rows="1"></textarea>
|
|
224
|
+
<button id="thread-send" class="send-btn" type="submit">
|
|
225
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><path d="M8 12V4M4 7l4-4 4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
226
|
+
</button>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</form>
|
|
230
|
+
</aside>
|
|
208
231
|
</div>
|
|
209
232
|
</main>
|
|
210
233
|
</div>
|
package/test/cli.test.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
vi.mock("@poncho-ai/harness", async (importOriginal) => {
|
|
15
15
|
const actual = await importOriginal<typeof import("@poncho-ai/harness")>();
|
|
16
16
|
return {
|
|
17
|
-
|
|
17
|
+
...actual,
|
|
18
18
|
AgentHarness: class MockHarness {
|
|
19
19
|
async initialize(): Promise<void> {}
|
|
20
20
|
setSubagentManager(): void {}
|
|
@@ -140,14 +140,65 @@ vi.mock("@poncho-ai/harness", async (importOriginal) => {
|
|
|
140
140
|
},
|
|
141
141
|
createConversationStore: () => {
|
|
142
142
|
const store = new FileConversationStore(process.cwd());
|
|
143
|
+
// The test mock conversation store implements the minimum of the
|
|
144
|
+
// ConversationStore interface the CLI uses. The file store already
|
|
145
|
+
// holds the full conversation object, so the light `get` / heavy
|
|
146
|
+
// `getWithArchive` distinction doesn't matter here — both delegate
|
|
147
|
+
// to the same underlying read.
|
|
148
|
+
// FileConversationStore returns a narrow WebUiConversation; widen to
|
|
149
|
+
// the optional harness fields so the summary / snapshot projections
|
|
150
|
+
// can reference them without TS errors.
|
|
151
|
+
type LooseConv = Awaited<ReturnType<FileConversationStore["get"]>> & {
|
|
152
|
+
parentConversationId?: string;
|
|
153
|
+
pendingApprovals?: unknown[];
|
|
154
|
+
channelMeta?: { platform: string; channelId: string; platformThreadId: string };
|
|
155
|
+
_continuationMessages?: unknown[];
|
|
156
|
+
runStatus?: "running" | "idle";
|
|
157
|
+
};
|
|
158
|
+
const listSummaries = async (ownerId?: string) =>
|
|
159
|
+
((await store.list(ownerId)) as LooseConv[]).map((c) => ({
|
|
160
|
+
conversationId: c!.conversationId,
|
|
161
|
+
title: c!.title,
|
|
162
|
+
updatedAt: c!.updatedAt,
|
|
163
|
+
createdAt: c!.createdAt,
|
|
164
|
+
ownerId: c!.ownerId,
|
|
165
|
+
tenantId: c!.tenantId,
|
|
166
|
+
parentConversationId: c!.parentConversationId,
|
|
167
|
+
messageCount: c!.messages?.length ?? 0,
|
|
168
|
+
hasPendingApprovals:
|
|
169
|
+
Array.isArray(c!.pendingApprovals) && c!.pendingApprovals.length > 0,
|
|
170
|
+
channelMeta: c!.channelMeta,
|
|
171
|
+
}));
|
|
143
172
|
return {
|
|
144
173
|
list: (ownerId?: string) => store.list(ownerId),
|
|
174
|
+
listSummaries,
|
|
145
175
|
get: (conversationId: string) => store.get(conversationId),
|
|
176
|
+
getWithArchive: (conversationId: string) => store.get(conversationId),
|
|
177
|
+
getStatusSnapshot: async (conversationId: string) => {
|
|
178
|
+
const c = (await store.get(conversationId)) as LooseConv | undefined;
|
|
179
|
+
if (!c) return undefined;
|
|
180
|
+
return {
|
|
181
|
+
conversationId: c.conversationId,
|
|
182
|
+
updatedAt: c.updatedAt,
|
|
183
|
+
messageCount: c.messages?.length ?? 0,
|
|
184
|
+
hasPendingApprovals:
|
|
185
|
+
Array.isArray(c.pendingApprovals) && c.pendingApprovals.length > 0,
|
|
186
|
+
hasContinuationMessages:
|
|
187
|
+
Array.isArray(c._continuationMessages) &&
|
|
188
|
+
(c._continuationMessages?.length ?? 0) > 0,
|
|
189
|
+
parentConversationId: c.parentConversationId ?? null,
|
|
190
|
+
ownerId: c.ownerId,
|
|
191
|
+
tenantId: c.tenantId ?? null,
|
|
192
|
+
runStatus: c.runStatus ?? null,
|
|
193
|
+
};
|
|
194
|
+
},
|
|
146
195
|
create: (ownerId?: string, title?: string) => store.create(ownerId, title),
|
|
147
196
|
update: (conversation: Awaited<ReturnType<FileConversationStore["create"]>>) =>
|
|
148
197
|
store.update(conversation),
|
|
149
198
|
rename: (conversationId: string, title: string) => store.rename(conversationId, title),
|
|
150
199
|
delete: (conversationId: string) => store.delete(conversationId),
|
|
200
|
+
appendSubagentResult: async () => {},
|
|
201
|
+
clearCallbackLock: async (conversationId: string) => store.get(conversationId),
|
|
151
202
|
};
|
|
152
203
|
},
|
|
153
204
|
InMemoryStateStore: class {},
|