@clawmem-ai/clawmem 0.1.18 → 0.1.19
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 +28 -9
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -0
- package/dist/src/collaboration.d.ts +49 -0
- package/dist/src/collaboration.js +69 -0
- package/dist/src/config.d.ts +21 -0
- package/dist/src/config.js +119 -0
- package/dist/src/conversation.d.ts +30 -0
- package/dist/src/conversation.js +323 -0
- package/dist/src/github-client.d.ts +269 -0
- package/dist/src/github-client.js +350 -0
- package/dist/src/keyed-async-queue.d.ts +12 -0
- package/dist/src/keyed-async-queue.js +23 -0
- package/dist/src/memory.d.ts +29 -0
- package/dist/src/memory.js +451 -0
- package/dist/src/recall-sanitize.d.ts +1 -0
- package/dist/src/recall-sanitize.js +149 -0
- package/dist/src/runtime-env.d.ts +2 -0
- package/dist/src/runtime-env.js +12 -0
- package/dist/src/service.d.ts +18 -0
- package/dist/src/service.js +3645 -0
- package/dist/src/state.d.ts +4 -0
- package/dist/src/state.js +182 -0
- package/dist/src/transcript.d.ts +3 -0
- package/dist/src/transcript.js +164 -0
- package/dist/src/types.d.ts +130 -0
- package/dist/src/types.js +1 -0
- package/dist/src/utils.d.ts +26 -0
- package/dist/src/utils.js +62 -0
- package/dist/src/yaml.d.ts +2 -0
- package/dist/src/yaml.js +81 -0
- package/openclaw.plugin.json +14 -1
- package/package.json +21 -7
- package/skills/clawmem/SKILL.md +26 -5
- package/skills/clawmem/references/collaboration.md +13 -5
- package/skills/clawmem/references/review.md +77 -0
- package/skills/clawmem/references/schema.md +44 -1
- package/index.ts +0 -6
- package/src/collaboration.test.ts +0 -71
- package/src/collaboration.ts +0 -109
- package/src/config.test.ts +0 -83
- package/src/config.ts +0 -117
- package/src/conversation.test.ts +0 -120
- package/src/conversation.ts +0 -304
- package/src/github-client.test.ts +0 -101
- package/src/github-client.ts +0 -363
- package/src/keyed-async-queue.ts +0 -26
- package/src/memory.test.ts +0 -588
- package/src/memory.ts +0 -444
- package/src/recall-sanitize.ts +0 -143
- package/src/runtime-env.ts +0 -12
- package/src/service.test.ts +0 -337
- package/src/service.ts +0 -2786
- package/src/state.test.ts +0 -119
- package/src/state.ts +0 -206
- package/src/transcript.ts +0 -186
- package/src/types.ts +0 -86
- package/src/utils.ts +0 -74
- package/src/yaml.ts +0 -88
- package/tsconfig.json +0 -15
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
// Session mirroring: creates/updates GitHub issues and comments for each conversation.
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { AGENT_LABEL_PREFIX, DEFAULT_LABELS, SESSION_TITLE_PREFIX, extractLabelNames } from "./config.js";
|
|
5
|
+
import { parseCandidates } from "./memory.js";
|
|
6
|
+
import { normalizeMessages, readTranscriptSnapshot } from "./transcript.js";
|
|
7
|
+
import { fmtTranscript, localDate, localDateTime, sha256, subKey } from "./utils.js";
|
|
8
|
+
import { stringifyFlatYaml } from "./yaml.js";
|
|
9
|
+
const FINALIZE_SCHEMA_KIND_LIMIT = 24;
|
|
10
|
+
const FINALIZE_SCHEMA_TOPIC_LIMIT = 80;
|
|
11
|
+
export class ConversationMirror {
|
|
12
|
+
client;
|
|
13
|
+
api;
|
|
14
|
+
config;
|
|
15
|
+
constructor(client, api, config) {
|
|
16
|
+
this.client = client;
|
|
17
|
+
this.api = api;
|
|
18
|
+
this.config = config;
|
|
19
|
+
}
|
|
20
|
+
shouldMirror(sessionId, messages) {
|
|
21
|
+
if (sessionId.startsWith("slug-generator-"))
|
|
22
|
+
return false;
|
|
23
|
+
const first = messages.find((m) => m.role === "user")?.text ?? "";
|
|
24
|
+
if (first.includes("generate a short 1-2 word filename slug") && first.includes("Reply with ONLY the slug"))
|
|
25
|
+
return false;
|
|
26
|
+
if (first.includes("Write the final issue summary and extract durable memory candidates from the conversation below."))
|
|
27
|
+
return false;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
async loadSnapshot(session, fallback) {
|
|
31
|
+
const normalizedFallback = normalizeMessages(fallback);
|
|
32
|
+
if (normalizedFallback.length > 0) {
|
|
33
|
+
return { sessionId: session.sessionId, messages: normalizedFallback };
|
|
34
|
+
}
|
|
35
|
+
const filePath = await this.resolveTranscriptPath(session.sessionFile);
|
|
36
|
+
if (filePath) {
|
|
37
|
+
session.sessionFile = filePath;
|
|
38
|
+
try {
|
|
39
|
+
const t = await readTranscriptSnapshot(filePath);
|
|
40
|
+
return { sessionId: t.sessionId ?? session.sessionId, messages: t.messages };
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
this.api.logger.warn(`clawmem: transcript read failed for ${filePath}: ${String(error)}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { sessionId: session.sessionId, messages: normalizedFallback };
|
|
47
|
+
}
|
|
48
|
+
async ensureIssue(session, snapshot) {
|
|
49
|
+
if (session.issueNumber) {
|
|
50
|
+
const existing = await this.lookupBoundIssue(session);
|
|
51
|
+
if (existing && this.isBoundIssue(session, existing)) {
|
|
52
|
+
session.issueTitle = existing.title?.trim() || session.issueTitle;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.api.logger.warn(`clawmem: issue binding for ${session.sessionId} is stale or mismatched (${session.issueNumber}); recreating`);
|
|
56
|
+
this.resetIssueBinding(session);
|
|
57
|
+
}
|
|
58
|
+
// Use placeholder title; real title is generated by LLM once enough messages are available.
|
|
59
|
+
const title = deriveInitialTitle(snapshot.messages, session.sessionId);
|
|
60
|
+
const labels = this.buildLabels(session, snapshot, false);
|
|
61
|
+
const body = this.renderBody(session, snapshot, "pending", false);
|
|
62
|
+
await this.client.ensureLabels(labels);
|
|
63
|
+
const issue = await this.client.createIssue({ title, body, labels });
|
|
64
|
+
session.issueNumber = issue.number;
|
|
65
|
+
session.issueTitle = issue.title ?? title;
|
|
66
|
+
session.titleSource = "placeholder";
|
|
67
|
+
session.lastSummaryHash = sha256(`${title}\n${body}\nopen`);
|
|
68
|
+
session.createdAt = new Date().toISOString();
|
|
69
|
+
session.updatedAt = session.createdAt;
|
|
70
|
+
}
|
|
71
|
+
async syncBody(session, snapshot, summary, closed, titleOverride) {
|
|
72
|
+
if (!session.issueNumber)
|
|
73
|
+
return;
|
|
74
|
+
// Prefer explicit override (LLM-generated title), then keep existing title, then fall back to session ID.
|
|
75
|
+
const title = titleOverride?.trim() || session.issueTitle || `${SESSION_TITLE_PREFIX}${session.sessionId}`;
|
|
76
|
+
const body = this.renderBody(session, snapshot, summary, closed);
|
|
77
|
+
const hash = sha256(`${title}\n${body}\n${closed ? "closed" : "open"}`);
|
|
78
|
+
if (hash === session.lastSummaryHash)
|
|
79
|
+
return;
|
|
80
|
+
await this.client.updateIssue(session.issueNumber, { title, body, ...(closed ? { state: "closed" } : {}) });
|
|
81
|
+
session.issueTitle = title;
|
|
82
|
+
if (titleOverride?.trim())
|
|
83
|
+
session.titleSource = "llm";
|
|
84
|
+
session.lastSummaryHash = hash;
|
|
85
|
+
}
|
|
86
|
+
async syncLabels(session, snapshot, closed) {
|
|
87
|
+
if (!session.issueNumber)
|
|
88
|
+
return;
|
|
89
|
+
const labels = this.buildLabels(session, snapshot, closed);
|
|
90
|
+
await this.client.ensureLabels(labels);
|
|
91
|
+
await this.client.syncManagedLabels(session.issueNumber, labels);
|
|
92
|
+
}
|
|
93
|
+
async appendComments(issueNumber, messages) {
|
|
94
|
+
let count = 0;
|
|
95
|
+
for (const msg of messages) {
|
|
96
|
+
try {
|
|
97
|
+
await this.client.createComment(issueNumber, `role: ${msg.role}\n\n${msg.text.trim()}`);
|
|
98
|
+
count++;
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
this.api.logger.warn(`clawmem: conversation comment failed: ${String(error)}`);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return count;
|
|
106
|
+
}
|
|
107
|
+
async generateFinalArtifacts(session, snapshot, schema) {
|
|
108
|
+
if (snapshot.messages.length === 0)
|
|
109
|
+
throw new Error("no conversation messages to finalize");
|
|
110
|
+
const subagent = this.api.runtime.subagent;
|
|
111
|
+
const sessionKey = subKey(session, "finalize");
|
|
112
|
+
const message = buildFinalizeArtifactsPrompt(snapshot, schema);
|
|
113
|
+
try {
|
|
114
|
+
const run = await subagent.run({
|
|
115
|
+
sessionKey,
|
|
116
|
+
message,
|
|
117
|
+
deliver: false,
|
|
118
|
+
lane: "clawmem-finalize",
|
|
119
|
+
idempotencyKey: sha256(`${session.sessionId}:${snapshot.messages.length}:finalize-v2`),
|
|
120
|
+
extraSystemPrompt: "You finalize ClawMem conversations. Output JSON only with summary, title, and durable memory candidates. Reuse existing schema when it fits and keep human-readable memory text in the conversation language.",
|
|
121
|
+
});
|
|
122
|
+
const wait = await subagent.waitForRun({
|
|
123
|
+
runId: run.runId,
|
|
124
|
+
timeoutMs: Math.max(this.config.summaryWaitTimeoutMs, this.config.memoryExtractWaitTimeoutMs),
|
|
125
|
+
});
|
|
126
|
+
if (wait.status === "timeout")
|
|
127
|
+
throw new Error("finalize subagent timed out");
|
|
128
|
+
if (wait.status === "error")
|
|
129
|
+
throw new Error(wait.error || "finalize subagent failed");
|
|
130
|
+
const msgs = normalizeMessages((await subagent.getSessionMessages({ sessionKey, limit: 50 })).messages);
|
|
131
|
+
const text = [...msgs].reverse().find((entry) => entry.role === "assistant" && entry.text.trim())?.text;
|
|
132
|
+
if (!text)
|
|
133
|
+
throw new Error("finalize subagent returned no assistant text");
|
|
134
|
+
return parseFinalArtifacts(text);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
subagent.deleteSession({ sessionKey, deleteTranscript: true }).catch(() => { });
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
buildLabels(session, _snapshot, _closed) {
|
|
141
|
+
const labels = new Set([...DEFAULT_LABELS, "type:conversation", `session:${session.sessionId}`]);
|
|
142
|
+
if (session.agentId)
|
|
143
|
+
labels.add(`${AGENT_LABEL_PREFIX}${session.agentId}`);
|
|
144
|
+
return [...labels].filter((l) => l.trim().length > 0);
|
|
145
|
+
}
|
|
146
|
+
renderBody(session, snapshot, summary, _closed) {
|
|
147
|
+
const dates = this.resolveDates(session, snapshot.messages);
|
|
148
|
+
return stringifyFlatYaml([
|
|
149
|
+
["type", "conversation"], ["session_id", session.sessionId], ["date", dates.date],
|
|
150
|
+
["start_at", dates.startAt], ["end_at", dates.endAt],
|
|
151
|
+
["summary", summary],
|
|
152
|
+
]);
|
|
153
|
+
}
|
|
154
|
+
resolveDates(session, messages) {
|
|
155
|
+
const ts = messages.map((m) => m.timestamp).filter((v) => Boolean(v?.trim()))
|
|
156
|
+
.map((v) => new Date(v)).filter((d) => Number.isFinite(d.getTime()));
|
|
157
|
+
const fallbackStart = session.createdAt ? new Date(session.createdAt) : new Date();
|
|
158
|
+
const fallbackEnd = session.updatedAt ? new Date(session.updatedAt) : fallbackStart;
|
|
159
|
+
const start = ts[0] ?? fallbackStart, end = ts.at(-1) ?? fallbackEnd;
|
|
160
|
+
return { date: localDate(start), startAt: localDateTime(start), endAt: localDateTime(end) };
|
|
161
|
+
}
|
|
162
|
+
async resolveTranscriptPath(filePath) {
|
|
163
|
+
if (!filePath)
|
|
164
|
+
return null;
|
|
165
|
+
if (await fexists(filePath))
|
|
166
|
+
return filePath;
|
|
167
|
+
try {
|
|
168
|
+
const dir = path.dirname(filePath), prefix = `${path.basename(filePath)}.reset.`;
|
|
169
|
+
const latest = (await fs.promises.readdir(dir, { withFileTypes: true }))
|
|
170
|
+
.filter((e) => e.isFile() && e.name.startsWith(prefix)).map((e) => e.name).sort().at(-1);
|
|
171
|
+
if (latest) {
|
|
172
|
+
this.api.logger.info?.(`clawmem: using reset transcript ${path.join(dir, latest)} because ${filePath} is missing`);
|
|
173
|
+
return path.join(dir, latest);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch { /* directory unreadable */ }
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
async lookupBoundIssue(session) {
|
|
180
|
+
if (!session.issueNumber)
|
|
181
|
+
return null;
|
|
182
|
+
try {
|
|
183
|
+
return await this.client.getIssue(session.issueNumber);
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (isNotFoundError(error))
|
|
187
|
+
return null;
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
isBoundIssue(session, issue) {
|
|
192
|
+
const labels = extractLabelNames(issue.labels);
|
|
193
|
+
return labels.includes("type:conversation") && labels.includes(`session:${session.sessionId}`);
|
|
194
|
+
}
|
|
195
|
+
resetIssueBinding(session) {
|
|
196
|
+
session.issueNumber = undefined;
|
|
197
|
+
session.issueTitle = undefined;
|
|
198
|
+
session.titleSource = undefined;
|
|
199
|
+
session.lastSummaryHash = undefined;
|
|
200
|
+
session.lastMirroredCount = 0;
|
|
201
|
+
session.turnCount = 0;
|
|
202
|
+
session.finalizedAt = undefined;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
export function buildFinalizeArtifactsPrompt(snapshot, schema) {
|
|
206
|
+
return [
|
|
207
|
+
"Write the final issue summary and extract durable memory candidates from the conversation below.",
|
|
208
|
+
'Return valid JSON only in the form {"summary":"...","title":"...","candidates":[{"title":"...","detail":"...","kind":"...","topics":["..."],"evidence":"..."}]}.',
|
|
209
|
+
"The summary should be concise, factual, and written in 2-4 sentences.",
|
|
210
|
+
"Do not include markdown, bullet points, or analysis.",
|
|
211
|
+
"",
|
|
212
|
+
"Title rules:",
|
|
213
|
+
"- Under 50 characters, accurately describe the main topic or task.",
|
|
214
|
+
"- Should let someone immediately know what the conversation is about.",
|
|
215
|
+
"- Must be in the same language as the majority of the conversation content.",
|
|
216
|
+
"- Good: precise, descriptive, specific. Bad: vague, overly creative, generic.",
|
|
217
|
+
"",
|
|
218
|
+
"Candidate rules:",
|
|
219
|
+
"- Extract only durable facts, preferences, decisions, constraints, workflows, and ongoing context worth remembering later.",
|
|
220
|
+
"- Each candidate must represent one durable fact. Split independent facts into separate candidates.",
|
|
221
|
+
"- Prefer a concise explicit title for each candidate whenever the fact can be named clearly.",
|
|
222
|
+
"- Candidate titles and details must be in the same language as the majority of the conversation content.",
|
|
223
|
+
"- Do not extract temporary requests, tool chatter, startup boilerplate, or summaries about internal helper sessions.",
|
|
224
|
+
"- Reuse existing schema labels when one already fits.",
|
|
225
|
+
"- If no existing kind or topic fits, create one short stable machine-readable label instead of a translated or near-duplicate variant.",
|
|
226
|
+
"- Keep kind and topic labels short, reusable, low-cardinality, and machine-readable.",
|
|
227
|
+
"- Evidence is optional. If present, keep it short and quote-free.",
|
|
228
|
+
"- Prefer an empty candidates array when nothing durable was learned.",
|
|
229
|
+
"",
|
|
230
|
+
...buildFinalizeSchemaSection(schema),
|
|
231
|
+
"<conversation>",
|
|
232
|
+
fmtTranscript(snapshot.messages),
|
|
233
|
+
"</conversation>",
|
|
234
|
+
].join("\n");
|
|
235
|
+
}
|
|
236
|
+
function buildFinalizeSchemaSection(schema) {
|
|
237
|
+
if (!schema)
|
|
238
|
+
return [];
|
|
239
|
+
const kinds = schema.kinds.map((kind) => kind.trim()).filter(Boolean);
|
|
240
|
+
const topics = schema.topics.map((topic) => topic.trim()).filter(Boolean);
|
|
241
|
+
if (kinds.length === 0 && topics.length === 0)
|
|
242
|
+
return [];
|
|
243
|
+
const kindLines = kinds.slice(0, FINALIZE_SCHEMA_KIND_LIMIT).map((kind) => `- kind:${kind}`);
|
|
244
|
+
const topicLines = topics.slice(0, FINALIZE_SCHEMA_TOPIC_LIMIT).map((topic) => `- topic:${topic}`);
|
|
245
|
+
const kindOverflow = kinds.length > kindLines.length ? [`- ...and ${kinds.length - kindLines.length} more kinds`] : [];
|
|
246
|
+
const topicOverflow = topics.length > topicLines.length ? [`- ...and ${topics.length - topicLines.length} more topics`] : [];
|
|
247
|
+
return [
|
|
248
|
+
"Current schema to reuse first:",
|
|
249
|
+
"<current-schema>",
|
|
250
|
+
"Kinds:",
|
|
251
|
+
...(kindLines.length > 0 ? kindLines : ["- None"]),
|
|
252
|
+
...kindOverflow,
|
|
253
|
+
"Topics:",
|
|
254
|
+
...(topicLines.length > 0 ? topicLines : ["- None"]),
|
|
255
|
+
...topicOverflow,
|
|
256
|
+
"</current-schema>",
|
|
257
|
+
"Prefer these existing labels whenever they fit. Only create a new label when none of the current labels matches the fact you are storing.",
|
|
258
|
+
"",
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
async function fexists(p) { try {
|
|
262
|
+
return (await fs.promises.stat(p)).isFile();
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return false;
|
|
266
|
+
} }
|
|
267
|
+
function isNotFoundError(error) {
|
|
268
|
+
const text = String(error);
|
|
269
|
+
return text.includes("HTTP 404");
|
|
270
|
+
}
|
|
271
|
+
/** Derive an initial placeholder title for a new conversation. The real title is generated by LLM once enough messages are available. */
|
|
272
|
+
export function deriveInitialTitle(_messages, sessionId) {
|
|
273
|
+
return `${SESSION_TITLE_PREFIX}${sessionId}`;
|
|
274
|
+
}
|
|
275
|
+
function parseSummaryAndTitle(raw) {
|
|
276
|
+
const tryParse = (s) => {
|
|
277
|
+
try {
|
|
278
|
+
const p = JSON.parse(s);
|
|
279
|
+
const summary = typeof p?.summary === "string" && p.summary.trim() ? p.summary.trim() : null;
|
|
280
|
+
if (!summary)
|
|
281
|
+
return null;
|
|
282
|
+
const title = typeof p?.title === "string" && p.title.trim() ? p.title.trim() : undefined;
|
|
283
|
+
return { summary, title };
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
const i = s.indexOf("{"), j = s.lastIndexOf("}");
|
|
287
|
+
if (i >= 0 && j > i) {
|
|
288
|
+
try {
|
|
289
|
+
const p = JSON.parse(s.slice(i, j + 1));
|
|
290
|
+
const summary = typeof p?.summary === "string" && p.summary.trim() ? p.summary.trim() : null;
|
|
291
|
+
if (!summary)
|
|
292
|
+
return null;
|
|
293
|
+
const title = typeof p?.title === "string" && p.title.trim() ? p.title.trim() : undefined;
|
|
294
|
+
return { summary, title };
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
const t = raw.trim();
|
|
304
|
+
const direct = tryParse(t);
|
|
305
|
+
if (direct)
|
|
306
|
+
return direct;
|
|
307
|
+
const f = /^```(?:json)?\s*([\s\S]*?)```$/i.exec(t);
|
|
308
|
+
if (f?.[1]) {
|
|
309
|
+
const nested = tryParse(f[1].trim());
|
|
310
|
+
if (nested)
|
|
311
|
+
return nested;
|
|
312
|
+
}
|
|
313
|
+
return { summary: t };
|
|
314
|
+
}
|
|
315
|
+
function parseFinalArtifacts(raw) {
|
|
316
|
+
const parsedSummary = parseSummaryAndTitle(raw);
|
|
317
|
+
const candidates = parseCandidates(raw);
|
|
318
|
+
return {
|
|
319
|
+
summary: parsedSummary.summary,
|
|
320
|
+
...(parsedSummary.title ? { title: parsedSummary.title } : {}),
|
|
321
|
+
candidates,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import type { AgentRegistrationResponse, AnonymousSessionResponse, ClawMemResolvedRoute } from "./types.js";
|
|
2
|
+
export type IssueResponse = {
|
|
3
|
+
id?: number;
|
|
4
|
+
number: number;
|
|
5
|
+
title?: string;
|
|
6
|
+
body?: string;
|
|
7
|
+
state?: string;
|
|
8
|
+
state_reason?: string | null;
|
|
9
|
+
locked?: boolean;
|
|
10
|
+
labels?: Array<{
|
|
11
|
+
name?: string;
|
|
12
|
+
} | string>;
|
|
13
|
+
assignees?: Array<{
|
|
14
|
+
login?: string;
|
|
15
|
+
name?: string;
|
|
16
|
+
}>;
|
|
17
|
+
user?: {
|
|
18
|
+
login?: string;
|
|
19
|
+
name?: string;
|
|
20
|
+
};
|
|
21
|
+
comments?: number;
|
|
22
|
+
created_at?: string;
|
|
23
|
+
updated_at?: string;
|
|
24
|
+
closed_at?: string | null;
|
|
25
|
+
html_url?: string;
|
|
26
|
+
url?: string;
|
|
27
|
+
};
|
|
28
|
+
export type CommentResponse = {
|
|
29
|
+
id?: number;
|
|
30
|
+
body?: string;
|
|
31
|
+
created_at?: string;
|
|
32
|
+
updated_at?: string;
|
|
33
|
+
html_url?: string;
|
|
34
|
+
url?: string;
|
|
35
|
+
in_reply_to_id?: number | null;
|
|
36
|
+
user?: {
|
|
37
|
+
login?: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
type LabelResponse = {
|
|
42
|
+
name?: string;
|
|
43
|
+
color?: string;
|
|
44
|
+
description?: string;
|
|
45
|
+
};
|
|
46
|
+
type PermissionMap = Record<string, boolean | undefined>;
|
|
47
|
+
export type RepoResponse = {
|
|
48
|
+
name?: string;
|
|
49
|
+
full_name?: string;
|
|
50
|
+
description?: string;
|
|
51
|
+
private?: boolean;
|
|
52
|
+
owner?: {
|
|
53
|
+
login?: string;
|
|
54
|
+
};
|
|
55
|
+
permissions?: PermissionMap;
|
|
56
|
+
role_name?: string;
|
|
57
|
+
};
|
|
58
|
+
type OrgResponse = {
|
|
59
|
+
id?: number;
|
|
60
|
+
login?: string;
|
|
61
|
+
name?: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
default_repository_permission?: string;
|
|
64
|
+
};
|
|
65
|
+
type TeamResponse = {
|
|
66
|
+
id?: number;
|
|
67
|
+
slug?: string;
|
|
68
|
+
name?: string;
|
|
69
|
+
description?: string;
|
|
70
|
+
privacy?: string;
|
|
71
|
+
permission?: string;
|
|
72
|
+
role_name?: string;
|
|
73
|
+
permissions?: PermissionMap;
|
|
74
|
+
};
|
|
75
|
+
type CollaboratorResponse = {
|
|
76
|
+
id?: number;
|
|
77
|
+
login?: string;
|
|
78
|
+
name?: string;
|
|
79
|
+
permissions?: PermissionMap;
|
|
80
|
+
role_name?: string;
|
|
81
|
+
organization_member?: boolean;
|
|
82
|
+
outside_collaborator?: boolean;
|
|
83
|
+
type?: string;
|
|
84
|
+
};
|
|
85
|
+
type CurrentUserResponse = {
|
|
86
|
+
id?: number;
|
|
87
|
+
login?: string;
|
|
88
|
+
name?: string;
|
|
89
|
+
};
|
|
90
|
+
type RepositoryInvitationResponse = {
|
|
91
|
+
id?: number;
|
|
92
|
+
created_at?: string;
|
|
93
|
+
permissions?: string;
|
|
94
|
+
repository?: RepoResponse;
|
|
95
|
+
invitee?: {
|
|
96
|
+
login?: string;
|
|
97
|
+
name?: string;
|
|
98
|
+
};
|
|
99
|
+
inviter?: {
|
|
100
|
+
login?: string;
|
|
101
|
+
name?: string;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
type TeamMembershipResponse = {
|
|
105
|
+
state?: string;
|
|
106
|
+
role?: string;
|
|
107
|
+
};
|
|
108
|
+
type OrganizationMembershipResponse = {
|
|
109
|
+
state?: string;
|
|
110
|
+
role?: string;
|
|
111
|
+
organization?: OrgResponse;
|
|
112
|
+
user?: CollaboratorResponse;
|
|
113
|
+
};
|
|
114
|
+
type InvitationResponse = {
|
|
115
|
+
id?: number;
|
|
116
|
+
role?: string;
|
|
117
|
+
created_at?: string;
|
|
118
|
+
expires_at?: string | null;
|
|
119
|
+
email?: string;
|
|
120
|
+
login?: string;
|
|
121
|
+
organization?: OrgResponse;
|
|
122
|
+
invitee?: {
|
|
123
|
+
login?: string;
|
|
124
|
+
};
|
|
125
|
+
inviter?: {
|
|
126
|
+
login?: string;
|
|
127
|
+
};
|
|
128
|
+
team_ids?: number[];
|
|
129
|
+
teams?: TeamResponse[];
|
|
130
|
+
};
|
|
131
|
+
export declare class GitHubIssueClient {
|
|
132
|
+
private readonly config;
|
|
133
|
+
private readonly log;
|
|
134
|
+
constructor(config: ClawMemResolvedRoute, log: {
|
|
135
|
+
warn?: (msg: string) => void;
|
|
136
|
+
});
|
|
137
|
+
repo(): string | undefined;
|
|
138
|
+
defaultRepo(): string | undefined;
|
|
139
|
+
createIssue(params: {
|
|
140
|
+
title: string;
|
|
141
|
+
body?: string;
|
|
142
|
+
labels?: string[];
|
|
143
|
+
assignees?: string[];
|
|
144
|
+
assignee?: string;
|
|
145
|
+
state?: "open" | "closed";
|
|
146
|
+
stateReason?: string;
|
|
147
|
+
}): Promise<IssueResponse>;
|
|
148
|
+
updateIssue(n: number, params: {
|
|
149
|
+
title?: string;
|
|
150
|
+
body?: string;
|
|
151
|
+
state?: "open" | "closed";
|
|
152
|
+
stateReason?: string;
|
|
153
|
+
labels?: string[];
|
|
154
|
+
assignees?: string[];
|
|
155
|
+
locked?: boolean;
|
|
156
|
+
}): Promise<IssueResponse>;
|
|
157
|
+
getIssue(n: number): Promise<IssueResponse>;
|
|
158
|
+
createComment(issueNumber: number, body: string, params?: {
|
|
159
|
+
inReplyTo?: number;
|
|
160
|
+
}): Promise<CommentResponse>;
|
|
161
|
+
listComments(issueNumber: number, params?: {
|
|
162
|
+
page?: number;
|
|
163
|
+
perPage?: number;
|
|
164
|
+
sort?: "created" | "updated";
|
|
165
|
+
direction?: "asc" | "desc";
|
|
166
|
+
since?: string;
|
|
167
|
+
threaded?: boolean;
|
|
168
|
+
}): Promise<CommentResponse[]>;
|
|
169
|
+
listIssues(params: {
|
|
170
|
+
labels?: string[];
|
|
171
|
+
state?: "open" | "closed" | "all";
|
|
172
|
+
assignee?: string;
|
|
173
|
+
creator?: string;
|
|
174
|
+
mentioned?: string;
|
|
175
|
+
sort?: "created" | "updated" | "comments";
|
|
176
|
+
direction?: "asc" | "desc";
|
|
177
|
+
since?: string;
|
|
178
|
+
page?: number;
|
|
179
|
+
perPage?: number;
|
|
180
|
+
}): Promise<IssueResponse[]>;
|
|
181
|
+
searchIssues(query: string, params?: {
|
|
182
|
+
page?: number;
|
|
183
|
+
perPage?: number;
|
|
184
|
+
}): Promise<IssueResponse[]>;
|
|
185
|
+
listLabels(params?: {
|
|
186
|
+
page?: number;
|
|
187
|
+
perPage?: number;
|
|
188
|
+
}): Promise<LabelResponse[]>;
|
|
189
|
+
listUserRepos(): Promise<RepoResponse[]>;
|
|
190
|
+
createUserRepo(params: {
|
|
191
|
+
name: string;
|
|
192
|
+
description?: string;
|
|
193
|
+
private?: boolean;
|
|
194
|
+
autoInit?: boolean;
|
|
195
|
+
}): Promise<RepoResponse>;
|
|
196
|
+
createOrgRepo(org: string, params: {
|
|
197
|
+
name: string;
|
|
198
|
+
description?: string;
|
|
199
|
+
private?: boolean;
|
|
200
|
+
autoInit?: boolean;
|
|
201
|
+
hasIssues?: boolean;
|
|
202
|
+
hasWiki?: boolean;
|
|
203
|
+
}): Promise<RepoResponse>;
|
|
204
|
+
listUserOrgs(): Promise<OrgResponse[]>;
|
|
205
|
+
getCurrentUser(): Promise<CurrentUserResponse>;
|
|
206
|
+
createUserOrg(params: {
|
|
207
|
+
login: string;
|
|
208
|
+
name?: string;
|
|
209
|
+
defaultRepositoryPermission?: string;
|
|
210
|
+
}): Promise<OrgResponse>;
|
|
211
|
+
getOrg(org: string): Promise<OrgResponse>;
|
|
212
|
+
listOrgMembers(org: string, role?: "admin"): Promise<CollaboratorResponse[]>;
|
|
213
|
+
getOrgMembership(org: string, username: string): Promise<OrganizationMembershipResponse>;
|
|
214
|
+
removeOrgMember(org: string, username: string): Promise<void>;
|
|
215
|
+
removeOrgMembership(org: string, username: string): Promise<void>;
|
|
216
|
+
listOrgTeams(org: string): Promise<TeamResponse[]>;
|
|
217
|
+
getTeam(org: string, teamSlug: string): Promise<TeamResponse>;
|
|
218
|
+
createOrgTeam(org: string, params: {
|
|
219
|
+
name: string;
|
|
220
|
+
description?: string;
|
|
221
|
+
privacy?: "closed" | "secret";
|
|
222
|
+
}): Promise<TeamResponse>;
|
|
223
|
+
updateTeam(org: string, teamSlug: string, params: {
|
|
224
|
+
name?: string;
|
|
225
|
+
description?: string;
|
|
226
|
+
privacy?: "closed" | "secret";
|
|
227
|
+
}): Promise<TeamResponse>;
|
|
228
|
+
deleteTeam(org: string, teamSlug: string): Promise<void>;
|
|
229
|
+
listTeamMembers(org: string, teamSlug: string): Promise<CollaboratorResponse[]>;
|
|
230
|
+
setTeamMembership(org: string, teamSlug: string, username: string, role: "member" | "maintainer"): Promise<TeamMembershipResponse>;
|
|
231
|
+
removeTeamMembership(org: string, teamSlug: string, username: string): Promise<void>;
|
|
232
|
+
listTeamRepos(org: string, teamSlug: string): Promise<RepoResponse[]>;
|
|
233
|
+
setTeamRepoAccess(org: string, teamSlug: string, owner: string, repo: string, permission: "read" | "write" | "admin"): Promise<void>;
|
|
234
|
+
removeTeamRepoAccess(org: string, teamSlug: string, owner: string, repo: string): Promise<void>;
|
|
235
|
+
listRepoCollaborators(owner: string, repo: string): Promise<CollaboratorResponse[]>;
|
|
236
|
+
listRepoInvitations(owner: string, repo: string): Promise<RepositoryInvitationResponse[]>;
|
|
237
|
+
setRepoCollaborator(owner: string, repo: string, username: string, permission: "read" | "write" | "admin"): Promise<RepositoryInvitationResponse | undefined>;
|
|
238
|
+
removeRepoCollaborator(owner: string, repo: string, username: string): Promise<void>;
|
|
239
|
+
getRepo(owner: string, repo: string): Promise<RepoResponse>;
|
|
240
|
+
listUserRepoInvitations(): Promise<RepositoryInvitationResponse[]>;
|
|
241
|
+
acceptUserRepoInvitation(invitationId: number): Promise<void>;
|
|
242
|
+
declineUserRepoInvitation(invitationId: number): Promise<void>;
|
|
243
|
+
listOrgInvitations(org: string): Promise<InvitationResponse[]>;
|
|
244
|
+
createOrgInvitation(org: string, params: {
|
|
245
|
+
inviteeLogin: string;
|
|
246
|
+
role?: "member" | "owner";
|
|
247
|
+
teamIds?: number[];
|
|
248
|
+
expiresInDays?: number;
|
|
249
|
+
}): Promise<InvitationResponse>;
|
|
250
|
+
revokeOrgInvitation(org: string, invitationId: number): Promise<void>;
|
|
251
|
+
listOrgOutsideCollaborators(org: string): Promise<CollaboratorResponse[]>;
|
|
252
|
+
listUserOrgInvitations(): Promise<InvitationResponse[]>;
|
|
253
|
+
acceptUserOrgInvitation(invitationId: number): Promise<void>;
|
|
254
|
+
declineUserOrgInvitation(invitationId: number): Promise<void>;
|
|
255
|
+
transferRepo(owner: string, repo: string, newOwner: string, newRepoName?: string): Promise<RepoResponse>;
|
|
256
|
+
renameRepo(owner: string, repo: string, newName: string): Promise<RepoResponse>;
|
|
257
|
+
ensureLabels(labels: string[]): Promise<void>;
|
|
258
|
+
syncManagedLabels(issueNumber: number, desired: string[]): Promise<void>;
|
|
259
|
+
getRepoInfo(): Promise<{
|
|
260
|
+
description?: string;
|
|
261
|
+
name?: string;
|
|
262
|
+
}>;
|
|
263
|
+
updateRepoDescription(description: string): Promise<void>;
|
|
264
|
+
registerAgent(prefixLogin: string, defaultRepoName: string): Promise<AgentRegistrationResponse>;
|
|
265
|
+
createAnonymousSession(locale?: string): Promise<AnonymousSessionResponse>;
|
|
266
|
+
private repoPath;
|
|
267
|
+
private req;
|
|
268
|
+
}
|
|
269
|
+
export {};
|