@clubnet/seedclub 0.2.43 → 0.2.45
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/assets/SYSTEM.md +24 -13
- package/assets/extensions/seedclub/commands/seedclub.ts +1 -1
- package/assets/extensions/seedclub/tools/opportunity-research.ts +20 -105
- package/assets/extensions/seedclub/ui-copy.ts +2 -2
- package/bin/cli.js +3 -0
- package/package.json +1 -1
- package/packages/seedclub-tui/src/app/interactive-mode.mjs +29 -12
package/assets/SYSTEM.md
CHANGED
|
@@ -32,20 +32,30 @@ Bad:
|
|
|
32
32
|
|
|
33
33
|
"This is a good deal."
|
|
34
34
|
"You should invest."
|
|
35
|
-
"The right answer is to
|
|
35
|
+
"The right answer is to source."
|
|
36
36
|
|
|
37
|
-
##
|
|
37
|
+
## Investment First-Read Format
|
|
38
38
|
|
|
39
|
-
When
|
|
39
|
+
When responding to decks, memos, tweets, company links, founder pitches, or investment opportunities, do not narrate your process. Do not open with meta framing such as "Considering a concise response," "I need to," or "I should." Start with the take.
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
2. Why: 1-3 strongest reasons
|
|
43
|
-
3. Main risk or open question
|
|
44
|
-
4. What to pull on next
|
|
41
|
+
Use this order internally, but do not print the structure labels as headings:
|
|
45
42
|
|
|
46
|
-
|
|
43
|
+
1. TLDR
|
|
44
|
+
2. Verdict
|
|
45
|
+
3. Why it is interesting
|
|
46
|
+
4. Where conviction breaks
|
|
47
|
+
5. Team read
|
|
48
|
+
6. Conversational next move
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
In the visible answer, keep at most one label: `TLDR:`. The final move should be phrased as a natural question, not labeled "Conversational next move."
|
|
51
|
+
|
|
52
|
+
Keep the first read sharp and qualitative. Do not default to bulleted Founder / Company / Opportunity sections unless the user asks for extraction. Do not lead with operational deal terms such as raise size, instrument, or use of funds unless they change the investment judgment.
|
|
53
|
+
|
|
54
|
+
Pressure-test claims with named comparables when useful, such as Polymarket, Kalshi, Earnest, Stripe, Shopify, or other relevant category references. End with a conversational pull, not a data-extraction checklist. Do not offer an upsell like "want a sharper investor take?" because the first read should already be sharp.
|
|
55
|
+
|
|
56
|
+
Example shape:
|
|
57
|
+
|
|
58
|
+
"TLDR: worth digging in, but only if the team has a non-obvious distribution wedge. I read it as mixed-positive: the interesting part is not the market size claim; it is whether they can sit between existing venues and a consumer workflow that Polymarket or Kalshi will not own. Conviction breaks if the product is just a nicer front end with no supply, liquidity, or trust advantage. The team read is promising if they have real market-structure instincts, but I would pressure-test that hard. Do they already have venue access or a credible path to it?"
|
|
49
59
|
|
|
50
60
|
Operating model:
|
|
51
61
|
|
|
@@ -79,10 +89,11 @@ Seed Club platform policy:
|
|
|
79
89
|
Research and review policy:
|
|
80
90
|
|
|
81
91
|
- Treat deal, company, founder, pitch deck, memo, and research workflows as `seed-network` by default unless the user explicitly names another program.
|
|
82
|
-
- When a member drops a deck, memo, PDF, document, or image, use the Seed Club
|
|
83
|
-
-
|
|
84
|
-
- After
|
|
85
|
-
-
|
|
92
|
+
- When a member drops a deck, memo, PDF, document, or image, use the Seed Club document reader tool without asking for the program, then review the returned document preview.
|
|
93
|
+
- First answer with the Investment First-Read Format. Do not treat normal deal chat as a submission workflow.
|
|
94
|
+
- After the first read, ask only the highest-leverage conversational next question. Do not ask a long intake checklist.
|
|
95
|
+
- Treat sourcing as a high-intent explicit member action. Do not suggest push/pass/hold, do not ask whether to file a note, and do not create a shared review handoff from ordinary discussion.
|
|
96
|
+
- When the member explicitly asks what to surface, source, or send to the network, answer directly with the few highest-leverage surfaced questions or points. Keep it to 3-5 items unless they ask for more. Do not end by offering to turn it into a note, memo, post, or sharper take.
|
|
86
97
|
- If document preview is partial or failed, say what could not be read and ask targeted questions instead of inventing missing facts.
|
|
87
98
|
|
|
88
99
|
External research policy:
|
|
@@ -201,7 +201,7 @@ export function registerSeedclubCommand(pi: ExtensionAPI, deps: SeedclubDeps) {
|
|
|
201
201
|
handler: async (_args, ctx) => {
|
|
202
202
|
await prefillEditor(
|
|
203
203
|
ctx,
|
|
204
|
-
"Help me diligence this opportunity. Start by asking for the company, deck, memo, source URL, or local file if needed. Use Seed Club records and source-backed external research,
|
|
204
|
+
"Help me diligence this opportunity. Start by asking for the company, deck, memo, source URL, or local file if needed. Use Seed Club records and source-backed external research. Give me a sharp first read that covers TLDR, verdict, why it is interesting, where conviction breaks, team read, and a natural closing question, without printing those as section labels except TLDR. Pressure-test claims with named comparables and avoid generic founder/company/opportunity extraction sections unless I ask for them.",
|
|
205
205
|
);
|
|
206
206
|
},
|
|
207
207
|
});
|
|
@@ -6,8 +6,6 @@ import { ApiError, api, NotConnectedError } from "../api-client.js";
|
|
|
6
6
|
import { makeProgressCallRenderer, makeProgressResultRenderer } from "../tool-utils.js";
|
|
7
7
|
import { getToolCallLabel, getToolSuccessLabel } from "../ui-copy.js";
|
|
8
8
|
|
|
9
|
-
type OpportunityResearchDecision = "pushing" | "passing" | "holding";
|
|
10
|
-
|
|
11
9
|
const DEFAULT_OPPORTUNITY_RESEARCH_PROGRAM_SLUG = "seed-network";
|
|
12
10
|
const MAX_DOCUMENT_PREVIEW_CHARS = 12000;
|
|
13
11
|
const MAX_IMAGE_PREVIEW_BYTES = 8 * 1024 * 1024;
|
|
@@ -39,14 +37,6 @@ function normalizeOptionalNumber(value: unknown): number | null | undefined {
|
|
|
39
37
|
return value;
|
|
40
38
|
}
|
|
41
39
|
|
|
42
|
-
function normalizeStringArray(value: unknown): string[] | undefined {
|
|
43
|
-
if (value === undefined) return undefined;
|
|
44
|
-
if (!Array.isArray(value)) return [];
|
|
45
|
-
return value
|
|
46
|
-
.map((item) => (typeof item === "string" ? item.trim() : ""))
|
|
47
|
-
.filter(Boolean);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
40
|
function clipPreviewText(value: string) {
|
|
51
41
|
const normalized = value.replace(/\u0000/g, "").trim();
|
|
52
42
|
if (normalized.length <= MAX_DOCUMENT_PREVIEW_CHARS) {
|
|
@@ -146,17 +136,6 @@ async function buildDocumentPreview(fileName: string, contentType: string, bytes
|
|
|
146
136
|
};
|
|
147
137
|
}
|
|
148
138
|
|
|
149
|
-
function suggestedReviewFields() {
|
|
150
|
-
return [
|
|
151
|
-
"Founder info",
|
|
152
|
-
"Company info",
|
|
153
|
-
"Opportunity info, including ask or explicit ask unknown",
|
|
154
|
-
"Opportunity Summary",
|
|
155
|
-
"Founder/contact path or explicit unknown",
|
|
156
|
-
"Member relationship and excitement/vouch before push",
|
|
157
|
-
];
|
|
158
|
-
}
|
|
159
|
-
|
|
160
139
|
function redactPreviewForJson(preview: DocumentPreview | undefined) {
|
|
161
140
|
if (!preview) return preview;
|
|
162
141
|
const { image, ...rest } = preview;
|
|
@@ -181,70 +160,24 @@ function inferDocumentContentType(fileName: string) {
|
|
|
181
160
|
return "application/octet-stream";
|
|
182
161
|
}
|
|
183
162
|
|
|
184
|
-
function normalizeDecision(value: unknown): OpportunityResearchDecision | null {
|
|
185
|
-
if (value === "pushing" || value === "passing" || value === "holding") return value;
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
async function submitDraftPosition(programSlug: string, draftId: string, args: any, decision: OpportunityResearchDecision) {
|
|
190
|
-
return api.post<any>(`/programs/${programSlug}/opportunity-research/drafts/${draftId}/member-position`, {
|
|
191
|
-
ask_unknown: args.askUnknown === true,
|
|
192
|
-
company_name: normalizeOptionalString(args.companyName),
|
|
193
|
-
company_website_url: normalizeOptionalString(args.companyWebsiteUrl),
|
|
194
|
-
contact_info: normalizeOptionalString(args.contactInfo),
|
|
195
|
-
contact_unknown: args.contactUnknown === true,
|
|
196
|
-
decision,
|
|
197
|
-
excitement_reason: normalizeOptionalString(args.excitementReason),
|
|
198
|
-
flagged_topics: normalizeStringArray(args.flaggedTopics),
|
|
199
|
-
founder_names: normalizeStringArray(args.founderNames),
|
|
200
|
-
founder_relationship: normalizeOptionalString(args.founderRelationship),
|
|
201
|
-
founder_unknown: args.founderUnknown === true,
|
|
202
|
-
target_amount: normalizeOptionalNumber(args.targetAmount),
|
|
203
|
-
vouch_text: normalizeOptionalString(args.vouchText),
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
163
|
async function submitOpportunityResearchDocument(args: {
|
|
208
|
-
askUnknown?: boolean | null;
|
|
209
164
|
programSlug?: string | null;
|
|
210
165
|
filePath?: string | null;
|
|
211
166
|
assetKind?: "deck" | "memo" | "supporting_material" | null;
|
|
212
167
|
companyName?: string | null;
|
|
213
168
|
companyWebsiteUrl?: string | null;
|
|
214
169
|
contentType?: string | null;
|
|
215
|
-
contactInfo?: string | null;
|
|
216
|
-
contactUnknown?: boolean | null;
|
|
217
|
-
decision?: OpportunityResearchDecision | null;
|
|
218
|
-
draftId?: string | null;
|
|
219
|
-
excitementReason?: string | null;
|
|
220
170
|
fileName?: string | null;
|
|
221
|
-
flaggedTopics?: string[] | null;
|
|
222
|
-
founderNames?: string[] | null;
|
|
223
|
-
founderRelationship?: string | null;
|
|
224
|
-
founderUnknown?: boolean | null;
|
|
225
171
|
roundType?: string | null;
|
|
226
172
|
sourceUrl?: string | null;
|
|
227
173
|
targetAmount?: number | null;
|
|
228
174
|
valuation?: number | null;
|
|
229
|
-
vouchText?: string | null;
|
|
230
175
|
}, runtime?: any) {
|
|
231
176
|
try {
|
|
232
177
|
const programSlug = normalizeOptionalString(args.programSlug) ?? DEFAULT_OPPORTUNITY_RESEARCH_PROGRAM_SLUG;
|
|
233
|
-
const existingDraftId = normalizeOptionalString(args.draftId);
|
|
234
|
-
const existingDecision = normalizeDecision(args.decision);
|
|
235
|
-
if (existingDraftId) {
|
|
236
|
-
if (!existingDecision) {
|
|
237
|
-
return { draft: { id: existingDraftId }, next_action: "Ask the member whether they want to push this opportunity for review, pass, or hold it for later." };
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
runtime?.setWorkingMessage?.("Saving opportunity draft position...");
|
|
241
|
-
const positionResponse = await submitDraftPosition(programSlug, existingDraftId, args, existingDecision);
|
|
242
|
-
return { draft: { id: existingDraftId }, draft_position_result: positionResponse };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
178
|
const filePath = args.filePath;
|
|
246
179
|
if (!filePath) {
|
|
247
|
-
return { error: "filePath is required
|
|
180
|
+
return { error: "filePath is required." };
|
|
248
181
|
}
|
|
249
182
|
|
|
250
183
|
const fileStats = await stat(filePath);
|
|
@@ -255,7 +188,7 @@ async function submitOpportunityResearchDocument(args: {
|
|
|
255
188
|
const fileName = normalizeOptionalString(args.fileName) ?? basename(filePath);
|
|
256
189
|
const contentType = normalizeOptionalString(args.contentType) ?? inferDocumentContentType(fileName);
|
|
257
190
|
const assetKind = normalizeOptionalString(args.assetKind) ?? "deck";
|
|
258
|
-
runtime?.setWorkingMessage?.("
|
|
191
|
+
runtime?.setWorkingMessage?.("Preparing document context...");
|
|
259
192
|
const ingestResponse = await api.post<any>(`/programs/${programSlug}/opportunity-research/document-ingests`, {
|
|
260
193
|
asset_kind: assetKind,
|
|
261
194
|
content_type: contentType,
|
|
@@ -269,10 +202,10 @@ async function submitOpportunityResearchDocument(args: {
|
|
|
269
202
|
const uploadId = typeof ingestResponse?.upload?.id === "string" ? ingestResponse.upload.id : null;
|
|
270
203
|
|
|
271
204
|
if (!uploadUrl || !objectKey || !uploadId) {
|
|
272
|
-
return { error: "The API did not return a valid
|
|
205
|
+
return { error: "The API did not return a valid document processing target.", response: ingestResponse };
|
|
273
206
|
}
|
|
274
207
|
|
|
275
|
-
runtime?.setWorkingMessage?.("
|
|
208
|
+
runtime?.setWorkingMessage?.("Processing document...");
|
|
276
209
|
const bytes = await readFile(filePath);
|
|
277
210
|
const documentPreview = await buildDocumentPreview(fileName, contentType, bytes);
|
|
278
211
|
const uploadHeaders: Record<string, string> = {};
|
|
@@ -292,12 +225,12 @@ async function submitOpportunityResearchDocument(args: {
|
|
|
292
225
|
if (!uploadResponse.ok) {
|
|
293
226
|
const text = await uploadResponse.text().catch(() => "");
|
|
294
227
|
return {
|
|
295
|
-
error: `
|
|
228
|
+
error: `Document processing failed (${uploadResponse.status}).`,
|
|
296
229
|
details: text.slice(0, 500),
|
|
297
230
|
};
|
|
298
231
|
}
|
|
299
232
|
|
|
300
|
-
runtime?.setWorkingMessage?.("
|
|
233
|
+
runtime?.setWorkingMessage?.("Preparing document preview...");
|
|
301
234
|
const completeResponse = await api.post<any>(`/programs/${programSlug}/opportunity-research/document-ingests/${uploadId}/complete`, {
|
|
302
235
|
asset_kind: assetKind,
|
|
303
236
|
company_name: normalizeOptionalString(args.companyName),
|
|
@@ -313,15 +246,9 @@ async function submitOpportunityResearchDocument(args: {
|
|
|
313
246
|
valuation: normalizeOptionalNumber(args.valuation),
|
|
314
247
|
});
|
|
315
248
|
|
|
316
|
-
const decision = normalizeDecision(args.decision);
|
|
317
249
|
return {
|
|
318
250
|
...completeResponse,
|
|
319
|
-
decision_ignored_until_review: Boolean(decision),
|
|
320
251
|
document_preview: documentPreview,
|
|
321
|
-
next_action: decision
|
|
322
|
-
? "The document is saved as a private draft. Review the material, summarize Founder info, Company info, Opportunity info, and Opportunity Summary, then ask missing questions before submitting the member decision."
|
|
323
|
-
: "Review the material, summarize Founder info, Company info, Opportunity info, and Opportunity Summary, then ask whether the member wants to push, pass, or hold after missing context is resolved.",
|
|
324
|
-
suggested_review_fields: suggestedReviewFields(),
|
|
325
252
|
upload: {
|
|
326
253
|
...completeResponse?.upload,
|
|
327
254
|
storage_url: storageUrl ?? completeResponse?.upload?.storage_url ?? null,
|
|
@@ -360,12 +287,15 @@ function makeOpportunityResearchExecute(fn: typeof submitOpportunityResearchDocu
|
|
|
360
287
|
...result,
|
|
361
288
|
document_preview: redactPreviewForJson(result?.document_preview),
|
|
362
289
|
};
|
|
363
|
-
const
|
|
290
|
+
const modelDetails = {
|
|
291
|
+
document_preview: details.document_preview,
|
|
292
|
+
};
|
|
293
|
+
const content: any[] = [{ type: "text", text: JSON.stringify(modelDetails, null, 2) }];
|
|
364
294
|
if (image?.data && image?.mimeType) {
|
|
365
295
|
content.push({ type: "image", data: image.data, mimeType: image.mimeType });
|
|
366
296
|
}
|
|
367
297
|
|
|
368
|
-
return { content, details };
|
|
298
|
+
return { content, details: modelDetails };
|
|
369
299
|
} catch (error) {
|
|
370
300
|
if (error instanceof NotConnectedError) {
|
|
371
301
|
return {
|
|
@@ -388,42 +318,27 @@ function makeOpportunityResearchExecute(fn: typeof submitOpportunityResearchDocu
|
|
|
388
318
|
}
|
|
389
319
|
|
|
390
320
|
export function registerOpportunityResearchTools(pi: ExtensionAPI) {
|
|
391
|
-
|
|
392
|
-
name: "
|
|
393
|
-
label: "
|
|
321
|
+
const readDocumentTool: any = {
|
|
322
|
+
name: "seedclub_read_document",
|
|
323
|
+
label: "Read Document",
|
|
394
324
|
description:
|
|
395
|
-
"
|
|
325
|
+
"Read a local deck, memo, PDF, document, or image for document review context. programSlug defaults to seed-network. For a plain file path or a prompt like 'let's chat about this opportunity', read the document preview and give a sharp first read that covers TLDR, verdict, why it is interesting, where conviction breaks, team read, and a natural closing question. Do not print those as section labels except TLDR. Do not treat this as sourcing or submission. Do not use generic founder/company/opportunity extraction sections unless the user asks for extraction.",
|
|
396
326
|
parameters: Type.Object({
|
|
397
|
-
askUnknown: Type.Optional(Type.Union([Type.Boolean(), Type.Null()], { description: "Set true when the member explicitly says the round ask is unknown." })),
|
|
398
327
|
programSlug: Type.Optional(Type.Union([Type.String({ description: "Program slug for the opportunity research program. Defaults to seed-network." }), Type.Null()])),
|
|
399
|
-
filePath: Type.Optional(Type.Union([Type.String({ description: "Local deck, memo, document, or image path.
|
|
328
|
+
filePath: Type.Optional(Type.Union([Type.String({ description: "Local deck, memo, PDF, document, or image path." }), Type.Null()])),
|
|
400
329
|
assetKind: Type.Optional(Type.Union([Type.Literal("deck"), Type.Literal("memo"), Type.Literal("supporting_material"), Type.Null()])),
|
|
401
330
|
companyName: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
402
331
|
companyWebsiteUrl: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
403
332
|
contentType: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
404
|
-
contactInfo: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Founder/company contact info supplied by the member if not present in the uploaded material." })),
|
|
405
|
-
contactUnknown: Type.Optional(Type.Union([Type.Boolean(), Type.Null()], { description: "Set true when the member explicitly says contact info is unknown." })),
|
|
406
|
-
decision: Type.Optional(Type.Union([Type.Literal("pushing"), Type.Literal("passing"), Type.Literal("holding"), Type.Null()])),
|
|
407
|
-
draftId: Type.Optional(Type.Union([Type.String({ description: "Existing private opportunity draft id to update without re-uploading the file." }), Type.Null()])),
|
|
408
|
-
excitementReason: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "Why the member is excited about the company/founder." })),
|
|
409
333
|
fileName: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
410
|
-
flaggedTopics: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()])),
|
|
411
|
-
founderNames: Type.Optional(Type.Union([Type.Array(Type.String()), Type.Null()], { description: "Founder names supplied or corrected by the member." })),
|
|
412
|
-
founderRelationship: Type.Optional(Type.Union([Type.String(), Type.Null()], { description: "How the member knows the founder." })),
|
|
413
|
-
founderUnknown: Type.Optional(Type.Union([Type.Boolean(), Type.Null()], { description: "Set true when the member explicitly says founder names are unknown." })),
|
|
414
334
|
roundType: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
415
335
|
sourceUrl: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
416
336
|
targetAmount: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
417
337
|
valuation: Type.Optional(Type.Union([Type.Number(), Type.Null()])),
|
|
418
|
-
vouchText: Type.Optional(Type.Union([Type.String(), Type.Null()])),
|
|
419
338
|
}),
|
|
420
339
|
execute: makeOpportunityResearchExecute(submitOpportunityResearchDocument),
|
|
421
|
-
renderCall: makeProgressCallRenderer(getToolCallLabel("
|
|
422
|
-
renderResult: makeProgressResultRenderer(getToolSuccessLabel("
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const id = details?.draft_position_result?.funding_round?.id ?? details?.draft?.id ?? details?.draft_position_result?.draft?.id ?? null;
|
|
426
|
-
return [company, state, id].filter(Boolean).join(" · ") || undefined;
|
|
427
|
-
}),
|
|
428
|
-
});
|
|
340
|
+
renderCall: makeProgressCallRenderer(getToolCallLabel("seedclub_read_document"), (args) => args?.companyName || args?.filePath || undefined),
|
|
341
|
+
renderResult: makeProgressResultRenderer(getToolSuccessLabel("seedclub_read_document")),
|
|
342
|
+
};
|
|
343
|
+
pi.registerTool(readDocumentTool);
|
|
429
344
|
}
|
|
@@ -11,7 +11,7 @@ export const TOOL_CALL_LABELS: Record<string, string> = {
|
|
|
11
11
|
seedclub_create_crm_task: "Creating CRM task",
|
|
12
12
|
seedclub_save_research: "Saving research",
|
|
13
13
|
seedclub_research_opportunity: "Researching opportunity",
|
|
14
|
-
|
|
14
|
+
seedclub_read_document: "Reading document",
|
|
15
15
|
seedclub_get_research: "Loading research",
|
|
16
16
|
seedclub_list_program_contacts: "Loading program contacts",
|
|
17
17
|
seedclub_list_program_funnels: "Loading program funnels",
|
|
@@ -63,7 +63,7 @@ export const TOOL_SUCCESS_LABELS: Record<string, string> = {
|
|
|
63
63
|
seedclub_create_crm_task: "CRM task created",
|
|
64
64
|
seedclub_save_research: "Research saved",
|
|
65
65
|
seedclub_research_opportunity: "Research queued",
|
|
66
|
-
|
|
66
|
+
seedclub_read_document: "Document contributed to network intelligence",
|
|
67
67
|
seedclub_get_research: "Research loaded",
|
|
68
68
|
seedclub_list_program_contacts: "Program contacts loaded",
|
|
69
69
|
seedclub_list_program_funnels: "Program funnels loaded",
|
package/bin/cli.js
CHANGED
package/package.json
CHANGED
|
@@ -99,6 +99,17 @@ function firstTextLine(content) {
|
|
|
99
99
|
return text.split("\n")[0] ?? null;
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
+
function hideThinkingBlock() {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function withoutThinkingContent(message) {
|
|
107
|
+
if (!message || !Array.isArray(message.content)) return message;
|
|
108
|
+
const content = message.content.filter((item) => item?.type !== "thinking");
|
|
109
|
+
if (content.length === message.content.length) return message;
|
|
110
|
+
return { ...message, content };
|
|
111
|
+
}
|
|
112
|
+
|
|
102
113
|
function getToolStatusText(toolName, toolDefinition, args) {
|
|
103
114
|
const callMeta = getToolRenderStatusMeta(toolDefinition, "renderCall");
|
|
104
115
|
if (typeof callMeta?.label === "string" && callMeta.label.trim()) {
|
|
@@ -1217,8 +1228,8 @@ export class SeedclubInteractiveModeApp {
|
|
|
1217
1228
|
}),
|
|
1218
1229
|
});
|
|
1219
1230
|
this.editor = this.defaultEditor;
|
|
1220
|
-
|
|
1221
|
-
|
|
1231
|
+
this.pendingTools = new Map();
|
|
1232
|
+
this.streamingAssistant = undefined;
|
|
1222
1233
|
this.toolOutputExpanded = false;
|
|
1223
1234
|
this.activeOverlay = undefined;
|
|
1224
1235
|
this.customHeader = undefined;
|
|
@@ -1365,7 +1376,9 @@ export class SeedclubInteractiveModeApp {
|
|
|
1365
1376
|
await runSeedclubUpdateCommand();
|
|
1366
1377
|
await storeUpdatePrefs({ skipVersion: null });
|
|
1367
1378
|
this.setAvailableUpdate(null, null);
|
|
1368
|
-
this.
|
|
1379
|
+
await this.stop({
|
|
1380
|
+
message: "seedclub was updated.\nRun `seedclub` to start the updated version.",
|
|
1381
|
+
});
|
|
1369
1382
|
} catch (error) {
|
|
1370
1383
|
this.showExtensionNotify(
|
|
1371
1384
|
`Update failed: ${error instanceof Error ? error.message : "Update failed."}`,
|
|
@@ -1625,7 +1638,7 @@ export class SeedclubInteractiveModeApp {
|
|
|
1625
1638
|
this.chat.addChild(
|
|
1626
1639
|
new AssistantMessageComponent(
|
|
1627
1640
|
assistantMarkdownMessage(text),
|
|
1628
|
-
|
|
1641
|
+
hideThinkingBlock(),
|
|
1629
1642
|
),
|
|
1630
1643
|
);
|
|
1631
1644
|
}
|
|
@@ -1713,8 +1726,8 @@ export class SeedclubInteractiveModeApp {
|
|
|
1713
1726
|
} else if (message.role === "assistant") {
|
|
1714
1727
|
this.chat.addChild(
|
|
1715
1728
|
new AssistantMessageComponent(
|
|
1716
|
-
message,
|
|
1717
|
-
|
|
1729
|
+
withoutThinkingContent(message),
|
|
1730
|
+
hideThinkingBlock(),
|
|
1718
1731
|
),
|
|
1719
1732
|
);
|
|
1720
1733
|
} else if (message.role === "custom" && message.display !== false) {
|
|
@@ -2469,8 +2482,8 @@ export class SeedclubInteractiveModeApp {
|
|
|
2469
2482
|
this.chat.addChild(new UserMessageComponent(messageText(event.message)));
|
|
2470
2483
|
} else if (event.message.role === "assistant") {
|
|
2471
2484
|
this.streamingAssistant = new AssistantMessageComponent(
|
|
2472
|
-
event.message,
|
|
2473
|
-
|
|
2485
|
+
withoutThinkingContent(event.message),
|
|
2486
|
+
hideThinkingBlock(),
|
|
2474
2487
|
);
|
|
2475
2488
|
this.chat.addChild(this.streamingAssistant);
|
|
2476
2489
|
} else if (event.message.role === "custom" && event.message.display !== false) {
|
|
@@ -2480,12 +2493,12 @@ export class SeedclubInteractiveModeApp {
|
|
|
2480
2493
|
if (event.message.role === "assistant") {
|
|
2481
2494
|
if (!this.streamingAssistant) {
|
|
2482
2495
|
this.streamingAssistant = new AssistantMessageComponent(
|
|
2483
|
-
event.message,
|
|
2484
|
-
|
|
2496
|
+
withoutThinkingContent(event.message),
|
|
2497
|
+
hideThinkingBlock(),
|
|
2485
2498
|
);
|
|
2486
2499
|
this.chat.addChild(this.streamingAssistant);
|
|
2487
2500
|
}
|
|
2488
|
-
this.streamingAssistant.updateContent(event.message);
|
|
2501
|
+
this.streamingAssistant.updateContent(withoutThinkingContent(event.message));
|
|
2489
2502
|
}
|
|
2490
2503
|
} else if (event.type === "tool_execution_start") {
|
|
2491
2504
|
const toolDefinition = this.session.getToolDefinition?.(event.toolName);
|
|
@@ -2602,7 +2615,7 @@ export class SeedclubInteractiveModeApp {
|
|
|
2602
2615
|
}
|
|
2603
2616
|
}
|
|
2604
2617
|
|
|
2605
|
-
async stop() {
|
|
2618
|
+
async stop(options = {}) {
|
|
2606
2619
|
if (this.isShuttingDown) {
|
|
2607
2620
|
return;
|
|
2608
2621
|
}
|
|
@@ -2622,6 +2635,10 @@ export class SeedclubInteractiveModeApp {
|
|
|
2622
2635
|
this.footerDataProvider.dispose();
|
|
2623
2636
|
this.session.dispose();
|
|
2624
2637
|
this.ui.stop();
|
|
2638
|
+
if (typeof options.message === "string" && options.message.trim()) {
|
|
2639
|
+
console.log("");
|
|
2640
|
+
console.log(options.message.trim());
|
|
2641
|
+
}
|
|
2625
2642
|
process.exit(0);
|
|
2626
2643
|
}
|
|
2627
2644
|
}
|