@praneeth_54/agentdoctor 2.0.1 → 2.1.0
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/CHANGELOG.md +69 -1
- package/README.md +65 -14
- package/dist/agent/approvals.d.ts +24 -0
- package/dist/agent/approvals.js +64 -0
- package/dist/agent/chat/index.d.ts +7 -0
- package/dist/agent/chat/index.js +5 -0
- package/dist/agent/chat/memory.d.ts +44 -0
- package/dist/agent/chat/memory.js +103 -0
- package/dist/agent/chat/project-summary.d.ts +16 -0
- package/dist/agent/chat/project-summary.js +70 -0
- package/dist/agent/chat/prompts.d.ts +6 -0
- package/dist/agent/chat/prompts.js +39 -0
- package/dist/agent/chat/response.d.ts +19 -0
- package/dist/agent/chat/response.js +109 -0
- package/dist/agent/chat/service.d.ts +42 -0
- package/dist/agent/chat/service.js +252 -0
- package/dist/agent/chat/types.d.ts +48 -0
- package/dist/agent/chat/types.js +1 -0
- package/dist/agent/context/retrieve.d.ts +21 -0
- package/dist/agent/context/retrieve.js +117 -0
- package/dist/agent/context/truth.d.ts +6 -0
- package/dist/agent/context/truth.js +19 -0
- package/dist/agent/context/types.d.ts +23 -0
- package/dist/agent/context/types.js +4 -0
- package/dist/agent/index.d.ts +26 -0
- package/dist/agent/index.js +14 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +222 -0
- package/dist/agent/modes.d.ts +18 -0
- package/dist/agent/modes.js +102 -0
- package/dist/agent/plan.d.ts +30 -0
- package/dist/agent/plan.js +121 -0
- package/dist/agent/runtime.d.ts +67 -0
- package/dist/agent/runtime.js +180 -0
- package/dist/agent/state.d.ts +30 -0
- package/dist/agent/state.js +95 -0
- package/dist/agent/student.d.ts +56 -0
- package/dist/agent/student.js +230 -0
- package/dist/agent/tools/execute.d.ts +18 -0
- package/dist/agent/tools/execute.js +355 -0
- package/dist/agent/tools/index.d.ts +6 -0
- package/dist/agent/tools/index.js +5 -0
- package/dist/agent/tools/registry.d.ts +7 -0
- package/dist/agent/tools/registry.js +212 -0
- package/dist/agent/tools/run.d.ts +24 -0
- package/dist/agent/tools/run.js +44 -0
- package/dist/agent/tools/types.d.ts +32 -0
- package/dist/agent/tools/types.js +10 -0
- package/dist/agent/tools/write.d.ts +24 -0
- package/dist/agent/tools/write.js +121 -0
- package/dist/agent/verify.d.ts +29 -0
- package/dist/agent/verify.js +210 -0
- package/dist/ai/config.d.ts +22 -0
- package/dist/ai/config.js +68 -0
- package/dist/ai/index.d.ts +17 -0
- package/dist/ai/index.js +52 -0
- package/dist/ai/providers/mock.d.ts +20 -0
- package/dist/ai/providers/mock.js +84 -0
- package/dist/ai/providers/none.d.ts +6 -0
- package/dist/ai/providers/none.js +23 -0
- package/dist/ai/providers/openai-compatible.d.ts +21 -0
- package/dist/ai/providers/openai-compatible.js +151 -0
- package/dist/ai/redact.d.ts +8 -0
- package/dist/ai/redact.js +21 -0
- package/dist/ai/types.d.ts +71 -0
- package/dist/ai/types.js +6 -0
- package/dist/cli/commands/agent.d.ts +27 -0
- package/dist/cli/commands/agent.js +153 -0
- package/dist/cli/commands/chat.d.ts +14 -0
- package/dist/cli/commands/chat.js +155 -0
- package/dist/cli/commands/learn.d.ts +12 -0
- package/dist/cli/commands/learn.js +107 -0
- package/dist/cli/program.js +92 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/dashboard/server.d.ts +6 -0
- package/dist/dashboard/server.js +89 -1
- package/dist/enforcement/runner.d.ts +5 -0
- package/dist/enforcement/runner.js +51 -4
- package/dist/index.d.ts +13 -0
- package/dist/index.js +8 -0
- package/dist/languages/go.js +7 -2
- package/dist/languages/php.d.ts +1 -0
- package/dist/languages/php.js +11 -2
- package/dist/languages/python.d.ts +6 -2
- package/dist/languages/python.js +39 -9
- package/dist/mcp/agent/registry.d.ts +13 -0
- package/dist/mcp/agent/registry.js +234 -0
- package/dist/mcp/agentdoctor/server.js +8 -1
- package/dist/security/paths.js +43 -15
- package/package.json +1 -1
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { truthLabelHelp } from "../context/truth.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build a conservative structured response from model text + retrieved evidence.
|
|
4
|
+
* Never invents line ranges. Citations come only from the context bundle.
|
|
5
|
+
*/
|
|
6
|
+
export function buildChatTurnResponse(options) {
|
|
7
|
+
const citations = options.context.citations.filter((c) => c.path && c.confidence === "VERIFIED");
|
|
8
|
+
const contextPaths = citations.map((c) => c.path).filter(Boolean);
|
|
9
|
+
const mentioned = extractMentionedPaths(options.modelText, contextPaths);
|
|
10
|
+
const truthClaims = [];
|
|
11
|
+
for (const path of mentioned) {
|
|
12
|
+
truthClaims.push({
|
|
13
|
+
text: `Referenced project file: ${path}`,
|
|
14
|
+
label: "VERIFIED",
|
|
15
|
+
citationPaths: [path],
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (citations.length === 0) {
|
|
19
|
+
truthClaims.push({
|
|
20
|
+
text: "Insufficient repository evidence was retrieved for this question.",
|
|
21
|
+
label: "UNKNOWN",
|
|
22
|
+
citationPaths: [],
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
else if (options.modelText.trim()) {
|
|
26
|
+
truthClaims.push({
|
|
27
|
+
text: "Interpretive explanation based on retrieved project evidence.",
|
|
28
|
+
label: "INFERRED",
|
|
29
|
+
citationPaths: mentioned.length ? mentioned : contextPaths.slice(0, 5),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (/\b(redis|kubernetes|production|aws|gcp|azure)\b/i.test(options.modelText)) {
|
|
33
|
+
const verifiedHit = citations.some((c) => /\b(redis|kubernetes|aws|gcp|azure)\b/i.test(`${c.path ?? ""} ${c.excerpt ?? ""} ${c.note ?? ""}`));
|
|
34
|
+
if (!verifiedHit) {
|
|
35
|
+
truthClaims.push({
|
|
36
|
+
text: "Claims about external/runtime infrastructure were not verified from repository evidence.",
|
|
37
|
+
label: "EXTERNAL",
|
|
38
|
+
citationPaths: [],
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const contextTruncated = options.context.limitations.some((l) => /truncat|budget|No file excerpts/i.test(l)) ||
|
|
43
|
+
options.context.estimatedTokens > 5_500;
|
|
44
|
+
return {
|
|
45
|
+
sessionId: options.sessionId,
|
|
46
|
+
message: options.modelText.trim(),
|
|
47
|
+
truthClaims,
|
|
48
|
+
citations,
|
|
49
|
+
provider: options.provider,
|
|
50
|
+
model: options.model,
|
|
51
|
+
status: options.status,
|
|
52
|
+
...(options.error ? { error: options.error } : {}),
|
|
53
|
+
...(options.usage ? { usage: options.usage } : {}),
|
|
54
|
+
contextPaths,
|
|
55
|
+
contextTruncated,
|
|
56
|
+
limitations: options.context.limitations,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function extractMentionedPaths(text, known) {
|
|
60
|
+
const found = [];
|
|
61
|
+
for (const p of known) {
|
|
62
|
+
if (text.includes(p) && !found.includes(p))
|
|
63
|
+
found.push(p);
|
|
64
|
+
}
|
|
65
|
+
return found;
|
|
66
|
+
}
|
|
67
|
+
export function formatChatResponseForCli(response) {
|
|
68
|
+
if (response.status === "provider-none" || response.status === "provider-error") {
|
|
69
|
+
return `${response.message || response.error || "AI provider unavailable."}\n`;
|
|
70
|
+
}
|
|
71
|
+
const lines = [];
|
|
72
|
+
lines.push("");
|
|
73
|
+
lines.push("AgentDoctor:");
|
|
74
|
+
lines.push("");
|
|
75
|
+
lines.push(response.message || "(empty response)");
|
|
76
|
+
lines.push("");
|
|
77
|
+
if (response.truthClaims.length > 0) {
|
|
78
|
+
lines.push("Truth:");
|
|
79
|
+
for (const claim of response.truthClaims.slice(0, 8)) {
|
|
80
|
+
lines.push(` [${claim.label}] ${claim.text}`);
|
|
81
|
+
}
|
|
82
|
+
lines.push("");
|
|
83
|
+
}
|
|
84
|
+
if (response.citations.length > 0) {
|
|
85
|
+
lines.push("Evidence:");
|
|
86
|
+
for (const c of response.citations.slice(0, 12)) {
|
|
87
|
+
const path = c.path ?? "(unknown)";
|
|
88
|
+
lines.push(` - ${path}${c.range ? `:${c.range}` : ""} (${c.confidence})`);
|
|
89
|
+
}
|
|
90
|
+
lines.push("");
|
|
91
|
+
}
|
|
92
|
+
if (response.contextTruncated) {
|
|
93
|
+
lines.push("Note: context was budget-limited; confidence may be reduced.");
|
|
94
|
+
lines.push("");
|
|
95
|
+
}
|
|
96
|
+
if (response.limitations.length > 0) {
|
|
97
|
+
lines.push("Limitations:");
|
|
98
|
+
for (const l of response.limitations.slice(0, 5)) {
|
|
99
|
+
lines.push(` - ${l}`);
|
|
100
|
+
}
|
|
101
|
+
lines.push("");
|
|
102
|
+
}
|
|
103
|
+
return `${lines.join("\n")}\n`;
|
|
104
|
+
}
|
|
105
|
+
export function describeTruthLabels() {
|
|
106
|
+
return ["VERIFIED", "INFERRED", "UNKNOWN", "EXTERNAL"]
|
|
107
|
+
.map((l) => `${l}: ${truthLabelHelp(l)}`)
|
|
108
|
+
.join("\n");
|
|
109
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ContextBundle } from "../context/types.js";
|
|
2
|
+
import { ChatMemory } from "./memory.js";
|
|
3
|
+
import { type ProjectChatSummary } from "./project-summary.js";
|
|
4
|
+
import type { ChatServiceOptions, ChatTurnResponse } from "./types.js";
|
|
5
|
+
export declare const CHAT_PROVIDER_NONE_MESSAGE = "AI chat is not configured.\n\nConfigure an AI provider to use AgentDoctor Project Chat.\n\nSet AGENTDOCTOR_AI_PROVIDER (and AGENTDOCTOR_AI_API_KEY / AGENTDOCTOR_AI_BASE_URL as needed).\n\nAgentDoctor's non-AI project analysis remains available.";
|
|
6
|
+
/**
|
|
7
|
+
* Project Chat service (Milestone 2).
|
|
8
|
+
* Orchestrates memory → context → model → truth/citations.
|
|
9
|
+
* Does not edit files or execute commands.
|
|
10
|
+
*/
|
|
11
|
+
export declare class ChatService {
|
|
12
|
+
readonly root: string;
|
|
13
|
+
readonly memory: ChatMemory;
|
|
14
|
+
private readonly provider;
|
|
15
|
+
private readonly budgetTokens;
|
|
16
|
+
private readonly persistAudit;
|
|
17
|
+
private readonly systemPrompt;
|
|
18
|
+
private auditSession;
|
|
19
|
+
private graphCache;
|
|
20
|
+
private lastContext;
|
|
21
|
+
private started;
|
|
22
|
+
constructor(options: ChatServiceOptions);
|
|
23
|
+
get sessionId(): string;
|
|
24
|
+
getProviderId(): string;
|
|
25
|
+
getLastContextPaths(): string[];
|
|
26
|
+
getLastContext(): ContextBundle | undefined;
|
|
27
|
+
start(): Promise<{
|
|
28
|
+
summary: ProjectChatSummary;
|
|
29
|
+
providerLabel: string;
|
|
30
|
+
}>;
|
|
31
|
+
ask(userMessage: string): Promise<ChatTurnResponse>;
|
|
32
|
+
clearMemory(): void;
|
|
33
|
+
end(): Promise<void>;
|
|
34
|
+
formatResponse(response: ChatTurnResponse): string;
|
|
35
|
+
formatProject(): Promise<string>;
|
|
36
|
+
formatContext(): string;
|
|
37
|
+
private providerLabel;
|
|
38
|
+
private audit;
|
|
39
|
+
}
|
|
40
|
+
export declare function createChatService(options: ChatServiceOptions): ChatService;
|
|
41
|
+
/** Test helper: unique id without IO */
|
|
42
|
+
export declare function newChatSessionId(): string;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { AI_PROVIDER_REQUIRED_MESSAGE, createModelProvider, loadAiConfig, publicAiConfig, redactForModel, } from "../../ai/index.js";
|
|
3
|
+
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
4
|
+
import { appendSessionEvent, createSession, endSession, } from "../../platform/sessions/store.js";
|
|
5
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
6
|
+
import { retrieveProjectContext } from "../context/retrieve.js";
|
|
7
|
+
import { ChatMemory } from "./memory.js";
|
|
8
|
+
import { PROJECT_CHAT_SYSTEM_PROMPT, wrapProjectData } from "./prompts.js";
|
|
9
|
+
import { formatProjectSummary, summarizeProjectForChat, } from "./project-summary.js";
|
|
10
|
+
import { buildChatTurnResponse, formatChatResponseForCli } from "./response.js";
|
|
11
|
+
export const CHAT_PROVIDER_NONE_MESSAGE = `AI chat is not configured.
|
|
12
|
+
|
|
13
|
+
Configure an AI provider to use AgentDoctor Project Chat.
|
|
14
|
+
|
|
15
|
+
Set AGENTDOCTOR_AI_PROVIDER (and AGENTDOCTOR_AI_API_KEY / AGENTDOCTOR_AI_BASE_URL as needed).
|
|
16
|
+
|
|
17
|
+
AgentDoctor's non-AI project analysis remains available.`;
|
|
18
|
+
/**
|
|
19
|
+
* Project Chat service (Milestone 2).
|
|
20
|
+
* Orchestrates memory → context → model → truth/citations.
|
|
21
|
+
* Does not edit files or execute commands.
|
|
22
|
+
*/
|
|
23
|
+
export class ChatService {
|
|
24
|
+
root;
|
|
25
|
+
memory;
|
|
26
|
+
provider;
|
|
27
|
+
budgetTokens;
|
|
28
|
+
persistAudit;
|
|
29
|
+
systemPrompt;
|
|
30
|
+
auditSession;
|
|
31
|
+
graphCache;
|
|
32
|
+
lastContext;
|
|
33
|
+
started = false;
|
|
34
|
+
constructor(options) {
|
|
35
|
+
this.root = resolveRepoRoot(options.root);
|
|
36
|
+
this.provider = options.provider ?? createModelProvider(loadAiConfig());
|
|
37
|
+
this.budgetTokens = options.budgetTokens ?? 6_000;
|
|
38
|
+
this.persistAudit = options.persistAudit !== false;
|
|
39
|
+
this.systemPrompt = options.systemPromptAddon
|
|
40
|
+
? `${PROJECT_CHAT_SYSTEM_PROMPT}\n\n${options.systemPromptAddon}`
|
|
41
|
+
: PROJECT_CHAT_SYSTEM_PROMPT;
|
|
42
|
+
this.memory = new ChatMemory({
|
|
43
|
+
root: this.root,
|
|
44
|
+
...(options.maxMemoryTurns ? { maxTurns: options.maxMemoryTurns } : {}),
|
|
45
|
+
...(options.maxMemoryChars ? { maxChars: options.maxMemoryChars } : {}),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
get sessionId() {
|
|
49
|
+
return this.memory.sessionId;
|
|
50
|
+
}
|
|
51
|
+
getProviderId() {
|
|
52
|
+
return this.provider.id;
|
|
53
|
+
}
|
|
54
|
+
getLastContextPaths() {
|
|
55
|
+
return (this.lastContext?.citations ?? [])
|
|
56
|
+
.map((c) => c.path)
|
|
57
|
+
.filter((p) => Boolean(p));
|
|
58
|
+
}
|
|
59
|
+
getLastContext() {
|
|
60
|
+
return this.lastContext;
|
|
61
|
+
}
|
|
62
|
+
async start() {
|
|
63
|
+
if (this.started) {
|
|
64
|
+
return {
|
|
65
|
+
summary: await summarizeProjectForChat(this.root),
|
|
66
|
+
providerLabel: this.providerLabel(),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
this.started = true;
|
|
70
|
+
if (this.persistAudit) {
|
|
71
|
+
const cfg = publicAiConfig(loadAiConfig());
|
|
72
|
+
this.auditSession = await createSession({
|
|
73
|
+
root: this.root,
|
|
74
|
+
agentId: "project-chat",
|
|
75
|
+
model: `${this.provider.id}:${cfg.model}`,
|
|
76
|
+
});
|
|
77
|
+
await this.audit("prompt", "CHAT_STARTED", {
|
|
78
|
+
provider: this.provider.id,
|
|
79
|
+
sessionId: this.sessionId,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// Warm graph once per chat session
|
|
83
|
+
try {
|
|
84
|
+
this.graphCache = await buildIntelligenceGraph({ root: this.root, mode: "auto" });
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
this.graphCache = undefined;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
summary: await summarizeProjectForChat(this.root),
|
|
91
|
+
providerLabel: this.providerLabel(),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
async ask(userMessage) {
|
|
95
|
+
await this.start();
|
|
96
|
+
const safeUser = redactForModel(userMessage);
|
|
97
|
+
this.memory.addUser(safeUser);
|
|
98
|
+
await this.audit("prompt", "CHAT_USER_MESSAGE", {
|
|
99
|
+
chars: String(safeUser.length),
|
|
100
|
+
});
|
|
101
|
+
if (this.provider.id === "none") {
|
|
102
|
+
const response = {
|
|
103
|
+
sessionId: this.sessionId,
|
|
104
|
+
message: CHAT_PROVIDER_NONE_MESSAGE,
|
|
105
|
+
truthClaims: [],
|
|
106
|
+
citations: [],
|
|
107
|
+
provider: "none",
|
|
108
|
+
model: "none",
|
|
109
|
+
status: "provider-none",
|
|
110
|
+
error: AI_PROVIDER_REQUIRED_MESSAGE,
|
|
111
|
+
contextPaths: [],
|
|
112
|
+
contextTruncated: false,
|
|
113
|
+
limitations: [],
|
|
114
|
+
};
|
|
115
|
+
await this.audit("error", "CHAT_FAILED", { reason: "provider-none" });
|
|
116
|
+
return response;
|
|
117
|
+
}
|
|
118
|
+
const query = this.memory.resolveQuery(safeUser);
|
|
119
|
+
const context = await retrieveProjectContext({
|
|
120
|
+
root: this.root,
|
|
121
|
+
query,
|
|
122
|
+
budgetTokens: this.budgetTokens,
|
|
123
|
+
includePaths: [
|
|
124
|
+
...this.memory.getReferencedPaths().slice(-8),
|
|
125
|
+
...this.memory.getLastContextPaths().slice(-8),
|
|
126
|
+
],
|
|
127
|
+
...(this.graphCache ? { graph: this.graphCache } : {}),
|
|
128
|
+
});
|
|
129
|
+
this.lastContext = context;
|
|
130
|
+
await this.audit("file-read", "CHAT_CONTEXT_RETRIEVED", {
|
|
131
|
+
citations: String(context.citations.length),
|
|
132
|
+
tokens: String(context.estimatedTokens),
|
|
133
|
+
});
|
|
134
|
+
const history = this.memory.historyForModel().slice(0, -1); // exclude current user (added separately)
|
|
135
|
+
const messages = [
|
|
136
|
+
{ role: "system", content: this.systemPrompt },
|
|
137
|
+
{ role: "system", content: wrapProjectData(context.rendered) },
|
|
138
|
+
...history.map((m) => ({ role: m.role, content: redactForModel(m.content) })),
|
|
139
|
+
{ role: "user", content: safeUser },
|
|
140
|
+
];
|
|
141
|
+
await this.audit("prompt", "CHAT_MODEL_REQUESTED", {
|
|
142
|
+
provider: this.provider.id,
|
|
143
|
+
messages: String(messages.length),
|
|
144
|
+
});
|
|
145
|
+
const modelResult = await this.provider.chat({
|
|
146
|
+
messages,
|
|
147
|
+
temperature: 0.2,
|
|
148
|
+
maxTokens: 2048,
|
|
149
|
+
});
|
|
150
|
+
if (modelResult.error) {
|
|
151
|
+
const response = buildChatTurnResponse({
|
|
152
|
+
sessionId: this.sessionId,
|
|
153
|
+
modelText: `AgentDoctor could not reach the configured AI provider.\n\nProvider: ${modelResult.provider}\nModel: ${modelResult.model}\nError: ${sanitizeProviderError(modelResult.error)}`,
|
|
154
|
+
context,
|
|
155
|
+
provider: modelResult.provider,
|
|
156
|
+
model: modelResult.model,
|
|
157
|
+
status: "provider-error",
|
|
158
|
+
error: sanitizeProviderError(modelResult.error),
|
|
159
|
+
...(modelResult.usage ? { usage: modelResult.usage } : {}),
|
|
160
|
+
});
|
|
161
|
+
await this.audit("error", "CHAT_FAILED", {
|
|
162
|
+
reason: "provider-error",
|
|
163
|
+
provider: modelResult.provider,
|
|
164
|
+
});
|
|
165
|
+
return response;
|
|
166
|
+
}
|
|
167
|
+
const text = redactForModel(modelResult.message.content || "");
|
|
168
|
+
const response = buildChatTurnResponse({
|
|
169
|
+
sessionId: this.sessionId,
|
|
170
|
+
modelText: text,
|
|
171
|
+
context,
|
|
172
|
+
provider: modelResult.provider,
|
|
173
|
+
model: modelResult.model,
|
|
174
|
+
status: "ok",
|
|
175
|
+
...(modelResult.usage ? { usage: modelResult.usage } : {}),
|
|
176
|
+
});
|
|
177
|
+
this.memory.addAssistant(response.message, response.contextPaths);
|
|
178
|
+
await this.audit("prompt", "CHAT_MODEL_RESPONSE", {
|
|
179
|
+
status: response.status,
|
|
180
|
+
citations: String(response.citations.length),
|
|
181
|
+
});
|
|
182
|
+
await this.audit("prompt", "CHAT_COMPLETED", {
|
|
183
|
+
turn: String(this.memory.snapshot().turnCount),
|
|
184
|
+
});
|
|
185
|
+
return response;
|
|
186
|
+
}
|
|
187
|
+
clearMemory() {
|
|
188
|
+
this.memory.clear();
|
|
189
|
+
this.lastContext = undefined;
|
|
190
|
+
}
|
|
191
|
+
async end() {
|
|
192
|
+
if (this.auditSession) {
|
|
193
|
+
await endSession(this.auditSession);
|
|
194
|
+
this.auditSession = undefined;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
formatResponse(response) {
|
|
198
|
+
return formatChatResponseForCli(response);
|
|
199
|
+
}
|
|
200
|
+
formatProject() {
|
|
201
|
+
return summarizeProjectForChat(this.root).then(formatProjectSummary);
|
|
202
|
+
}
|
|
203
|
+
formatContext() {
|
|
204
|
+
const paths = this.getLastContextPaths();
|
|
205
|
+
if (!paths.length) {
|
|
206
|
+
return "No context retrieved yet. Ask a project question first.\n";
|
|
207
|
+
}
|
|
208
|
+
const lines = ["Context used for the last answer:", ""];
|
|
209
|
+
for (const p of paths) {
|
|
210
|
+
lines.push(` ✓ ${p}`);
|
|
211
|
+
}
|
|
212
|
+
if (this.lastContext?.limitations.length) {
|
|
213
|
+
lines.push("");
|
|
214
|
+
lines.push("Limitations:");
|
|
215
|
+
for (const l of this.lastContext.limitations.slice(0, 5)) {
|
|
216
|
+
lines.push(` - ${l}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
lines.push("");
|
|
220
|
+
return `${lines.join("\n")}\n`;
|
|
221
|
+
}
|
|
222
|
+
providerLabel() {
|
|
223
|
+
const cfg = publicAiConfig(loadAiConfig());
|
|
224
|
+
if (this.provider.id === "none")
|
|
225
|
+
return "none (AI chat disabled)";
|
|
226
|
+
if (this.provider.id === "mock")
|
|
227
|
+
return "mock (tests/demo)";
|
|
228
|
+
return `${this.provider.id} / ${cfg.model}`;
|
|
229
|
+
}
|
|
230
|
+
async audit(type, summary, detail) {
|
|
231
|
+
if (!this.persistAudit || !this.auditSession)
|
|
232
|
+
return;
|
|
233
|
+
await appendSessionEvent(this.auditSession, {
|
|
234
|
+
type,
|
|
235
|
+
summary,
|
|
236
|
+
risk: type === "error" ? "low" : "none",
|
|
237
|
+
...(detail ? { detail } : {}),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function sanitizeProviderError(message) {
|
|
242
|
+
return redactForModel(message)
|
|
243
|
+
.replace(/Bearer\s+\S+/gi, "Bearer [REDACTED]")
|
|
244
|
+
.replace(/sk-[A-Za-z0-9]+/g, "[REDACTED]");
|
|
245
|
+
}
|
|
246
|
+
export function createChatService(options) {
|
|
247
|
+
return new ChatService(options);
|
|
248
|
+
}
|
|
249
|
+
/** Test helper: unique id without IO */
|
|
250
|
+
export function newChatSessionId() {
|
|
251
|
+
return randomUUID();
|
|
252
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { TruthLabel, ContextCitation } from "../context/types.js";
|
|
2
|
+
import type { AiProviderId, ModelProvider, TokenUsage } from "../../ai/types.js";
|
|
3
|
+
export type ChatRole = "system" | "user" | "assistant";
|
|
4
|
+
export interface ChatMessage {
|
|
5
|
+
role: ChatRole;
|
|
6
|
+
content: string;
|
|
7
|
+
at: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TruthClaim {
|
|
10
|
+
text: string;
|
|
11
|
+
label: TruthLabel;
|
|
12
|
+
citationPaths: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface ChatTurnResponse {
|
|
15
|
+
sessionId: string;
|
|
16
|
+
message: string;
|
|
17
|
+
truthClaims: TruthClaim[];
|
|
18
|
+
citations: ContextCitation[];
|
|
19
|
+
provider: AiProviderId;
|
|
20
|
+
model: string;
|
|
21
|
+
status: "ok" | "provider-none" | "provider-error" | "failed";
|
|
22
|
+
error?: string;
|
|
23
|
+
usage?: TokenUsage;
|
|
24
|
+
contextPaths: string[];
|
|
25
|
+
contextTruncated: boolean;
|
|
26
|
+
limitations: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface ChatMemorySnapshot {
|
|
29
|
+
sessionId: string;
|
|
30
|
+
root: string;
|
|
31
|
+
topic?: string;
|
|
32
|
+
messages: ChatMessage[];
|
|
33
|
+
referencedPaths: string[];
|
|
34
|
+
lastContextPaths: string[];
|
|
35
|
+
turnCount: number;
|
|
36
|
+
}
|
|
37
|
+
export interface ChatServiceOptions {
|
|
38
|
+
root: string;
|
|
39
|
+
/** Inject provider for tests; otherwise load from env */
|
|
40
|
+
provider?: ModelProvider;
|
|
41
|
+
budgetTokens?: number;
|
|
42
|
+
maxMemoryTurns?: number;
|
|
43
|
+
maxMemoryChars?: number;
|
|
44
|
+
/** Persist audit via platform sessions (default true) */
|
|
45
|
+
persistAudit?: boolean;
|
|
46
|
+
/** Appended to PROJECT_CHAT_SYSTEM_PROMPT (modes) */
|
|
47
|
+
systemPromptAddon?: string;
|
|
48
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ContextBundle } from "./types.js";
|
|
2
|
+
import type { RepositoryGraph } from "../../platform/types.js";
|
|
3
|
+
export interface RetrieveContextOptions {
|
|
4
|
+
root: string;
|
|
5
|
+
query: string;
|
|
6
|
+
budgetTokens?: number;
|
|
7
|
+
/** Optional relative paths the caller already wants included */
|
|
8
|
+
includePaths?: string[];
|
|
9
|
+
maxExcerptChars?: number;
|
|
10
|
+
/** Reuse a previously built graph (session cache) */
|
|
11
|
+
graph?: RepositoryGraph & {
|
|
12
|
+
builder?: string;
|
|
13
|
+
astFilesParsed?: number;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Retrieve a budgeted project context pack for the agent.
|
|
18
|
+
* Reuses graph + planContext. Does not invent facts.
|
|
19
|
+
* Hostile paths are rejected via resolveSafeRepoPath.
|
|
20
|
+
*/
|
|
21
|
+
export declare function retrieveProjectContext(options: RetrieveContextOptions): Promise<ContextBundle>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { planContext } from "../../platform/tokens/plan.js";
|
|
4
|
+
import { buildIntelligenceGraph } from "../../intelligence/graph/build.js";
|
|
5
|
+
import { resolveRepoRoot } from "../../utils/path.js";
|
|
6
|
+
import { resolveSafeRepoPath, PathEscapeError } from "../../security/paths.js";
|
|
7
|
+
function estimateTokens(text) {
|
|
8
|
+
return Math.ceil(text.length / 4);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Retrieve a budgeted project context pack for the agent.
|
|
12
|
+
* Reuses graph + planContext. Does not invent facts.
|
|
13
|
+
* Hostile paths are rejected via resolveSafeRepoPath.
|
|
14
|
+
*/
|
|
15
|
+
export async function retrieveProjectContext(options) {
|
|
16
|
+
const root = resolveRepoRoot(options.root);
|
|
17
|
+
const query = options.query.trim() || "project overview";
|
|
18
|
+
const maxExcerpt = options.maxExcerptChars ?? 1_200;
|
|
19
|
+
const citations = [];
|
|
20
|
+
const limitations = [];
|
|
21
|
+
const chunks = [];
|
|
22
|
+
let graph = options.graph;
|
|
23
|
+
try {
|
|
24
|
+
if (!graph) {
|
|
25
|
+
graph = await buildIntelligenceGraph({ root, mode: "auto" });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
limitations.push(`Graph unavailable: ${error instanceof Error ? error.message : String(error)}`);
|
|
30
|
+
return {
|
|
31
|
+
root,
|
|
32
|
+
query,
|
|
33
|
+
citations: [
|
|
34
|
+
{
|
|
35
|
+
source: "repository",
|
|
36
|
+
evidenceType: "metadata",
|
|
37
|
+
confidence: "UNKNOWN",
|
|
38
|
+
note: "Repository graph could not be built for this query.",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
rendered: `Query: ${query}\n(No graph context available.)`,
|
|
42
|
+
estimatedTokens: 32,
|
|
43
|
+
limitations,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const plan = await planContext({
|
|
47
|
+
root,
|
|
48
|
+
graph,
|
|
49
|
+
query,
|
|
50
|
+
budgetTokens: options.budgetTokens ?? 6_000,
|
|
51
|
+
});
|
|
52
|
+
limitations.push(...plan.limitations);
|
|
53
|
+
const paths = new Set(plan.selected.map((s) => s.path));
|
|
54
|
+
for (const p of options.includePaths ?? []) {
|
|
55
|
+
paths.add(p);
|
|
56
|
+
}
|
|
57
|
+
for (const rel of paths) {
|
|
58
|
+
try {
|
|
59
|
+
const abs = resolveSafeRepoPath(root, rel);
|
|
60
|
+
const text = await fs.readFile(abs, "utf8");
|
|
61
|
+
const excerpt = text.slice(0, maxExcerpt);
|
|
62
|
+
const citation = {
|
|
63
|
+
source: "repository",
|
|
64
|
+
path: rel.split(path.sep).join("/"),
|
|
65
|
+
evidenceType: "source-code",
|
|
66
|
+
confidence: "VERIFIED",
|
|
67
|
+
excerpt,
|
|
68
|
+
};
|
|
69
|
+
citations.push(citation);
|
|
70
|
+
chunks.push(`--- FILE ${citation.path} (VERIFIED excerpt) ---\n${excerpt}`);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
if (error instanceof PathEscapeError) {
|
|
74
|
+
citations.push({
|
|
75
|
+
source: "repository",
|
|
76
|
+
path: rel,
|
|
77
|
+
evidenceType: "metadata",
|
|
78
|
+
confidence: "UNKNOWN",
|
|
79
|
+
note: "path_escape: rejected",
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
citations.push({
|
|
84
|
+
source: "repository",
|
|
85
|
+
path: rel,
|
|
86
|
+
evidenceType: "metadata",
|
|
87
|
+
confidence: "UNKNOWN",
|
|
88
|
+
note: error instanceof Error ? error.message : String(error),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (citations.length === 0) {
|
|
93
|
+
citations.push({
|
|
94
|
+
source: "repository",
|
|
95
|
+
evidenceType: "metadata",
|
|
96
|
+
confidence: "UNKNOWN",
|
|
97
|
+
note: "No files selected for this query within the token budget.",
|
|
98
|
+
});
|
|
99
|
+
limitations.push("No file excerpts retrieved — answer must use UNKNOWN where evidence is missing.");
|
|
100
|
+
}
|
|
101
|
+
const rendered = [
|
|
102
|
+
`Project root: ${root}`,
|
|
103
|
+
`Query: ${query}`,
|
|
104
|
+
`Selected files: ${plan.selected.length}`,
|
|
105
|
+
`Budget tokens: ${plan.budgetTokens} (est. used ${plan.estimatedTokens})`,
|
|
106
|
+
"",
|
|
107
|
+
...chunks,
|
|
108
|
+
].join("\n");
|
|
109
|
+
return {
|
|
110
|
+
root,
|
|
111
|
+
query,
|
|
112
|
+
citations,
|
|
113
|
+
rendered,
|
|
114
|
+
estimatedTokens: estimateTokens(rendered),
|
|
115
|
+
limitations,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { TruthLabel } from "./types.js";
|
|
2
|
+
export declare function truthLabelHelp(label: TruthLabel): string;
|
|
3
|
+
/**
|
|
4
|
+
* Downgrade helper: never promote INFERRED/UNKNOWN to VERIFIED accidentally.
|
|
5
|
+
*/
|
|
6
|
+
export declare function minTruth(a: TruthLabel, b: TruthLabel): TruthLabel;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function truthLabelHelp(label) {
|
|
2
|
+
switch (label) {
|
|
3
|
+
case "VERIFIED":
|
|
4
|
+
return "Directly supported by repository/project evidence.";
|
|
5
|
+
case "INFERRED":
|
|
6
|
+
return "Reasonable conclusion from available evidence but not directly established.";
|
|
7
|
+
case "UNKNOWN":
|
|
8
|
+
return "Cannot be determined from current project evidence.";
|
|
9
|
+
case "EXTERNAL":
|
|
10
|
+
return "Requires information outside the repository.";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Downgrade helper: never promote INFERRED/UNKNOWN to VERIFIED accidentally.
|
|
15
|
+
*/
|
|
16
|
+
export function minTruth(a, b) {
|
|
17
|
+
const order = ["VERIFIED", "INFERRED", "UNKNOWN", "EXTERNAL"];
|
|
18
|
+
return order[Math.max(order.indexOf(a), order.indexOf(b))];
|
|
19
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context citation and truth labels for evidence-backed agent answers.
|
|
3
|
+
*/
|
|
4
|
+
export type TruthLabel = "VERIFIED" | "INFERRED" | "UNKNOWN" | "EXTERNAL";
|
|
5
|
+
export type EvidenceType = "source-code" | "project-brain" | "graph" | "architecture" | "test" | "git" | "finding" | "documentation" | "metadata";
|
|
6
|
+
export interface ContextCitation {
|
|
7
|
+
source: "repository" | "brain" | "graph" | "session" | "external";
|
|
8
|
+
path?: string;
|
|
9
|
+
range?: string;
|
|
10
|
+
evidenceType: EvidenceType;
|
|
11
|
+
confidence: TruthLabel;
|
|
12
|
+
excerpt?: string;
|
|
13
|
+
note?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ContextBundle {
|
|
16
|
+
root: string;
|
|
17
|
+
query: string;
|
|
18
|
+
citations: ContextCitation[];
|
|
19
|
+
/** Rendered text for model (DATA channel — untrusted) */
|
|
20
|
+
rendered: string;
|
|
21
|
+
estimatedTokens: number;
|
|
22
|
+
limitations: string[];
|
|
23
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export { AgentState, AgentStateMachine } from "./state.js";
|
|
2
|
+
export type { AgentStateTransition } from "./state.js";
|
|
3
|
+
export { AgentRuntime, DEFAULT_AGENT_LIMITS } from "./runtime.js";
|
|
4
|
+
export type { AgentLimits, AgentAuditEvent, AgentRuntimeOptions, AgentTurnResult, } from "./runtime.js";
|
|
5
|
+
export { retrieveProjectContext } from "./context/retrieve.js";
|
|
6
|
+
export type { RetrieveContextOptions } from "./context/retrieve.js";
|
|
7
|
+
export type { ContextBundle, ContextCitation, EvidenceType, TruthLabel } from "./context/types.js";
|
|
8
|
+
export { truthLabelHelp, minTruth } from "./context/truth.js";
|
|
9
|
+
/** Project Chat (2.1 Milestone 2) */
|
|
10
|
+
export { ChatService, createChatService, ChatMemory, CHAT_PROVIDER_NONE_MESSAGE, buildChatTurnResponse, formatChatResponseForCli, describeTruthLabels, PROJECT_CHAT_SYSTEM_PROMPT, wrapProjectData, summarizeProjectForChat, formatProjectSummary, } from "./chat/index.js";
|
|
11
|
+
export type { ChatTurnResponse, ChatMemorySnapshot, ChatMessage as ProjectChatMessage, TruthClaim, ChatServiceOptions, ProjectChatSummary, } from "./chat/index.js";
|
|
12
|
+
/** Agent tools + plan/approval (2.1 Milestone 3) */
|
|
13
|
+
export { listAgentToolSpecs, getToolSpec, riskForTool, executeAgentTool, isReadTool, newToolCall, } from "./tools/index.js";
|
|
14
|
+
export type { AgentToolName, AgentToolSpec, AgentToolCall, AgentToolResult, AgentRiskLevel, } from "./tools/index.js";
|
|
15
|
+
export { evaluateApproval, formatApprovalPrompt } from "./approvals.js";
|
|
16
|
+
export type { ApprovalRequest, ApprovalResult, ApprovalDecision } from "./approvals.js";
|
|
17
|
+
export { buildAgentPlan, formatAgentPlan, approvePlan } from "./plan.js";
|
|
18
|
+
export type { AgentPlan, AgentPlanStep } from "./plan.js";
|
|
19
|
+
export { runCodingLoop } from "./loop.js";
|
|
20
|
+
export type { CodingLoopOptions, CodingLoopResult } from "./loop.js";
|
|
21
|
+
export { verifyAgentWork, formatVerificationReport } from "./verify.js";
|
|
22
|
+
export type { AgentVerificationReport, VerificationCheck } from "./verify.js";
|
|
23
|
+
export { getModeProfile, defaultStudentMode, parseAgentMode, modeAllowsMutation, modeBlocksToolCategory, } from "./modes.js";
|
|
24
|
+
export type { AgentMode, ModeProfile } from "./modes.js";
|
|
25
|
+
export { StudentService } from "./student.js";
|
|
26
|
+
export type { StudentDocSection, BuildWithMeResult } from "./student.js";
|