@maintainer-pro/ai-cli 0.1.2 → 0.1.4
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/README.md +0 -1
- package/dist/index.cjs +193 -24
- package/dist/index.d.cts +45 -3
- package/dist/index.d.ts +45 -3
- package/dist/index.js +191 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,6 +58,5 @@ Chat history is stored in Maintainer Pro admin. Do not put API keys in the brows
|
|
|
58
58
|
|---------|------|
|
|
59
59
|
| [`@maintainer-pro/ai-server`](https://www.npmjs.com/package/@maintainer-pro/ai-server) | Local Node chat server (sidecar) that uses this SDK |
|
|
60
60
|
| [`@maintainer-pro/ai-ui`](https://www.npmjs.com/package/@maintainer-pro/ai-ui) | Embed widget (`ai-ui.iife.js`) — sidecar in dev, static file in prod |
|
|
61
|
-
| [`@maintainer-pro/setup`](https://www.npmjs.com/package/@maintainer-pro/setup) | Download the sandbox setup guide |
|
|
62
61
|
|
|
63
62
|
Optional SQL persistence lives on `@maintainer-pro/ai-cli/db` (install `better-sqlite3`, `postgres`, or `mysql2`).
|
package/dist/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
+
WORKING_PROVIDER: () => WORKING_PROVIDER,
|
|
33
34
|
buildClaudeUserPrompt: () => buildClaudeUserPrompt,
|
|
34
35
|
buildConversationPrompt: () => buildConversationPrompt,
|
|
35
36
|
buildCursorPrompt: () => buildCursorPrompt,
|
|
@@ -48,6 +49,7 @@ __export(index_exports, {
|
|
|
48
49
|
createToolValidator: () => createToolValidator,
|
|
49
50
|
formatClientContext: () => formatClientContext,
|
|
50
51
|
getProviderPreference: () => getProviderPreference,
|
|
52
|
+
inspectAndRepairWorkspace: () => inspectAndRepairWorkspace,
|
|
51
53
|
parseAiResponse: () => parseAiResponse,
|
|
52
54
|
providerLabel: () => providerLabel,
|
|
53
55
|
resolveCliBinary: () => resolveCliBinary,
|
|
@@ -102,7 +104,7 @@ function splitMessages(messages) {
|
|
|
102
104
|
const history = latestUserIndex > 0 ? messages.slice(0, latestUserIndex) : [];
|
|
103
105
|
return { request, history };
|
|
104
106
|
}
|
|
105
|
-
function buildUserFacingPrompt(systemPrompt, messages, context, attachmentPaths, priorConversationsContext) {
|
|
107
|
+
function buildUserFacingPrompt(systemPrompt, messages, context, attachmentPaths, priorConversationsContext, technical) {
|
|
106
108
|
const { request, history } = splitMessages(messages);
|
|
107
109
|
const historyBlock = history.length > 0 ? `Conversation so far:
|
|
108
110
|
${buildConversationPrompt(history)}
|
|
@@ -122,27 +124,30 @@ ${buildConversationPrompt(history)}
|
|
|
122
124
|
${attachmentPaths.map((p) => `- ${p}`).join("\n")}
|
|
123
125
|
|
|
124
126
|
` : "";
|
|
127
|
+
const closer = technical ? "Do the work now. Follow the requested output format exactly." : "Do the work now (or ask one short clarifying question if needed). Reply in English, non-technical and user-facing. If it's not resolving, offer a quick call.";
|
|
125
128
|
return `${systemSection}${contextSection}${priorSection}${historyBlock}${attachmentsSection}Current user request:
|
|
126
129
|
${request || "(See the attached screenshot(s).)"}
|
|
127
130
|
|
|
128
|
-
|
|
131
|
+
${closer}`;
|
|
129
132
|
}
|
|
130
|
-
function buildCursorPrompt(systemPrompt, messages, context, attachmentPaths, priorConversationsContext) {
|
|
133
|
+
function buildCursorPrompt(systemPrompt, messages, context, attachmentPaths, priorConversationsContext, technical) {
|
|
131
134
|
return buildUserFacingPrompt(
|
|
132
135
|
systemPrompt,
|
|
133
136
|
messages,
|
|
134
137
|
context,
|
|
135
138
|
attachmentPaths,
|
|
136
|
-
priorConversationsContext
|
|
139
|
+
priorConversationsContext,
|
|
140
|
+
technical
|
|
137
141
|
);
|
|
138
142
|
}
|
|
139
|
-
function buildClaudeUserPrompt(messages, context, attachmentPaths, priorConversationsContext) {
|
|
143
|
+
function buildClaudeUserPrompt(messages, context, attachmentPaths, priorConversationsContext, technical) {
|
|
140
144
|
return buildUserFacingPrompt(
|
|
141
145
|
null,
|
|
142
146
|
messages,
|
|
143
147
|
context,
|
|
144
148
|
attachmentPaths,
|
|
145
|
-
priorConversationsContext
|
|
149
|
+
priorConversationsContext,
|
|
150
|
+
technical
|
|
146
151
|
);
|
|
147
152
|
}
|
|
148
153
|
|
|
@@ -255,7 +260,8 @@ async function callClaudeCli(command, messages, context, options) {
|
|
|
255
260
|
messages,
|
|
256
261
|
context,
|
|
257
262
|
options.attachmentPaths,
|
|
258
|
-
options.priorConversationsContext
|
|
263
|
+
options.priorConversationsContext,
|
|
264
|
+
options.technical
|
|
259
265
|
);
|
|
260
266
|
const resolved = resolveCliCommand("claude", command);
|
|
261
267
|
const workspace = options.workspaceDir ?? process.env.AI_CLI_WORKSPACE ?? process.cwd();
|
|
@@ -294,7 +300,8 @@ async function callCursorCli(command, messages, context, options) {
|
|
|
294
300
|
messages,
|
|
295
301
|
context,
|
|
296
302
|
options.attachmentPaths,
|
|
297
|
-
options.priorConversationsContext
|
|
303
|
+
options.priorConversationsContext,
|
|
304
|
+
options.technical
|
|
298
305
|
);
|
|
299
306
|
const resolved = resolveCliCommand("cursor", command);
|
|
300
307
|
const workspace = options.workspaceDir ?? process.env.AI_CLI_WORKSPACE ?? process.cwd();
|
|
@@ -335,7 +342,8 @@ async function callAntigravityCli(command, messages, context, options) {
|
|
|
335
342
|
messages,
|
|
336
343
|
context,
|
|
337
344
|
options.attachmentPaths,
|
|
338
|
-
options.priorConversationsContext
|
|
345
|
+
options.priorConversationsContext,
|
|
346
|
+
options.technical
|
|
339
347
|
);
|
|
340
348
|
const prompt = `You are operating as a coding agent with full permission to read and edit files in this workspace. Do not introduce yourself. Do not ask what you are. Execute the user's latest request now (edit files and/or emit runtime tool JSON as instructed).
|
|
341
349
|
|
|
@@ -592,7 +600,9 @@ async function buildPriorConversationsContext(db, options) {
|
|
|
592
600
|
).slice(0, maxConversations) : ranked;
|
|
593
601
|
const blocks = selected.map((c) => {
|
|
594
602
|
const slice = c.messages.length > maxMessages ? c.messages.slice(-maxMessages) : c.messages;
|
|
595
|
-
const lines = slice.filter(
|
|
603
|
+
const lines = slice.filter(
|
|
604
|
+
(m) => (m.role === "user" || m.role === "assistant") && m.provider !== "working" && Boolean(m.content?.trim())
|
|
605
|
+
).map(
|
|
596
606
|
(m) => `${m.role === "user" ? "Human" : "Assistant"}: ${truncate(m.content)}`
|
|
597
607
|
);
|
|
598
608
|
const when = c.updatedAt ? ` (updated ${c.updatedAt.slice(0, 10)})` : "";
|
|
@@ -625,6 +635,18 @@ function createToolValidator(schemas) {
|
|
|
625
635
|
}
|
|
626
636
|
|
|
627
637
|
// src/http/handler.ts
|
|
638
|
+
var WORKING_PROVIDER = "working";
|
|
639
|
+
async function setWorkingMessage(db, conversationId) {
|
|
640
|
+
await db.ensureConversation(conversationId);
|
|
641
|
+
await db.clearWorkingMessages?.(conversationId);
|
|
642
|
+
await db.saveMessage({
|
|
643
|
+
conversationId,
|
|
644
|
+
role: "assistant",
|
|
645
|
+
content: "",
|
|
646
|
+
provider: WORKING_PROVIDER,
|
|
647
|
+
senderType: "ai"
|
|
648
|
+
});
|
|
649
|
+
}
|
|
628
650
|
var SHARED_CONVERSATION_ID = "shared";
|
|
629
651
|
var conversationTurnSeq = /* @__PURE__ */ new Map();
|
|
630
652
|
function beginConversationTurn(conversationId) {
|
|
@@ -697,6 +719,8 @@ function createChatHandler(options) {
|
|
|
697
719
|
}
|
|
698
720
|
},
|
|
699
721
|
async POST(request) {
|
|
722
|
+
let trackedConversationId;
|
|
723
|
+
let trackedTurn = 0;
|
|
700
724
|
try {
|
|
701
725
|
const body = await request.json();
|
|
702
726
|
const conversationId = await resolveSharedConversationId(
|
|
@@ -704,21 +728,16 @@ function createChatHandler(options) {
|
|
|
704
728
|
options,
|
|
705
729
|
body.conversationId
|
|
706
730
|
);
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (!conversationId
|
|
731
|
+
trackedConversationId = conversationId;
|
|
732
|
+
if (body.type === "working-clear") {
|
|
733
|
+
if (!conversationId) {
|
|
710
734
|
return Response.json(
|
|
711
|
-
{ error: "conversationId
|
|
735
|
+
{ error: "conversationId is required" },
|
|
712
736
|
{ status: 400 }
|
|
713
737
|
);
|
|
714
738
|
}
|
|
715
|
-
if (options.db) {
|
|
716
|
-
await options.db.
|
|
717
|
-
await options.db.saveMessage({
|
|
718
|
-
conversationId,
|
|
719
|
-
role: "assistant",
|
|
720
|
-
content
|
|
721
|
-
});
|
|
739
|
+
if (options.db?.clearWorkingMessages) {
|
|
740
|
+
await options.db.clearWorkingMessages(conversationId);
|
|
722
741
|
}
|
|
723
742
|
return Response.json({ ok: true, conversationId });
|
|
724
743
|
}
|
|
@@ -766,7 +785,8 @@ function createChatHandler(options) {
|
|
|
766
785
|
);
|
|
767
786
|
persistAttachmentPaths = attachmentPaths;
|
|
768
787
|
}
|
|
769
|
-
const turn = beginConversationTurn(conversationId);
|
|
788
|
+
const turn = beginConversationTurn(conversationId ?? SHARED_CONVERSATION_ID);
|
|
789
|
+
trackedTurn = turn;
|
|
770
790
|
const signal = request.signal;
|
|
771
791
|
if (options.db && conversationId) {
|
|
772
792
|
const latest = messages[messages.length - 1];
|
|
@@ -782,13 +802,26 @@ function createChatHandler(options) {
|
|
|
782
802
|
senderName: body.senderName?.trim() || void 0
|
|
783
803
|
});
|
|
784
804
|
}
|
|
805
|
+
await setWorkingMessage(options.db, conversationId);
|
|
785
806
|
}
|
|
786
807
|
const latestUser = [...messages].reverse().find((m) => m.role === "user");
|
|
787
808
|
const priorConversationsContext = options.db ? await buildPriorConversationsContext(options.db, {
|
|
788
809
|
excludeConversationId: conversationId,
|
|
789
810
|
currentRequest: latestUser?.content ?? ""
|
|
790
811
|
}) : "";
|
|
791
|
-
if (
|
|
812
|
+
if (!isActiveConversationTurn(
|
|
813
|
+
conversationId ?? SHARED_CONVERSATION_ID,
|
|
814
|
+
turn
|
|
815
|
+
)) {
|
|
816
|
+
return Response.json({
|
|
817
|
+
superseded: true,
|
|
818
|
+
conversationId: conversationId ?? null
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
if (signal.aborted) {
|
|
822
|
+
if (options.db?.clearWorkingMessages && conversationId) {
|
|
823
|
+
await options.db.clearWorkingMessages(conversationId);
|
|
824
|
+
}
|
|
792
825
|
return Response.json({
|
|
793
826
|
superseded: true,
|
|
794
827
|
conversationId: conversationId ?? null
|
|
@@ -799,7 +832,10 @@ function createChatHandler(options) {
|
|
|
799
832
|
attachmentPaths,
|
|
800
833
|
priorConversationsContext: priorConversationsContext || void 0
|
|
801
834
|
});
|
|
802
|
-
if (!isActiveConversationTurn(
|
|
835
|
+
if (!isActiveConversationTurn(
|
|
836
|
+
conversationId ?? SHARED_CONVERSATION_ID,
|
|
837
|
+
turn
|
|
838
|
+
)) {
|
|
803
839
|
return Response.json({
|
|
804
840
|
superseded: true,
|
|
805
841
|
conversationId: conversationId ?? null
|
|
@@ -811,6 +847,7 @@ function createChatHandler(options) {
|
|
|
811
847
|
}
|
|
812
848
|
if (options.db && conversationId) {
|
|
813
849
|
await options.db.ensureConversation(conversationId);
|
|
850
|
+
await options.db.clearWorkingMessages?.(conversationId);
|
|
814
851
|
await options.db.saveMessage({
|
|
815
852
|
conversationId,
|
|
816
853
|
role: "assistant",
|
|
@@ -834,6 +871,11 @@ function createChatHandler(options) {
|
|
|
834
871
|
});
|
|
835
872
|
} catch (err) {
|
|
836
873
|
console.error("Chat API error:", err);
|
|
874
|
+
if (trackedConversationId && options.db?.clearWorkingMessages && isActiveConversationTurn(trackedConversationId, trackedTurn)) {
|
|
875
|
+
await options.db.clearWorkingMessages(trackedConversationId).catch(
|
|
876
|
+
() => void 0
|
|
877
|
+
);
|
|
878
|
+
}
|
|
837
879
|
return Response.json(
|
|
838
880
|
{
|
|
839
881
|
error: "Sorry about that \u2014 I couldn't finish this right now. Happy to jump on a quick call if you'd like help \u2014 just let us know when works."
|
|
@@ -903,6 +945,16 @@ function createLocalDirectoryStore(baseDir) {
|
|
|
903
945
|
await writeConversation(filePath, existing);
|
|
904
946
|
return message;
|
|
905
947
|
},
|
|
948
|
+
async clearWorkingMessages(conversationId) {
|
|
949
|
+
const filePath = fileFor(conversationId);
|
|
950
|
+
const existing = await readConversation(filePath);
|
|
951
|
+
if (!existing) return;
|
|
952
|
+
const next = existing.messages.filter((m) => m.provider !== "working");
|
|
953
|
+
if (next.length === existing.messages.length) return;
|
|
954
|
+
existing.messages = next;
|
|
955
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
956
|
+
await writeConversation(filePath, existing);
|
|
957
|
+
},
|
|
906
958
|
async listMessages(conversationId) {
|
|
907
959
|
const existing = await readConversation(fileFor(conversationId));
|
|
908
960
|
return existing?.messages ?? [];
|
|
@@ -1009,6 +1061,15 @@ function createMaintainerProStore(options) {
|
|
|
1009
1061
|
);
|
|
1010
1062
|
return data.message;
|
|
1011
1063
|
},
|
|
1064
|
+
async clearWorkingMessages(conversationId) {
|
|
1065
|
+
await mpFetch(
|
|
1066
|
+
baseUrl,
|
|
1067
|
+
apiKey,
|
|
1068
|
+
`/api/v1/store/conversations/${encodeURIComponent(conversationId)}/messages?provider=working`,
|
|
1069
|
+
{ method: "DELETE" },
|
|
1070
|
+
fetchImpl
|
|
1071
|
+
);
|
|
1072
|
+
},
|
|
1012
1073
|
async listMessages(conversationId) {
|
|
1013
1074
|
const data = await mpFetch(
|
|
1014
1075
|
baseUrl,
|
|
@@ -1088,8 +1149,115 @@ function createMaintainerProStoreFromEnv() {
|
|
|
1088
1149
|
if (!baseUrl || !apiKey) return null;
|
|
1089
1150
|
return createMaintainerProStore({ baseUrl, apiKey });
|
|
1090
1151
|
}
|
|
1152
|
+
|
|
1153
|
+
// src/workspace-inspect.ts
|
|
1154
|
+
var import_zod = require("zod");
|
|
1155
|
+
var inspectJsonSchema = import_zod.z.object({
|
|
1156
|
+
kind: import_zod.z.enum(["next", "vite", "html", "empty", "other"]).optional(),
|
|
1157
|
+
name: import_zod.z.string().max(200).optional(),
|
|
1158
|
+
summary: import_zod.z.string().max(800).optional(),
|
|
1159
|
+
scripts: import_zod.z.object({
|
|
1160
|
+
ui: import_zod.z.string().min(1).max(64).optional(),
|
|
1161
|
+
backend: import_zod.z.string().min(1).max(64).optional(),
|
|
1162
|
+
app: import_zod.z.string().min(1).max(64).optional()
|
|
1163
|
+
}).optional(),
|
|
1164
|
+
ports: import_zod.z.object({
|
|
1165
|
+
ui: import_zod.z.number().int().min(1).max(65535).optional(),
|
|
1166
|
+
backend: import_zod.z.number().int().min(1).max(65535).optional(),
|
|
1167
|
+
app: import_zod.z.number().int().min(1).max(65535).optional()
|
|
1168
|
+
}).optional(),
|
|
1169
|
+
issues: import_zod.z.array(import_zod.z.string().max(500)).max(20).optional(),
|
|
1170
|
+
fixes: import_zod.z.array(import_zod.z.string().max(500)).max(20).optional(),
|
|
1171
|
+
ready: import_zod.z.boolean().optional()
|
|
1172
|
+
});
|
|
1173
|
+
function extractInspectJson(text) {
|
|
1174
|
+
const fence = text.match(/```json\s*([\s\S]*?)```/i);
|
|
1175
|
+
const raw = fence?.[1]?.trim() || text.match(/\{[\s\S]*\}/)?.[0];
|
|
1176
|
+
if (!raw) return null;
|
|
1177
|
+
try {
|
|
1178
|
+
const parsed = inspectJsonSchema.safeParse(JSON.parse(raw));
|
|
1179
|
+
return parsed.success ? parsed.data : null;
|
|
1180
|
+
} catch {
|
|
1181
|
+
return null;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function setupSystemPrompt(input) {
|
|
1185
|
+
return `You are Maintainer Pro's local setup assistant on the developer's machine.
|
|
1186
|
+
|
|
1187
|
+
Workspace: ${input.workspaceDir}
|
|
1188
|
+
App name: ${input.appName || "the app"}
|
|
1189
|
+
|
|
1190
|
+
Inspect this repository (package.json, README, lockfiles, existing env files, how the UI and API start).
|
|
1191
|
+
You MAY edit files to fix setup problems that would block local run or the chat widget (env keys, widget mount, missing config).
|
|
1192
|
+
Do NOT start long-lived dev servers, tunnels, or install global tools.
|
|
1193
|
+
Do NOT run \`npm run dev\` or similar.
|
|
1194
|
+
|
|
1195
|
+
Script names you report MUST already exist in package.json. Omit chat/ai-server scripts.
|
|
1196
|
+
|
|
1197
|
+
End with a short human summary AND one \`\`\`json fence with:
|
|
1198
|
+
|
|
1199
|
+
{
|
|
1200
|
+
"kind": "next" | "vite" | "html" | "empty" | "other",
|
|
1201
|
+
"name": "package or folder name",
|
|
1202
|
+
"summary": "one sentence about this project",
|
|
1203
|
+
"scripts": { "ui": "dev:client", "backend": "dev:server" },
|
|
1204
|
+
"ports": { "ui": 5173, "backend": 4100 },
|
|
1205
|
+
"issues": ["remaining problems"],
|
|
1206
|
+
"fixes": ["what you changed"],
|
|
1207
|
+
"ready": true
|
|
1208
|
+
}`;
|
|
1209
|
+
}
|
|
1210
|
+
function setupUserMessage(input) {
|
|
1211
|
+
if (input.problem?.trim()) {
|
|
1212
|
+
return `The local bridge failed to keep this project running:
|
|
1213
|
+
|
|
1214
|
+
${input.problem.trim()}
|
|
1215
|
+
|
|
1216
|
+
${input.extraContext ? `${input.extraContext.trim()}
|
|
1217
|
+
|
|
1218
|
+
` : ""}Inspect the repo, fix the cause if you can, and return the JSON report.`;
|
|
1219
|
+
}
|
|
1220
|
+
return `${input.extraContext ? `${input.extraContext.trim()}
|
|
1221
|
+
|
|
1222
|
+
` : ""}Inspect this project, fix any setup gaps you can, and return the JSON report.`;
|
|
1223
|
+
}
|
|
1224
|
+
async function inspectAndRepairWorkspace(input) {
|
|
1225
|
+
const workspaceDir = input.workspaceDir;
|
|
1226
|
+
const provider = await resolveProvider({ preference: "auto" });
|
|
1227
|
+
const response = await callAi(
|
|
1228
|
+
[{ role: "user", content: setupUserMessage(input) }],
|
|
1229
|
+
{
|
|
1230
|
+
route: "/local-setup",
|
|
1231
|
+
pageTitle: input.appName || "Local setup",
|
|
1232
|
+
relevantFiles: ["package.json", "README.md", ".env", ".env.example"],
|
|
1233
|
+
data: {
|
|
1234
|
+
workspaceDir,
|
|
1235
|
+
problem: input.problem || null
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1238
|
+
{
|
|
1239
|
+
systemPrompt: setupSystemPrompt(input),
|
|
1240
|
+
workspaceDir,
|
|
1241
|
+
technical: true
|
|
1242
|
+
}
|
|
1243
|
+
);
|
|
1244
|
+
const parsed = extractInspectJson(response.text);
|
|
1245
|
+
return {
|
|
1246
|
+
kind: parsed?.kind ?? "other",
|
|
1247
|
+
name: parsed?.name?.trim() || input.appName || "",
|
|
1248
|
+
summary: parsed?.summary?.trim() || response.text.replace(/```json[\s\S]*```/i, "").trim().slice(0, 400),
|
|
1249
|
+
scripts: parsed?.scripts ?? {},
|
|
1250
|
+
ports: parsed?.ports ?? {},
|
|
1251
|
+
issues: parsed?.issues ?? [],
|
|
1252
|
+
fixes: parsed?.fixes ?? [],
|
|
1253
|
+
ready: parsed?.ready ?? parsed?.issues?.length === 0,
|
|
1254
|
+
provider: response.provider || provider.id,
|
|
1255
|
+
rawText: response.text
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1091
1258
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1092
1259
|
0 && (module.exports = {
|
|
1260
|
+
WORKING_PROVIDER,
|
|
1093
1261
|
buildClaudeUserPrompt,
|
|
1094
1262
|
buildConversationPrompt,
|
|
1095
1263
|
buildCursorPrompt,
|
|
@@ -1108,6 +1276,7 @@ function createMaintainerProStoreFromEnv() {
|
|
|
1108
1276
|
createToolValidator,
|
|
1109
1277
|
formatClientContext,
|
|
1110
1278
|
getProviderPreference,
|
|
1279
|
+
inspectAndRepairWorkspace,
|
|
1111
1280
|
parseAiResponse,
|
|
1112
1281
|
providerLabel,
|
|
1113
1282
|
resolveCliBinary,
|
package/dist/index.d.cts
CHANGED
|
@@ -56,6 +56,8 @@ interface CallAiOptions {
|
|
|
56
56
|
priorConversationsContext?: string;
|
|
57
57
|
/** Extra / override providers (e.g. future HTTP model backends). */
|
|
58
58
|
providers?: AiProvider[];
|
|
59
|
+
/** When true, skip the end-user chat footer so the agent can return structured output. */
|
|
60
|
+
technical?: boolean;
|
|
59
61
|
}
|
|
60
62
|
interface AiProvider {
|
|
61
63
|
id: string;
|
|
@@ -85,9 +87,9 @@ declare function parseAiResponse(raw: string, provider: AiCliProviderId): AiResp
|
|
|
85
87
|
declare function buildConversationPrompt(messages: ChatMessage[]): string;
|
|
86
88
|
declare function formatClientContext(context?: ClientContext): string;
|
|
87
89
|
/** Full prompt for providers without a native --system-prompt flag (Cursor). */
|
|
88
|
-
declare function buildCursorPrompt(systemPrompt: string, messages: ChatMessage[], context?: ClientContext, attachmentPaths?: string[], priorConversationsContext?: string): string;
|
|
90
|
+
declare function buildCursorPrompt(systemPrompt: string, messages: ChatMessage[], context?: ClientContext, attachmentPaths?: string[], priorConversationsContext?: string, technical?: boolean): string;
|
|
89
91
|
/** Conversation + context for Claude (system prompt passed separately). */
|
|
90
|
-
declare function buildClaudeUserPrompt(messages: ChatMessage[], context?: ClientContext, attachmentPaths?: string[], priorConversationsContext?: string): string;
|
|
92
|
+
declare function buildClaudeUserPrompt(messages: ChatMessage[], context?: ClientContext, attachmentPaths?: string[], priorConversationsContext?: string, technical?: boolean): string;
|
|
91
93
|
|
|
92
94
|
declare function createDefaultSystemPrompt(input: {
|
|
93
95
|
productDescription: string;
|
|
@@ -111,6 +113,8 @@ declare function createToolValidator(schemas: ToolSchemaMap): (toolCall: ToolCal
|
|
|
111
113
|
error: string;
|
|
112
114
|
};
|
|
113
115
|
|
|
116
|
+
/** Stored on assistant rows while a single in-flight AI turn is running. */
|
|
117
|
+
declare const WORKING_PROVIDER = "working";
|
|
114
118
|
/** Minimal persistence contract (file store, SQL, or Maintainer Pro). */
|
|
115
119
|
interface ChatStoreLike {
|
|
116
120
|
ensureConversation(id: string): Promise<void>;
|
|
@@ -133,6 +137,8 @@ interface ChatStoreLike {
|
|
|
133
137
|
senderType?: "client" | "developer" | "ai" | null;
|
|
134
138
|
senderName?: string | null;
|
|
135
139
|
}>>;
|
|
140
|
+
/** Remove in-progress working placeholders (at most one per conversation). */
|
|
141
|
+
clearWorkingMessages?(conversationId: string): Promise<void>;
|
|
136
142
|
/** All conversations under the store (for prior-chat context). */
|
|
137
143
|
listConversations?(): Promise<Array<{
|
|
138
144
|
id: string;
|
|
@@ -141,6 +147,7 @@ interface ChatStoreLike {
|
|
|
141
147
|
role: string;
|
|
142
148
|
content: string;
|
|
143
149
|
createdAt?: string | Date;
|
|
150
|
+
provider?: string | null;
|
|
144
151
|
}>;
|
|
145
152
|
}>>;
|
|
146
153
|
saveToolEvents?(conversationId: string, events: Array<{
|
|
@@ -236,4 +243,39 @@ interface PriorConversationOptions {
|
|
|
236
243
|
*/
|
|
237
244
|
declare function buildPriorConversationsContext(db: ChatStoreLike, options: PriorConversationOptions): Promise<string>;
|
|
238
245
|
|
|
239
|
-
|
|
246
|
+
type WorkspaceKind = "next" | "vite" | "html" | "empty" | "other";
|
|
247
|
+
interface WorkspaceInspectInput {
|
|
248
|
+
workspaceDir: string;
|
|
249
|
+
appName?: string;
|
|
250
|
+
/** When set, the agent diagnoses and tries to fix this failure. */
|
|
251
|
+
problem?: string;
|
|
252
|
+
extraContext?: string;
|
|
253
|
+
}
|
|
254
|
+
interface WorkspaceInspectResult {
|
|
255
|
+
kind: WorkspaceKind;
|
|
256
|
+
name: string;
|
|
257
|
+
summary: string;
|
|
258
|
+
scripts: {
|
|
259
|
+
ui?: string;
|
|
260
|
+
backend?: string;
|
|
261
|
+
app?: string;
|
|
262
|
+
};
|
|
263
|
+
ports: {
|
|
264
|
+
ui?: number;
|
|
265
|
+
backend?: number;
|
|
266
|
+
app?: number;
|
|
267
|
+
};
|
|
268
|
+
issues: string[];
|
|
269
|
+
fixes: string[];
|
|
270
|
+
ready: boolean;
|
|
271
|
+
provider: string;
|
|
272
|
+
rawText: string;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Ask the coding-agent CLI (via callAi) to inspect a workspace and optionally
|
|
276
|
+
* repair setup issues. Used by ai-bridge so host-process planning uses the
|
|
277
|
+
* same provider resolution as chat.
|
|
278
|
+
*/
|
|
279
|
+
declare function inspectAndRepairWorkspace(input: WorkspaceInspectInput): Promise<WorkspaceInspectResult>;
|
|
280
|
+
|
|
281
|
+
export { type AiCliProviderId, type AiProvider, type AiResponse, type CallAiOptions, type ChatAttachment, type ChatHandlerOptions, type ChatHandlers, type ChatMessage, type ChatSenderType, type ChatStoreLike, type ChatUser, type ClientContext, type LocalStoredMessage, type MaintainerProStoreOptions, type PriorConversationOptions, type ProviderPreference, type ToolCall, type ToolSchemaMap, WORKING_PROVIDER, type WorkspaceInspectInput, type WorkspaceInspectResult, type WorkspaceKind, buildClaudeUserPrompt, buildConversationPrompt, buildCursorPrompt, buildPriorConversationsContext, callAi, commandExists, createAntigravityProvider, createBuiltinProviders, createChatHandler, createClaudeProvider, createCursorProvider, createDefaultSystemPrompt, createLocalDirectoryStore, createMaintainerProStore, createMaintainerProStoreFromEnv, createToolValidator, formatClientContext, getProviderPreference, inspectAndRepairWorkspace, parseAiResponse, providerLabel, resolveCliBinary, resolveProvider, saveChatAttachments, toNextRoute };
|
package/dist/index.d.ts
CHANGED
|
@@ -56,6 +56,8 @@ interface CallAiOptions {
|
|
|
56
56
|
priorConversationsContext?: string;
|
|
57
57
|
/** Extra / override providers (e.g. future HTTP model backends). */
|
|
58
58
|
providers?: AiProvider[];
|
|
59
|
+
/** When true, skip the end-user chat footer so the agent can return structured output. */
|
|
60
|
+
technical?: boolean;
|
|
59
61
|
}
|
|
60
62
|
interface AiProvider {
|
|
61
63
|
id: string;
|
|
@@ -85,9 +87,9 @@ declare function parseAiResponse(raw: string, provider: AiCliProviderId): AiResp
|
|
|
85
87
|
declare function buildConversationPrompt(messages: ChatMessage[]): string;
|
|
86
88
|
declare function formatClientContext(context?: ClientContext): string;
|
|
87
89
|
/** Full prompt for providers without a native --system-prompt flag (Cursor). */
|
|
88
|
-
declare function buildCursorPrompt(systemPrompt: string, messages: ChatMessage[], context?: ClientContext, attachmentPaths?: string[], priorConversationsContext?: string): string;
|
|
90
|
+
declare function buildCursorPrompt(systemPrompt: string, messages: ChatMessage[], context?: ClientContext, attachmentPaths?: string[], priorConversationsContext?: string, technical?: boolean): string;
|
|
89
91
|
/** Conversation + context for Claude (system prompt passed separately). */
|
|
90
|
-
declare function buildClaudeUserPrompt(messages: ChatMessage[], context?: ClientContext, attachmentPaths?: string[], priorConversationsContext?: string): string;
|
|
92
|
+
declare function buildClaudeUserPrompt(messages: ChatMessage[], context?: ClientContext, attachmentPaths?: string[], priorConversationsContext?: string, technical?: boolean): string;
|
|
91
93
|
|
|
92
94
|
declare function createDefaultSystemPrompt(input: {
|
|
93
95
|
productDescription: string;
|
|
@@ -111,6 +113,8 @@ declare function createToolValidator(schemas: ToolSchemaMap): (toolCall: ToolCal
|
|
|
111
113
|
error: string;
|
|
112
114
|
};
|
|
113
115
|
|
|
116
|
+
/** Stored on assistant rows while a single in-flight AI turn is running. */
|
|
117
|
+
declare const WORKING_PROVIDER = "working";
|
|
114
118
|
/** Minimal persistence contract (file store, SQL, or Maintainer Pro). */
|
|
115
119
|
interface ChatStoreLike {
|
|
116
120
|
ensureConversation(id: string): Promise<void>;
|
|
@@ -133,6 +137,8 @@ interface ChatStoreLike {
|
|
|
133
137
|
senderType?: "client" | "developer" | "ai" | null;
|
|
134
138
|
senderName?: string | null;
|
|
135
139
|
}>>;
|
|
140
|
+
/** Remove in-progress working placeholders (at most one per conversation). */
|
|
141
|
+
clearWorkingMessages?(conversationId: string): Promise<void>;
|
|
136
142
|
/** All conversations under the store (for prior-chat context). */
|
|
137
143
|
listConversations?(): Promise<Array<{
|
|
138
144
|
id: string;
|
|
@@ -141,6 +147,7 @@ interface ChatStoreLike {
|
|
|
141
147
|
role: string;
|
|
142
148
|
content: string;
|
|
143
149
|
createdAt?: string | Date;
|
|
150
|
+
provider?: string | null;
|
|
144
151
|
}>;
|
|
145
152
|
}>>;
|
|
146
153
|
saveToolEvents?(conversationId: string, events: Array<{
|
|
@@ -236,4 +243,39 @@ interface PriorConversationOptions {
|
|
|
236
243
|
*/
|
|
237
244
|
declare function buildPriorConversationsContext(db: ChatStoreLike, options: PriorConversationOptions): Promise<string>;
|
|
238
245
|
|
|
239
|
-
|
|
246
|
+
type WorkspaceKind = "next" | "vite" | "html" | "empty" | "other";
|
|
247
|
+
interface WorkspaceInspectInput {
|
|
248
|
+
workspaceDir: string;
|
|
249
|
+
appName?: string;
|
|
250
|
+
/** When set, the agent diagnoses and tries to fix this failure. */
|
|
251
|
+
problem?: string;
|
|
252
|
+
extraContext?: string;
|
|
253
|
+
}
|
|
254
|
+
interface WorkspaceInspectResult {
|
|
255
|
+
kind: WorkspaceKind;
|
|
256
|
+
name: string;
|
|
257
|
+
summary: string;
|
|
258
|
+
scripts: {
|
|
259
|
+
ui?: string;
|
|
260
|
+
backend?: string;
|
|
261
|
+
app?: string;
|
|
262
|
+
};
|
|
263
|
+
ports: {
|
|
264
|
+
ui?: number;
|
|
265
|
+
backend?: number;
|
|
266
|
+
app?: number;
|
|
267
|
+
};
|
|
268
|
+
issues: string[];
|
|
269
|
+
fixes: string[];
|
|
270
|
+
ready: boolean;
|
|
271
|
+
provider: string;
|
|
272
|
+
rawText: string;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Ask the coding-agent CLI (via callAi) to inspect a workspace and optionally
|
|
276
|
+
* repair setup issues. Used by ai-bridge so host-process planning uses the
|
|
277
|
+
* same provider resolution as chat.
|
|
278
|
+
*/
|
|
279
|
+
declare function inspectAndRepairWorkspace(input: WorkspaceInspectInput): Promise<WorkspaceInspectResult>;
|
|
280
|
+
|
|
281
|
+
export { type AiCliProviderId, type AiProvider, type AiResponse, type CallAiOptions, type ChatAttachment, type ChatHandlerOptions, type ChatHandlers, type ChatMessage, type ChatSenderType, type ChatStoreLike, type ChatUser, type ClientContext, type LocalStoredMessage, type MaintainerProStoreOptions, type PriorConversationOptions, type ProviderPreference, type ToolCall, type ToolSchemaMap, WORKING_PROVIDER, type WorkspaceInspectInput, type WorkspaceInspectResult, type WorkspaceKind, buildClaudeUserPrompt, buildConversationPrompt, buildCursorPrompt, buildPriorConversationsContext, callAi, commandExists, createAntigravityProvider, createBuiltinProviders, createChatHandler, createClaudeProvider, createCursorProvider, createDefaultSystemPrompt, createLocalDirectoryStore, createMaintainerProStore, createMaintainerProStoreFromEnv, createToolValidator, formatClientContext, getProviderPreference, inspectAndRepairWorkspace, parseAiResponse, providerLabel, resolveCliBinary, resolveProvider, saveChatAttachments, toNextRoute };
|
package/dist/index.js
CHANGED
|
@@ -43,7 +43,7 @@ function splitMessages(messages) {
|
|
|
43
43
|
const history = latestUserIndex > 0 ? messages.slice(0, latestUserIndex) : [];
|
|
44
44
|
return { request, history };
|
|
45
45
|
}
|
|
46
|
-
function buildUserFacingPrompt(systemPrompt, messages, context, attachmentPaths, priorConversationsContext) {
|
|
46
|
+
function buildUserFacingPrompt(systemPrompt, messages, context, attachmentPaths, priorConversationsContext, technical) {
|
|
47
47
|
const { request, history } = splitMessages(messages);
|
|
48
48
|
const historyBlock = history.length > 0 ? `Conversation so far:
|
|
49
49
|
${buildConversationPrompt(history)}
|
|
@@ -63,27 +63,30 @@ ${buildConversationPrompt(history)}
|
|
|
63
63
|
${attachmentPaths.map((p) => `- ${p}`).join("\n")}
|
|
64
64
|
|
|
65
65
|
` : "";
|
|
66
|
+
const closer = technical ? "Do the work now. Follow the requested output format exactly." : "Do the work now (or ask one short clarifying question if needed). Reply in English, non-technical and user-facing. If it's not resolving, offer a quick call.";
|
|
66
67
|
return `${systemSection}${contextSection}${priorSection}${historyBlock}${attachmentsSection}Current user request:
|
|
67
68
|
${request || "(See the attached screenshot(s).)"}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
${closer}`;
|
|
70
71
|
}
|
|
71
|
-
function buildCursorPrompt(systemPrompt, messages, context, attachmentPaths, priorConversationsContext) {
|
|
72
|
+
function buildCursorPrompt(systemPrompt, messages, context, attachmentPaths, priorConversationsContext, technical) {
|
|
72
73
|
return buildUserFacingPrompt(
|
|
73
74
|
systemPrompt,
|
|
74
75
|
messages,
|
|
75
76
|
context,
|
|
76
77
|
attachmentPaths,
|
|
77
|
-
priorConversationsContext
|
|
78
|
+
priorConversationsContext,
|
|
79
|
+
technical
|
|
78
80
|
);
|
|
79
81
|
}
|
|
80
|
-
function buildClaudeUserPrompt(messages, context, attachmentPaths, priorConversationsContext) {
|
|
82
|
+
function buildClaudeUserPrompt(messages, context, attachmentPaths, priorConversationsContext, technical) {
|
|
81
83
|
return buildUserFacingPrompt(
|
|
82
84
|
null,
|
|
83
85
|
messages,
|
|
84
86
|
context,
|
|
85
87
|
attachmentPaths,
|
|
86
|
-
priorConversationsContext
|
|
88
|
+
priorConversationsContext,
|
|
89
|
+
technical
|
|
87
90
|
);
|
|
88
91
|
}
|
|
89
92
|
|
|
@@ -196,7 +199,8 @@ async function callClaudeCli(command, messages, context, options) {
|
|
|
196
199
|
messages,
|
|
197
200
|
context,
|
|
198
201
|
options.attachmentPaths,
|
|
199
|
-
options.priorConversationsContext
|
|
202
|
+
options.priorConversationsContext,
|
|
203
|
+
options.technical
|
|
200
204
|
);
|
|
201
205
|
const resolved = resolveCliCommand("claude", command);
|
|
202
206
|
const workspace = options.workspaceDir ?? process.env.AI_CLI_WORKSPACE ?? process.cwd();
|
|
@@ -235,7 +239,8 @@ async function callCursorCli(command, messages, context, options) {
|
|
|
235
239
|
messages,
|
|
236
240
|
context,
|
|
237
241
|
options.attachmentPaths,
|
|
238
|
-
options.priorConversationsContext
|
|
242
|
+
options.priorConversationsContext,
|
|
243
|
+
options.technical
|
|
239
244
|
);
|
|
240
245
|
const resolved = resolveCliCommand("cursor", command);
|
|
241
246
|
const workspace = options.workspaceDir ?? process.env.AI_CLI_WORKSPACE ?? process.cwd();
|
|
@@ -276,7 +281,8 @@ async function callAntigravityCli(command, messages, context, options) {
|
|
|
276
281
|
messages,
|
|
277
282
|
context,
|
|
278
283
|
options.attachmentPaths,
|
|
279
|
-
options.priorConversationsContext
|
|
284
|
+
options.priorConversationsContext,
|
|
285
|
+
options.technical
|
|
280
286
|
);
|
|
281
287
|
const prompt = `You are operating as a coding agent with full permission to read and edit files in this workspace. Do not introduce yourself. Do not ask what you are. Execute the user's latest request now (edit files and/or emit runtime tool JSON as instructed).
|
|
282
288
|
|
|
@@ -533,7 +539,9 @@ async function buildPriorConversationsContext(db, options) {
|
|
|
533
539
|
).slice(0, maxConversations) : ranked;
|
|
534
540
|
const blocks = selected.map((c) => {
|
|
535
541
|
const slice = c.messages.length > maxMessages ? c.messages.slice(-maxMessages) : c.messages;
|
|
536
|
-
const lines = slice.filter(
|
|
542
|
+
const lines = slice.filter(
|
|
543
|
+
(m) => (m.role === "user" || m.role === "assistant") && m.provider !== "working" && Boolean(m.content?.trim())
|
|
544
|
+
).map(
|
|
537
545
|
(m) => `${m.role === "user" ? "Human" : "Assistant"}: ${truncate(m.content)}`
|
|
538
546
|
);
|
|
539
547
|
const when = c.updatedAt ? ` (updated ${c.updatedAt.slice(0, 10)})` : "";
|
|
@@ -566,6 +574,18 @@ function createToolValidator(schemas) {
|
|
|
566
574
|
}
|
|
567
575
|
|
|
568
576
|
// src/http/handler.ts
|
|
577
|
+
var WORKING_PROVIDER = "working";
|
|
578
|
+
async function setWorkingMessage(db, conversationId) {
|
|
579
|
+
await db.ensureConversation(conversationId);
|
|
580
|
+
await db.clearWorkingMessages?.(conversationId);
|
|
581
|
+
await db.saveMessage({
|
|
582
|
+
conversationId,
|
|
583
|
+
role: "assistant",
|
|
584
|
+
content: "",
|
|
585
|
+
provider: WORKING_PROVIDER,
|
|
586
|
+
senderType: "ai"
|
|
587
|
+
});
|
|
588
|
+
}
|
|
569
589
|
var SHARED_CONVERSATION_ID = "shared";
|
|
570
590
|
var conversationTurnSeq = /* @__PURE__ */ new Map();
|
|
571
591
|
function beginConversationTurn(conversationId) {
|
|
@@ -638,6 +658,8 @@ function createChatHandler(options) {
|
|
|
638
658
|
}
|
|
639
659
|
},
|
|
640
660
|
async POST(request) {
|
|
661
|
+
let trackedConversationId;
|
|
662
|
+
let trackedTurn = 0;
|
|
641
663
|
try {
|
|
642
664
|
const body = await request.json();
|
|
643
665
|
const conversationId = await resolveSharedConversationId(
|
|
@@ -645,21 +667,16 @@ function createChatHandler(options) {
|
|
|
645
667
|
options,
|
|
646
668
|
body.conversationId
|
|
647
669
|
);
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
if (!conversationId
|
|
670
|
+
trackedConversationId = conversationId;
|
|
671
|
+
if (body.type === "working-clear") {
|
|
672
|
+
if (!conversationId) {
|
|
651
673
|
return Response.json(
|
|
652
|
-
{ error: "conversationId
|
|
674
|
+
{ error: "conversationId is required" },
|
|
653
675
|
{ status: 400 }
|
|
654
676
|
);
|
|
655
677
|
}
|
|
656
|
-
if (options.db) {
|
|
657
|
-
await options.db.
|
|
658
|
-
await options.db.saveMessage({
|
|
659
|
-
conversationId,
|
|
660
|
-
role: "assistant",
|
|
661
|
-
content
|
|
662
|
-
});
|
|
678
|
+
if (options.db?.clearWorkingMessages) {
|
|
679
|
+
await options.db.clearWorkingMessages(conversationId);
|
|
663
680
|
}
|
|
664
681
|
return Response.json({ ok: true, conversationId });
|
|
665
682
|
}
|
|
@@ -707,7 +724,8 @@ function createChatHandler(options) {
|
|
|
707
724
|
);
|
|
708
725
|
persistAttachmentPaths = attachmentPaths;
|
|
709
726
|
}
|
|
710
|
-
const turn = beginConversationTurn(conversationId);
|
|
727
|
+
const turn = beginConversationTurn(conversationId ?? SHARED_CONVERSATION_ID);
|
|
728
|
+
trackedTurn = turn;
|
|
711
729
|
const signal = request.signal;
|
|
712
730
|
if (options.db && conversationId) {
|
|
713
731
|
const latest = messages[messages.length - 1];
|
|
@@ -723,13 +741,26 @@ function createChatHandler(options) {
|
|
|
723
741
|
senderName: body.senderName?.trim() || void 0
|
|
724
742
|
});
|
|
725
743
|
}
|
|
744
|
+
await setWorkingMessage(options.db, conversationId);
|
|
726
745
|
}
|
|
727
746
|
const latestUser = [...messages].reverse().find((m) => m.role === "user");
|
|
728
747
|
const priorConversationsContext = options.db ? await buildPriorConversationsContext(options.db, {
|
|
729
748
|
excludeConversationId: conversationId,
|
|
730
749
|
currentRequest: latestUser?.content ?? ""
|
|
731
750
|
}) : "";
|
|
732
|
-
if (
|
|
751
|
+
if (!isActiveConversationTurn(
|
|
752
|
+
conversationId ?? SHARED_CONVERSATION_ID,
|
|
753
|
+
turn
|
|
754
|
+
)) {
|
|
755
|
+
return Response.json({
|
|
756
|
+
superseded: true,
|
|
757
|
+
conversationId: conversationId ?? null
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
if (signal.aborted) {
|
|
761
|
+
if (options.db?.clearWorkingMessages && conversationId) {
|
|
762
|
+
await options.db.clearWorkingMessages(conversationId);
|
|
763
|
+
}
|
|
733
764
|
return Response.json({
|
|
734
765
|
superseded: true,
|
|
735
766
|
conversationId: conversationId ?? null
|
|
@@ -740,7 +771,10 @@ function createChatHandler(options) {
|
|
|
740
771
|
attachmentPaths,
|
|
741
772
|
priorConversationsContext: priorConversationsContext || void 0
|
|
742
773
|
});
|
|
743
|
-
if (!isActiveConversationTurn(
|
|
774
|
+
if (!isActiveConversationTurn(
|
|
775
|
+
conversationId ?? SHARED_CONVERSATION_ID,
|
|
776
|
+
turn
|
|
777
|
+
)) {
|
|
744
778
|
return Response.json({
|
|
745
779
|
superseded: true,
|
|
746
780
|
conversationId: conversationId ?? null
|
|
@@ -752,6 +786,7 @@ function createChatHandler(options) {
|
|
|
752
786
|
}
|
|
753
787
|
if (options.db && conversationId) {
|
|
754
788
|
await options.db.ensureConversation(conversationId);
|
|
789
|
+
await options.db.clearWorkingMessages?.(conversationId);
|
|
755
790
|
await options.db.saveMessage({
|
|
756
791
|
conversationId,
|
|
757
792
|
role: "assistant",
|
|
@@ -775,6 +810,11 @@ function createChatHandler(options) {
|
|
|
775
810
|
});
|
|
776
811
|
} catch (err) {
|
|
777
812
|
console.error("Chat API error:", err);
|
|
813
|
+
if (trackedConversationId && options.db?.clearWorkingMessages && isActiveConversationTurn(trackedConversationId, trackedTurn)) {
|
|
814
|
+
await options.db.clearWorkingMessages(trackedConversationId).catch(
|
|
815
|
+
() => void 0
|
|
816
|
+
);
|
|
817
|
+
}
|
|
778
818
|
return Response.json(
|
|
779
819
|
{
|
|
780
820
|
error: "Sorry about that \u2014 I couldn't finish this right now. Happy to jump on a quick call if you'd like help \u2014 just let us know when works."
|
|
@@ -844,6 +884,16 @@ function createLocalDirectoryStore(baseDir) {
|
|
|
844
884
|
await writeConversation(filePath, existing);
|
|
845
885
|
return message;
|
|
846
886
|
},
|
|
887
|
+
async clearWorkingMessages(conversationId) {
|
|
888
|
+
const filePath = fileFor(conversationId);
|
|
889
|
+
const existing = await readConversation(filePath);
|
|
890
|
+
if (!existing) return;
|
|
891
|
+
const next = existing.messages.filter((m) => m.provider !== "working");
|
|
892
|
+
if (next.length === existing.messages.length) return;
|
|
893
|
+
existing.messages = next;
|
|
894
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
895
|
+
await writeConversation(filePath, existing);
|
|
896
|
+
},
|
|
847
897
|
async listMessages(conversationId) {
|
|
848
898
|
const existing = await readConversation(fileFor(conversationId));
|
|
849
899
|
return existing?.messages ?? [];
|
|
@@ -950,6 +1000,15 @@ function createMaintainerProStore(options) {
|
|
|
950
1000
|
);
|
|
951
1001
|
return data.message;
|
|
952
1002
|
},
|
|
1003
|
+
async clearWorkingMessages(conversationId) {
|
|
1004
|
+
await mpFetch(
|
|
1005
|
+
baseUrl,
|
|
1006
|
+
apiKey,
|
|
1007
|
+
`/api/v1/store/conversations/${encodeURIComponent(conversationId)}/messages?provider=working`,
|
|
1008
|
+
{ method: "DELETE" },
|
|
1009
|
+
fetchImpl
|
|
1010
|
+
);
|
|
1011
|
+
},
|
|
953
1012
|
async listMessages(conversationId) {
|
|
954
1013
|
const data = await mpFetch(
|
|
955
1014
|
baseUrl,
|
|
@@ -1029,7 +1088,114 @@ function createMaintainerProStoreFromEnv() {
|
|
|
1029
1088
|
if (!baseUrl || !apiKey) return null;
|
|
1030
1089
|
return createMaintainerProStore({ baseUrl, apiKey });
|
|
1031
1090
|
}
|
|
1091
|
+
|
|
1092
|
+
// src/workspace-inspect.ts
|
|
1093
|
+
import { z } from "zod";
|
|
1094
|
+
var inspectJsonSchema = z.object({
|
|
1095
|
+
kind: z.enum(["next", "vite", "html", "empty", "other"]).optional(),
|
|
1096
|
+
name: z.string().max(200).optional(),
|
|
1097
|
+
summary: z.string().max(800).optional(),
|
|
1098
|
+
scripts: z.object({
|
|
1099
|
+
ui: z.string().min(1).max(64).optional(),
|
|
1100
|
+
backend: z.string().min(1).max(64).optional(),
|
|
1101
|
+
app: z.string().min(1).max(64).optional()
|
|
1102
|
+
}).optional(),
|
|
1103
|
+
ports: z.object({
|
|
1104
|
+
ui: z.number().int().min(1).max(65535).optional(),
|
|
1105
|
+
backend: z.number().int().min(1).max(65535).optional(),
|
|
1106
|
+
app: z.number().int().min(1).max(65535).optional()
|
|
1107
|
+
}).optional(),
|
|
1108
|
+
issues: z.array(z.string().max(500)).max(20).optional(),
|
|
1109
|
+
fixes: z.array(z.string().max(500)).max(20).optional(),
|
|
1110
|
+
ready: z.boolean().optional()
|
|
1111
|
+
});
|
|
1112
|
+
function extractInspectJson(text) {
|
|
1113
|
+
const fence = text.match(/```json\s*([\s\S]*?)```/i);
|
|
1114
|
+
const raw = fence?.[1]?.trim() || text.match(/\{[\s\S]*\}/)?.[0];
|
|
1115
|
+
if (!raw) return null;
|
|
1116
|
+
try {
|
|
1117
|
+
const parsed = inspectJsonSchema.safeParse(JSON.parse(raw));
|
|
1118
|
+
return parsed.success ? parsed.data : null;
|
|
1119
|
+
} catch {
|
|
1120
|
+
return null;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
function setupSystemPrompt(input) {
|
|
1124
|
+
return `You are Maintainer Pro's local setup assistant on the developer's machine.
|
|
1125
|
+
|
|
1126
|
+
Workspace: ${input.workspaceDir}
|
|
1127
|
+
App name: ${input.appName || "the app"}
|
|
1128
|
+
|
|
1129
|
+
Inspect this repository (package.json, README, lockfiles, existing env files, how the UI and API start).
|
|
1130
|
+
You MAY edit files to fix setup problems that would block local run or the chat widget (env keys, widget mount, missing config).
|
|
1131
|
+
Do NOT start long-lived dev servers, tunnels, or install global tools.
|
|
1132
|
+
Do NOT run \`npm run dev\` or similar.
|
|
1133
|
+
|
|
1134
|
+
Script names you report MUST already exist in package.json. Omit chat/ai-server scripts.
|
|
1135
|
+
|
|
1136
|
+
End with a short human summary AND one \`\`\`json fence with:
|
|
1137
|
+
|
|
1138
|
+
{
|
|
1139
|
+
"kind": "next" | "vite" | "html" | "empty" | "other",
|
|
1140
|
+
"name": "package or folder name",
|
|
1141
|
+
"summary": "one sentence about this project",
|
|
1142
|
+
"scripts": { "ui": "dev:client", "backend": "dev:server" },
|
|
1143
|
+
"ports": { "ui": 5173, "backend": 4100 },
|
|
1144
|
+
"issues": ["remaining problems"],
|
|
1145
|
+
"fixes": ["what you changed"],
|
|
1146
|
+
"ready": true
|
|
1147
|
+
}`;
|
|
1148
|
+
}
|
|
1149
|
+
function setupUserMessage(input) {
|
|
1150
|
+
if (input.problem?.trim()) {
|
|
1151
|
+
return `The local bridge failed to keep this project running:
|
|
1152
|
+
|
|
1153
|
+
${input.problem.trim()}
|
|
1154
|
+
|
|
1155
|
+
${input.extraContext ? `${input.extraContext.trim()}
|
|
1156
|
+
|
|
1157
|
+
` : ""}Inspect the repo, fix the cause if you can, and return the JSON report.`;
|
|
1158
|
+
}
|
|
1159
|
+
return `${input.extraContext ? `${input.extraContext.trim()}
|
|
1160
|
+
|
|
1161
|
+
` : ""}Inspect this project, fix any setup gaps you can, and return the JSON report.`;
|
|
1162
|
+
}
|
|
1163
|
+
async function inspectAndRepairWorkspace(input) {
|
|
1164
|
+
const workspaceDir = input.workspaceDir;
|
|
1165
|
+
const provider = await resolveProvider({ preference: "auto" });
|
|
1166
|
+
const response = await callAi(
|
|
1167
|
+
[{ role: "user", content: setupUserMessage(input) }],
|
|
1168
|
+
{
|
|
1169
|
+
route: "/local-setup",
|
|
1170
|
+
pageTitle: input.appName || "Local setup",
|
|
1171
|
+
relevantFiles: ["package.json", "README.md", ".env", ".env.example"],
|
|
1172
|
+
data: {
|
|
1173
|
+
workspaceDir,
|
|
1174
|
+
problem: input.problem || null
|
|
1175
|
+
}
|
|
1176
|
+
},
|
|
1177
|
+
{
|
|
1178
|
+
systemPrompt: setupSystemPrompt(input),
|
|
1179
|
+
workspaceDir,
|
|
1180
|
+
technical: true
|
|
1181
|
+
}
|
|
1182
|
+
);
|
|
1183
|
+
const parsed = extractInspectJson(response.text);
|
|
1184
|
+
return {
|
|
1185
|
+
kind: parsed?.kind ?? "other",
|
|
1186
|
+
name: parsed?.name?.trim() || input.appName || "",
|
|
1187
|
+
summary: parsed?.summary?.trim() || response.text.replace(/```json[\s\S]*```/i, "").trim().slice(0, 400),
|
|
1188
|
+
scripts: parsed?.scripts ?? {},
|
|
1189
|
+
ports: parsed?.ports ?? {},
|
|
1190
|
+
issues: parsed?.issues ?? [],
|
|
1191
|
+
fixes: parsed?.fixes ?? [],
|
|
1192
|
+
ready: parsed?.ready ?? parsed?.issues?.length === 0,
|
|
1193
|
+
provider: response.provider || provider.id,
|
|
1194
|
+
rawText: response.text
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1032
1197
|
export {
|
|
1198
|
+
WORKING_PROVIDER,
|
|
1033
1199
|
buildClaudeUserPrompt,
|
|
1034
1200
|
buildConversationPrompt,
|
|
1035
1201
|
buildCursorPrompt,
|
|
@@ -1048,6 +1214,7 @@ export {
|
|
|
1048
1214
|
createToolValidator,
|
|
1049
1215
|
formatClientContext,
|
|
1050
1216
|
getProviderPreference,
|
|
1217
|
+
inspectAndRepairWorkspace,
|
|
1051
1218
|
parseAiResponse,
|
|
1052
1219
|
providerLabel,
|
|
1053
1220
|
resolveCliBinary,
|