@kleroterion/koine 0.0.1 → 0.0.2
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/dist/index.d.ts +34 -4
- package/dist/index.js +85 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -117,18 +117,47 @@ declare function decodePrivateKey(raw: string): string;
|
|
|
117
117
|
declare function mintToken(auth: GitHubAuth): Promise<string>;
|
|
118
118
|
|
|
119
119
|
type OpKind = "read" | "write";
|
|
120
|
+
interface BudgetSnapshot {
|
|
121
|
+
limit: number;
|
|
122
|
+
remaining: number;
|
|
123
|
+
resetAt: number;
|
|
124
|
+
}
|
|
120
125
|
interface GitHubClient {
|
|
121
126
|
rest: Octokit;
|
|
122
|
-
/** Run a REST call through the retry
|
|
127
|
+
/** Run a REST call through the concurrency+retry gate. */
|
|
123
128
|
withRest<T>(op: OpKind, fn: (o: Octokit) => Promise<T>): Promise<T>;
|
|
124
|
-
/** Run a GraphQL document through the gate. */
|
|
129
|
+
/** Run a GraphQL document through the gate; piggybacks rateLimit{} budget tracking on reads. */
|
|
125
130
|
graphql<T = unknown>(op: OpKind, query: string, vars?: Record<string, unknown>): Promise<T>;
|
|
131
|
+
budget(): {
|
|
132
|
+
rest: BudgetSnapshot;
|
|
133
|
+
graphql: BudgetSnapshot;
|
|
134
|
+
};
|
|
126
135
|
}
|
|
127
136
|
interface ClientOptions {
|
|
137
|
+
readConcurrency?: number;
|
|
138
|
+
writeConcurrency?: number;
|
|
128
139
|
maxRetries?: number;
|
|
129
140
|
}
|
|
130
141
|
declare function createGitHubClient(auth: AuthConfig, log: Logger, opts?: ClientOptions): Promise<GitHubClient>;
|
|
131
142
|
|
|
143
|
+
interface ModelUsage {
|
|
144
|
+
inputTokens?: number;
|
|
145
|
+
outputTokens?: number;
|
|
146
|
+
costUSD?: number;
|
|
147
|
+
}
|
|
148
|
+
interface ModelTotals {
|
|
149
|
+
inputTokens: number;
|
|
150
|
+
outputTokens: number;
|
|
151
|
+
costUsd: number;
|
|
152
|
+
}
|
|
153
|
+
declare class CostMeter {
|
|
154
|
+
totalUsd: number;
|
|
155
|
+
private models;
|
|
156
|
+
/** Fold one result message's totals into the meter. */
|
|
157
|
+
record(totalCostUsd: number, modelUsage: Record<string, ModelUsage>): void;
|
|
158
|
+
byModel(): Record<string, ModelTotals>;
|
|
159
|
+
}
|
|
160
|
+
|
|
132
161
|
type StopReason = "success" | "error_max_turns" | "error_max_budget_usd" | "error_during_execution";
|
|
133
162
|
interface RunOutcome {
|
|
134
163
|
ok: boolean;
|
|
@@ -136,7 +165,8 @@ interface RunOutcome {
|
|
|
136
165
|
sessionId: string;
|
|
137
166
|
numTurns: number;
|
|
138
167
|
costUsd: number;
|
|
139
|
-
|
|
168
|
+
/** Per-model token + cost totals aggregated across the run's result messages. */
|
|
169
|
+
modelUsage: Record<string, ModelTotals>;
|
|
140
170
|
errors: string[];
|
|
141
171
|
}
|
|
142
172
|
interface RunHooks {
|
|
@@ -154,4 +184,4 @@ interface LoggerOptions {
|
|
|
154
184
|
}
|
|
155
185
|
declare function createLogger(opts?: LoggerOptions): Logger;
|
|
156
186
|
|
|
157
|
-
export { type ArtifactKind, type AuthConfig, type BouleBlock, type ClientOptions, DISCUSSION_CATEGORIES, type Fingerprint, type GitHubAuth, type GitHubClient, ISSUE_TYPE_NAMES, type LoggerOptions, type MentionResult, OPERATIONAL_LABELS, type Outbound, PRAKTOR_LABELS, PRIORITY_LABELS, PROJECT_FIELDS, type RunHooks, type RunOutcome, STATUS_LABELS, STATUS_OPTIONS, type ScrubResult, type StatusLabel, type StopReason, allBootstrapLabels, bouleId, cleanOutbound, contentHash, createGitHubClient, createLogger, decodePrivateKey, idLabel, kindLabel, mintToken, parseBouleBlock, parseVerifies, renderBouleBlock, runQuery, sanitizeMentions, scrubSecrets, stripBouleBlock, withBouleBlock };
|
|
187
|
+
export { type ArtifactKind, type AuthConfig, type BouleBlock, type BudgetSnapshot, type ClientOptions, CostMeter, DISCUSSION_CATEGORIES, type Fingerprint, type GitHubAuth, type GitHubClient, ISSUE_TYPE_NAMES, type LoggerOptions, type MentionResult, type ModelTotals, type ModelUsage, OPERATIONAL_LABELS, type Outbound, PRAKTOR_LABELS, PRIORITY_LABELS, PROJECT_FIELDS, type RunHooks, type RunOutcome, STATUS_LABELS, STATUS_OPTIONS, type ScrubResult, type StatusLabel, type StopReason, allBootstrapLabels, bouleId, cleanOutbound, contentHash, createGitHubClient, createLogger, decodePrivateKey, idLabel, kindLabel, mintToken, parseBouleBlock, parseVerifies, renderBouleBlock, runQuery, sanitizeMentions, scrubSecrets, stripBouleBlock, withBouleBlock };
|
package/dist/index.js
CHANGED
|
@@ -205,24 +205,11 @@ async function mintToken(auth) {
|
|
|
205
205
|
import { graphql as octokitGraphql } from "@octokit/graphql";
|
|
206
206
|
import { throttling } from "@octokit/plugin-throttling";
|
|
207
207
|
import { Octokit } from "@octokit/rest";
|
|
208
|
+
import pLimit from "p-limit";
|
|
208
209
|
import pRetry, { AbortError } from "p-retry";
|
|
209
210
|
var ThrottledOctokit = Octokit.plugin(throttling);
|
|
210
|
-
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
211
211
|
var jitter = (ms) => ms * (0.5 + Math.random());
|
|
212
|
-
|
|
213
|
-
const e = err;
|
|
214
|
-
const status = e.status ?? e.response?.status;
|
|
215
|
-
const headers = e.response?.headers ?? {};
|
|
216
|
-
if (status === 403 || status === 429) {
|
|
217
|
-
const ra = Number(headers["retry-after"]);
|
|
218
|
-
if (Number.isFinite(ra)) return ra * 1e3;
|
|
219
|
-
const reset = Number(headers["x-ratelimit-reset"]);
|
|
220
|
-
if (Number.isFinite(reset)) return Math.max(0, reset * 1e3 - Date.now());
|
|
221
|
-
return Math.max(6e4, jitter(2 ** attempt * 1e3));
|
|
222
|
-
}
|
|
223
|
-
if (status && status >= 500) return jitter(2 ** attempt * 500);
|
|
224
|
-
throw new AbortError(err);
|
|
225
|
-
}
|
|
212
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
226
213
|
async function createGitHubClient(auth, log, opts = {}) {
|
|
227
214
|
const token = await mintToken(auth.github);
|
|
228
215
|
const rest = new ThrottledOctokit({
|
|
@@ -241,29 +228,88 @@ async function createGitHubClient(auth, log, opts = {}) {
|
|
|
241
228
|
const gql = octokitGraphql.defaults({
|
|
242
229
|
headers: {
|
|
243
230
|
authorization: `token ${token}`,
|
|
231
|
+
// Opt into issue-types + sub-issues GraphQL fields (harmless once GA; required while in preview).
|
|
244
232
|
"GraphQL-Features": "issue_types,sub_issues"
|
|
245
233
|
}
|
|
246
234
|
});
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
235
|
+
const limits = {
|
|
236
|
+
read: pLimit(opts.readConcurrency ?? 8),
|
|
237
|
+
write: pLimit(opts.writeConcurrency ?? 1)
|
|
238
|
+
// serialize writes ⇒ stay under the ~80/min cap
|
|
239
|
+
};
|
|
240
|
+
const budget = {
|
|
241
|
+
rest: { limit: 5e3, remaining: 5e3, resetAt: 0 },
|
|
242
|
+
graphql: { limit: 5e3, remaining: 5e3, resetAt: 0 }
|
|
243
|
+
};
|
|
244
|
+
function classifyWait(err, attempt) {
|
|
245
|
+
const e = err;
|
|
246
|
+
const status = e.status ?? e.response?.status;
|
|
247
|
+
const headers = e.response?.headers ?? {};
|
|
248
|
+
if (status === 403 || status === 429) {
|
|
249
|
+
const ra = Number(headers["retry-after"]);
|
|
250
|
+
if (Number.isFinite(ra)) return ra * 1e3;
|
|
251
|
+
const reset = Number(headers["x-ratelimit-reset"]);
|
|
252
|
+
if (Number.isFinite(reset)) return Math.max(0, reset * 1e3 - Date.now());
|
|
253
|
+
return Math.max(6e4, jitter(2 ** attempt * 1e3));
|
|
254
|
+
}
|
|
255
|
+
if (status && status >= 500) return jitter(2 ** attempt * 500);
|
|
256
|
+
throw new AbortError(err);
|
|
257
|
+
}
|
|
258
|
+
const run = (op, task) => limits[op](
|
|
259
|
+
() => pRetry(
|
|
260
|
+
async (attempt) => {
|
|
261
|
+
try {
|
|
262
|
+
return await task();
|
|
263
|
+
} catch (err) {
|
|
264
|
+
await sleep(classifyWait(err, attempt));
|
|
265
|
+
throw err;
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
{ retries: opts.maxRetries ?? 6, minTimeout: 1e3, factor: 2 }
|
|
269
|
+
)
|
|
257
270
|
);
|
|
258
271
|
return {
|
|
259
272
|
rest,
|
|
260
273
|
withRest: (op, fn) => run(op, () => fn(rest)),
|
|
261
|
-
graphql: (op, query2, vars) => run(op, () =>
|
|
274
|
+
graphql: (op, query2, vars) => run(op, async () => {
|
|
275
|
+
const track = op === "read" && !query2.includes("rateLimit {");
|
|
276
|
+
const doc = track ? query2.replace(/}\s*$/, " rateLimit { limit remaining resetAt }\n}") : query2;
|
|
277
|
+
const data = await gql(doc, vars);
|
|
278
|
+
if (track && data?.rateLimit) {
|
|
279
|
+
budget.graphql.remaining = data.rateLimit.remaining;
|
|
280
|
+
budget.graphql.limit = data.rateLimit.limit;
|
|
281
|
+
budget.graphql.resetAt = Math.floor(new Date(data.rateLimit.resetAt).getTime() / 1e3);
|
|
282
|
+
}
|
|
283
|
+
return data;
|
|
284
|
+
}),
|
|
285
|
+
budget: () => ({ rest: { ...budget.rest }, graphql: { ...budget.graphql } })
|
|
262
286
|
};
|
|
263
287
|
}
|
|
264
288
|
|
|
265
289
|
// src/agent/run.ts
|
|
266
290
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
291
|
+
|
|
292
|
+
// src/observability/cost.ts
|
|
293
|
+
var CostMeter = class {
|
|
294
|
+
totalUsd = 0;
|
|
295
|
+
models = {};
|
|
296
|
+
/** Fold one result message's totals into the meter. */
|
|
297
|
+
record(totalCostUsd, modelUsage) {
|
|
298
|
+
this.totalUsd += totalCostUsd;
|
|
299
|
+
for (const [model, u] of Object.entries(modelUsage)) {
|
|
300
|
+
const acc = this.models[model] ?? { inputTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
301
|
+
acc.inputTokens += u.inputTokens ?? 0;
|
|
302
|
+
acc.outputTokens += u.outputTokens ?? 0;
|
|
303
|
+
acc.costUsd += u.costUSD ?? 0;
|
|
304
|
+
this.models[model] = acc;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
byModel() {
|
|
308
|
+
return this.models;
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/agent/run.ts
|
|
267
313
|
function stopReasonOf(subtype) {
|
|
268
314
|
if (subtype === "success") return "success";
|
|
269
315
|
if (subtype === "error_max_turns") return "error_max_turns";
|
|
@@ -272,11 +318,10 @@ function stopReasonOf(subtype) {
|
|
|
272
318
|
}
|
|
273
319
|
async function runQuery(prompt, options, hooks) {
|
|
274
320
|
const { log } = hooks;
|
|
321
|
+
const meter = new CostMeter();
|
|
275
322
|
let stopReason = "error_during_execution";
|
|
276
323
|
let numTurns = 0;
|
|
277
|
-
let costUsd = 0;
|
|
278
324
|
let sessionId = "";
|
|
279
|
-
let modelUsage = {};
|
|
280
325
|
const errors = [];
|
|
281
326
|
let gotResult = false;
|
|
282
327
|
try {
|
|
@@ -289,11 +334,10 @@ async function runQuery(prompt, options, hooks) {
|
|
|
289
334
|
if (msg.type === "result") {
|
|
290
335
|
stopReason = stopReasonOf(msg.subtype);
|
|
291
336
|
numTurns = msg.num_turns;
|
|
292
|
-
|
|
293
|
-
modelUsage = msg.modelUsage ?? {};
|
|
337
|
+
meter.record(msg.total_cost_usd, msg.modelUsage ?? {});
|
|
294
338
|
if (msg.subtype !== "success") errors.push(...msg.errors ?? []);
|
|
295
339
|
gotResult = true;
|
|
296
|
-
log.info({ stopReason, costUsd, numTurns }, "agent run finished");
|
|
340
|
+
log.info({ stopReason, costUsd: msg.total_cost_usd, numTurns }, "agent run finished");
|
|
297
341
|
}
|
|
298
342
|
}
|
|
299
343
|
} catch (err) {
|
|
@@ -306,7 +350,15 @@ async function runQuery(prompt, options, hooks) {
|
|
|
306
350
|
stopReason = "error_during_execution";
|
|
307
351
|
}
|
|
308
352
|
}
|
|
309
|
-
return {
|
|
353
|
+
return {
|
|
354
|
+
ok: stopReason === "success",
|
|
355
|
+
stopReason,
|
|
356
|
+
sessionId,
|
|
357
|
+
numTurns,
|
|
358
|
+
costUsd: meter.totalUsd,
|
|
359
|
+
modelUsage: meter.byModel(),
|
|
360
|
+
errors
|
|
361
|
+
};
|
|
310
362
|
}
|
|
311
363
|
|
|
312
364
|
// src/observability/logger.ts
|
|
@@ -325,6 +377,7 @@ function createLogger(opts = {}) {
|
|
|
325
377
|
return Object.keys(bindings).length ? base.child(bindings) : base;
|
|
326
378
|
}
|
|
327
379
|
export {
|
|
380
|
+
CostMeter,
|
|
328
381
|
DISCUSSION_CATEGORIES,
|
|
329
382
|
ISSUE_TYPE_NAMES,
|
|
330
383
|
OPERATIONAL_LABELS,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/taxonomy.ts","../src/identity.ts","../src/security/secrets.ts","../src/security/mentions.ts","../src/security/outbound.ts","../src/github/auth.ts","../src/github/client.ts","../src/agent/run.ts","../src/observability/logger.ts"],"sourcesContent":["// src/taxonomy.ts — THE shared label/field contract written to GitHub. Boule produces it; Praktor reads\n// it; both import it here so it can never drift. The string VALUES are the on-wire format (already on\n// live issues) — change them only with a migration.\nimport type { ArtifactKind } from \"./types.js\";\n\nexport const ISSUE_TYPE_NAMES = {\n design: \"Design\",\n requirement: \"Requirement\",\n competitor: \"Competitor\",\n market: \"Market\",\n gap: \"Gap\",\n epic: \"Epic\",\n feature: \"Feature\",\n task: \"Task\",\n spike: \"Spike\",\n} as const;\n\n/** Fallback kind label used when native Issue Types are unavailable. */\nexport const kindLabel = (kind: ArtifactKind): string => `kind:${kind}`;\n\nexport const OPERATIONAL_LABELS = {\n managed: \"boule:managed\",\n needsHuman: \"boule:needs-human\",\n superseded: \"boule:superseded\",\n /** Kill-switch: an OPEN issue carrying this label halts all autonomous writes. */\n halt: \"boule:halt\",\n} as const;\n\n/** An artifact's ACCEPTANCE lifecycle, carried on the Issue as a label. */\nexport const STATUS_LABELS = [\n \"status:draft\",\n \"status:needs-review\",\n \"status:accepted\",\n \"status:superseded\",\n] as const;\nexport type StatusLabel = (typeof STATUS_LABELS)[number];\n\nexport const PRIORITY_LABELS = [\n \"priority:must\",\n \"priority:should\",\n \"priority:could\",\n \"priority:wont\",\n] as const;\n\n/** Praktor's own progress labels — namespaced so they never collide with Boule's lifecycle. */\nexport const PRAKTOR_LABELS = {\n inProgress: \"praktor:in-progress\",\n done: \"praktor:done\",\n blocked: \"praktor:blocked\",\n} as const;\n\n/** Projects v2 custom field names (canonical keys for field values). */\nexport const PROJECT_FIELDS = {\n status: \"Status\",\n kind: \"Kind\",\n priority: \"Priority\",\n rice: \"RICE\",\n wsjf: \"WSJF\",\n moscow: \"MoSCoW\",\n iteration: \"Iteration\",\n} as const;\n\n/** Projects v2 Status column options (the board workflow state). */\nexport const STATUS_OPTIONS = [\n \"Triage\",\n \"In Design\",\n \"In Review\",\n \"Ready\",\n \"In Progress\",\n \"Blocked\",\n \"Done\",\n] as const;\n\nexport const DISCUSSION_CATEGORIES = {\n dailyStatus: \"Daily Status\",\n handoff: \"Agent Handoffs\",\n designReview: \"Design Review\",\n} as const;\n\n/** Every repo label the tools bootstrap (kinds + operational + status + priority). */\nexport function allBootstrapLabels(): string[] {\n const kinds = Object.keys(ISSUE_TYPE_NAMES) as ArtifactKind[];\n return [\n ...kinds.map(kindLabel),\n ...Object.values(OPERATIONAL_LABELS),\n ...STATUS_LABELS,\n ...PRIORITY_LABELS,\n ];\n}\n","// src/identity.ts — the boule:v1 identity block + content addressing. The crux of safe autonomy:\n// pure, deterministic, network-free. Same logical work ⇒ same id ⇒ idempotent re-runs.\nimport { createHash } from \"node:crypto\";\nimport type { ArtifactKind, Fingerprint } from \"./types.js\";\n\nconst BOULE_BEGIN = \"<!-- boule:v1\";\nconst BOULE_END = \"-->\";\n\nexport interface BouleBlock {\n kind: ArtifactKind;\n bouleId: string;\n contentHash: Fingerprint;\n parent?: string;\n runId?: string;\n generatedBy?: string;\n}\n\n/** Stable, content-independent slug. Same natural key ⇒ same id (NOT random). */\nexport function bouleId(kind: ArtifactKind, naturalKey: string): string {\n const slug = naturalKey\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 64);\n return `${kind}:${slug}`;\n}\n\n/** sha256 over the normalized semantic body, EXCLUDING any boule block. */\nexport function contentHash(body: string): Fingerprint {\n const normalized = stripBouleBlock(body)\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/[ \\t]+$/gm, \"\")\n .trim();\n const hex = createHash(\"sha256\").update(normalized, \"utf8\").digest(\"hex\");\n return `sha256:${hex.slice(0, 16)}`;\n}\n\n/** A unique, deterministic dedup label (hashed to stay under GitHub's 50-char label limit). */\nexport function idLabel(id: string): string {\n const hex = createHash(\"sha256\").update(id, \"utf8\").digest(\"hex\");\n return `boule-id-${hex.slice(0, 12)}`;\n}\n\nexport function renderBouleBlock(b: BouleBlock): string {\n return [\n BOULE_BEGIN,\n `kind: ${b.kind}`,\n `boule-id: ${b.bouleId}`,\n `content-hash: ${b.contentHash}`,\n b.parent ? `parent: ${b.parent}` : \"parent:\",\n b.runId ? `run-id: ${b.runId}` : null,\n b.generatedBy ? `generated-by: ${b.generatedBy}` : null,\n BOULE_END,\n ]\n .filter((l): l is string => l !== null)\n .join(\"\\n\");\n}\n\n/** Append the block to a body, recomputing the hash over the body sans-block. */\nexport function withBouleBlock(body: string, meta: Omit<BouleBlock, \"contentHash\">): string {\n const clean = stripBouleBlock(body).trimEnd();\n const block = renderBouleBlock({ ...meta, contentHash: contentHash(clean) });\n return `${clean}\\n\\n${block}\\n`;\n}\n\nexport function parseBouleBlock(body: string): BouleBlock | null {\n const start = body.indexOf(BOULE_BEGIN);\n if (start === -1) return null;\n const end = body.indexOf(BOULE_END, start);\n if (end === -1) return null;\n const inner = body.slice(start + BOULE_BEGIN.length, end);\n const get = (k: string): string | undefined => {\n const m = inner.match(new RegExp(`^${k}:\\\\s*(.+)$`, \"m\"));\n return m?.[1]?.trim() || undefined;\n };\n const kind = get(\"kind\") as ArtifactKind | undefined;\n const id = get(\"boule-id\");\n const hash = get(\"content-hash\");\n if (!kind || !id || !hash) return null;\n return {\n kind,\n bouleId: id,\n contentHash: hash,\n parent: get(\"parent\"),\n runId: get(\"run-id\"),\n generatedBy: get(\"generated-by\"),\n };\n}\n\nexport function stripBouleBlock(body: string): string {\n const start = body.indexOf(BOULE_BEGIN);\n if (start === -1) return body;\n const end = body.indexOf(BOULE_END, start);\n if (end === -1) return body;\n return body.slice(0, start) + body.slice(end + BOULE_END.length);\n}\n\n/** Requirement issue numbers referenced by a `Verifies: #110, #112` link line (Task → Requirement). */\nexport function parseVerifies(body: string): number[] {\n const m = body.match(/^\\s*Verifies:\\s*(.+)$/im);\n if (!m?.[1]) return [];\n return [...m[1].matchAll(/#(\\d+)/g)].map((x) => Number(x[1])).filter((n) => Number.isInteger(n));\n}\n","// src/security/secrets.ts — last-line defense: redact credential-looking strings before any\n// agent-authored text reaches GitHub (a public issue/discussion/PR must never leak a token).\nconst PATTERNS: ReadonlyArray<{ name: string; re: RegExp }> = [\n { name: \"github-token\", re: /\\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})\\b/g },\n { name: \"anthropic-key\", re: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/g },\n { name: \"aws-access-key\", re: /\\bAKIA[0-9A-Z]{16}\\b/g },\n { name: \"private-key\", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },\n { name: \"openai-key\", re: /\\bsk-[A-Za-z0-9]{32,}\\b/g },\n];\n\nexport interface ScrubResult {\n clean: string;\n found: string[]; // distinct credential kinds redacted\n}\n\n/** Replace any credential-looking substrings with `[REDACTED:<kind>]`; report the kinds found. */\nexport function scrubSecrets(text: string): ScrubResult {\n let clean = text;\n const found = new Set<string>();\n for (const { name, re } of PATTERNS) {\n clean = clean.replace(re, () => {\n found.add(name);\n return `[REDACTED:${name}]`;\n });\n }\n return { clean, found: [...found] };\n}\n","// src/security/mentions.ts — neutralize @-mentions so autonomous artifacts never ping people.\n// Wrapping a handle in a backtick code span stops GitHub from sending a notification.\n\n// @handle not preceded by a word char/backtick and not part of an email; 1-39 chars, GitHub rules.\nconst MENTION_RE = /(^|[^A-Za-z0-9_`@/])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?)\\b/g;\n\nexport interface MentionResult {\n clean: string;\n stripped: string[]; // handles neutralized\n}\n\nexport function sanitizeMentions(text: string): MentionResult {\n const stripped: string[] = [];\n const clean = text.replace(MENTION_RE, (_m, pre: string, handle: string) => {\n stripped.push(handle);\n return `${pre}\\`@${handle}\\``;\n });\n return { clean, stripped };\n}\n","// src/security/outbound.ts — the single cleanup applied to every agent-authored string before it\n// reaches GitHub: redact credentials, then neutralize @-mentions.\nimport { sanitizeMentions } from \"./mentions.js\";\nimport { scrubSecrets } from \"./secrets.js\";\n\nexport interface Outbound {\n clean: string;\n secrets: string[]; // kinds of credential redacted\n mentions: string[]; // handles neutralized\n}\n\nexport function cleanOutbound(text: string): Outbound {\n const s = scrubSecrets(text);\n const m = sanitizeMentions(s.clean);\n return { clean: m.clean, secrets: s.found, mentions: m.stripped };\n}\n","// src/github/auth.ts — credential TYPES + token minting only. Each tool resolves its OWN env names\n// into an AuthConfig (Boule uses BOULE_APP_*, Praktor uses PRAKTOR_APP_*) and passes it to the client;\n// koine never reads process.env, so it stays identity-agnostic.\nimport { createAppAuth } from \"@octokit/auth-app\";\n\nexport type GitHubAuth =\n | { kind: \"pat\"; token: string }\n | { kind: \"app\"; appId: string; installationId: string; privateKey: string };\n\nexport interface AuthConfig {\n github: GitHubAuth;\n}\n\n/** Decode a base64-or-PEM private key (CI usually stores a single-line base64 blob). */\nexport function decodePrivateKey(raw: string): string {\n const v = raw.trim();\n if (v.includes(\"BEGIN\") && v.includes(\"PRIVATE KEY\")) return v;\n try {\n return Buffer.from(v, \"base64\").toString(\"utf8\");\n } catch {\n return v;\n }\n}\n\n/** Mint a usable token: a PAT passes through; an App mints a short-lived installation token. */\nexport async function mintToken(auth: GitHubAuth): Promise<string> {\n if (auth.kind === \"pat\") return auth.token;\n const appAuth = createAppAuth({\n appId: auth.appId,\n privateKey: auth.privateKey,\n installationId: Number(auth.installationId),\n });\n const { token } = await appAuth({ type: \"installation\" });\n return token;\n}\n","// src/github/client.ts — THE only path to the GitHub API. All backoff/retry + auth live here so both\n// tools share one hardened transport. Reads are concurrency-friendly; writes serialize via p-retry.\nimport { graphql as octokitGraphql } from \"@octokit/graphql\";\nimport { throttling } from \"@octokit/plugin-throttling\";\nimport { Octokit } from \"@octokit/rest\";\nimport pRetry, { AbortError } from \"p-retry\";\nimport type { Logger } from \"pino\";\nimport { type AuthConfig, mintToken } from \"./auth.js\";\n\nconst ThrottledOctokit = Octokit.plugin(throttling);\ntype OpKind = \"read\" | \"write\";\n\nexport interface GitHubClient {\n rest: Octokit;\n /** Run a REST call through the retry/backoff gate. */\n withRest<T>(op: OpKind, fn: (o: Octokit) => Promise<T>): Promise<T>;\n /** Run a GraphQL document through the gate. */\n graphql<T = unknown>(op: OpKind, query: string, vars?: Record<string, unknown>): Promise<T>;\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\nconst jitter = (ms: number): number => ms * (0.5 + Math.random());\n\nfunction classifyWait(err: unknown, attempt: number): number {\n const e = err as { status?: number; response?: { status?: number; headers?: Record<string, string> } };\n const status = e.status ?? e.response?.status;\n const headers = e.response?.headers ?? {};\n if (status === 403 || status === 429) {\n const ra = Number(headers[\"retry-after\"]);\n if (Number.isFinite(ra)) return ra * 1000;\n const reset = Number(headers[\"x-ratelimit-reset\"]);\n if (Number.isFinite(reset)) return Math.max(0, reset * 1000 - Date.now());\n return Math.max(60_000, jitter(2 ** attempt * 1000));\n }\n if (status && status >= 500) return jitter(2 ** attempt * 500);\n throw new AbortError(err as Error); // 4xx (non-rate) ⇒ don't retry\n}\n\nexport interface ClientOptions {\n maxRetries?: number;\n}\n\nexport async function createGitHubClient(\n auth: AuthConfig,\n log: Logger,\n opts: ClientOptions = {},\n): Promise<GitHubClient> {\n const token = await mintToken(auth.github);\n const rest = new ThrottledOctokit({\n auth: token,\n throttle: {\n onRateLimit: (after, _o, _ok, retryCount) => {\n log.warn({ after, retryCount }, \"primary rate limit\");\n return retryCount < 3;\n },\n onSecondaryRateLimit: (after) => {\n log.warn({ after }, \"secondary rate limit; honoring retry-after\");\n return true;\n },\n },\n });\n const gql = octokitGraphql.defaults({\n headers: {\n authorization: `token ${token}`,\n \"GraphQL-Features\": \"issue_types,sub_issues\",\n },\n });\n\n const run = <T>(_op: OpKind, task: () => Promise<T>): Promise<T> =>\n pRetry(\n async (attempt) => {\n try {\n return await task();\n } catch (err) {\n await sleep(classifyWait(err, attempt));\n throw err;\n }\n },\n { retries: opts.maxRetries ?? 6, minTimeout: 1000, factor: 2 },\n );\n\n return {\n rest,\n withRest: (op, fn) => run(op, () => fn(rest)),\n graphql: <T>(op: OpKind, query: string, vars?: Record<string, unknown>) =>\n run<T>(op, () => gql(query, vars) as Promise<T>),\n };\n}\n","// src/agent/run.ts — the shared Claude Agent SDK run-loop. Resilient to transport noise: the SDK's\n// subprocess can exit non-zero on teardown AFTER emitting a terminal result; that must not override a\n// run whose outcome is already known. A failure BEFORE any result is real and reported as such.\nimport { type Options, query } from \"@anthropic-ai/claude-agent-sdk\";\nimport type { Logger } from \"pino\";\n\nexport type StopReason = \"success\" | \"error_max_turns\" | \"error_max_budget_usd\" | \"error_during_execution\";\n\nexport interface RunOutcome {\n ok: boolean;\n stopReason: StopReason;\n sessionId: string;\n numTurns: number;\n costUsd: number;\n modelUsage: Record<string, unknown>;\n errors: string[];\n}\n\nexport interface RunHooks {\n log: Logger;\n /** Called once with the SDK session id (at init) — e.g. to checkpoint for resume. */\n onSession?: (sessionId: string) => void;\n}\n\nfunction stopReasonOf(subtype: string): StopReason {\n if (subtype === \"success\") return \"success\";\n if (subtype === \"error_max_turns\") return \"error_max_turns\";\n if (subtype === \"error_max_budget_usd\") return \"error_max_budget_usd\";\n return \"error_during_execution\";\n}\n\n/** Drive one query() to completion, returning a normalized outcome. Never throws on transport noise. */\nexport async function runQuery(prompt: string, options: Options, hooks: RunHooks): Promise<RunOutcome> {\n const { log } = hooks;\n let stopReason: StopReason = \"error_during_execution\";\n let numTurns = 0;\n let costUsd = 0;\n let sessionId = \"\";\n let modelUsage: Record<string, unknown> = {};\n const errors: string[] = [];\n let gotResult = false;\n\n try {\n for await (const msg of query({ prompt, options })) {\n if (msg.type === \"system\" && msg.subtype === \"init\") {\n sessionId = msg.session_id;\n log.info({ sessionId }, \"agent run started\");\n hooks.onSession?.(sessionId);\n }\n if (msg.type === \"result\") {\n stopReason = stopReasonOf(msg.subtype);\n numTurns = msg.num_turns;\n costUsd = msg.total_cost_usd;\n modelUsage = (msg.modelUsage ?? {}) as Record<string, unknown>;\n if (msg.subtype !== \"success\") errors.push(...(msg.errors ?? []));\n gotResult = true;\n log.info({ stopReason, costUsd, numTurns }, \"agent run finished\");\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (gotResult) {\n log.warn({ err: message }, \"agent transport error after result; keeping captured outcome\");\n } else {\n log.error({ err: message }, \"agent run failed before producing a result\");\n errors.push(message);\n stopReason = \"error_during_execution\";\n }\n }\n\n return { ok: stopReason === \"success\", stopReason, sessionId, numTurns, costUsd, modelUsage, errors };\n}\n","// src/observability/logger.ts — structured pino logger with credential redaction. Each tool passes its\n// own level + service name; a run-scoped child carries the runId.\nimport pino, { type Logger } from \"pino\";\n\nexport type { Logger };\n\nexport interface LoggerOptions {\n level?: string;\n service?: string;\n runId?: string;\n}\n\nexport function createLogger(opts: LoggerOptions = {}): Logger {\n const base = pino({\n level: opts.level ?? \"info\",\n redact: {\n paths: [\"token\", \"privateKey\", \"*.token\", \"*.privateKey\", \"headers.authorization\"],\n censor: \"[redacted]\",\n },\n });\n const bindings: Record<string, string> = {};\n if (opts.service) bindings.service = opts.service;\n if (opts.runId) bindings.runId = opts.runId;\n return Object.keys(bindings).length ? base.child(bindings) : base;\n}\n"],"mappings":"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","names":["query"]}
|
|
1
|
+
{"version":3,"sources":["../src/taxonomy.ts","../src/identity.ts","../src/security/secrets.ts","../src/security/mentions.ts","../src/security/outbound.ts","../src/github/auth.ts","../src/github/client.ts","../src/agent/run.ts","../src/observability/cost.ts","../src/observability/logger.ts"],"sourcesContent":["// src/taxonomy.ts — THE shared label/field contract written to GitHub. Boule produces it; Praktor reads\n// it; both import it here so it can never drift. The string VALUES are the on-wire format (already on\n// live issues) — change them only with a migration.\nimport type { ArtifactKind } from \"./types.js\";\n\nexport const ISSUE_TYPE_NAMES = {\n design: \"Design\",\n requirement: \"Requirement\",\n competitor: \"Competitor\",\n market: \"Market\",\n gap: \"Gap\",\n epic: \"Epic\",\n feature: \"Feature\",\n task: \"Task\",\n spike: \"Spike\",\n} as const;\n\n/** Fallback kind label used when native Issue Types are unavailable. */\nexport const kindLabel = (kind: ArtifactKind): string => `kind:${kind}`;\n\nexport const OPERATIONAL_LABELS = {\n managed: \"boule:managed\",\n needsHuman: \"boule:needs-human\",\n superseded: \"boule:superseded\",\n /** Kill-switch: an OPEN issue carrying this label halts all autonomous writes. */\n halt: \"boule:halt\",\n} as const;\n\n/** An artifact's ACCEPTANCE lifecycle, carried on the Issue as a label. */\nexport const STATUS_LABELS = [\n \"status:draft\",\n \"status:needs-review\",\n \"status:accepted\",\n \"status:superseded\",\n] as const;\nexport type StatusLabel = (typeof STATUS_LABELS)[number];\n\nexport const PRIORITY_LABELS = [\n \"priority:must\",\n \"priority:should\",\n \"priority:could\",\n \"priority:wont\",\n] as const;\n\n/** Praktor's own progress labels — namespaced so they never collide with Boule's lifecycle. */\nexport const PRAKTOR_LABELS = {\n inProgress: \"praktor:in-progress\",\n done: \"praktor:done\",\n blocked: \"praktor:blocked\",\n} as const;\n\n/** Projects v2 custom field names (canonical keys for field values). */\nexport const PROJECT_FIELDS = {\n status: \"Status\",\n kind: \"Kind\",\n priority: \"Priority\",\n rice: \"RICE\",\n wsjf: \"WSJF\",\n moscow: \"MoSCoW\",\n iteration: \"Iteration\",\n} as const;\n\n/** Projects v2 Status column options (the board workflow state). */\nexport const STATUS_OPTIONS = [\n \"Triage\",\n \"In Design\",\n \"In Review\",\n \"Ready\",\n \"In Progress\",\n \"Blocked\",\n \"Done\",\n] as const;\n\nexport const DISCUSSION_CATEGORIES = {\n dailyStatus: \"Daily Status\",\n handoff: \"Agent Handoffs\",\n designReview: \"Design Review\",\n} as const;\n\n/** Every repo label the tools bootstrap (kinds + operational + status + priority). */\nexport function allBootstrapLabels(): string[] {\n const kinds = Object.keys(ISSUE_TYPE_NAMES) as ArtifactKind[];\n return [\n ...kinds.map(kindLabel),\n ...Object.values(OPERATIONAL_LABELS),\n ...STATUS_LABELS,\n ...PRIORITY_LABELS,\n ];\n}\n","// src/identity.ts — the boule:v1 identity block + content addressing. The crux of safe autonomy:\n// pure, deterministic, network-free. Same logical work ⇒ same id ⇒ idempotent re-runs.\nimport { createHash } from \"node:crypto\";\nimport type { ArtifactKind, Fingerprint } from \"./types.js\";\n\nconst BOULE_BEGIN = \"<!-- boule:v1\";\nconst BOULE_END = \"-->\";\n\nexport interface BouleBlock {\n kind: ArtifactKind;\n bouleId: string;\n contentHash: Fingerprint;\n parent?: string;\n runId?: string;\n generatedBy?: string;\n}\n\n/** Stable, content-independent slug. Same natural key ⇒ same id (NOT random). */\nexport function bouleId(kind: ArtifactKind, naturalKey: string): string {\n const slug = naturalKey\n .toLowerCase()\n .normalize(\"NFKD\")\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 64);\n return `${kind}:${slug}`;\n}\n\n/** sha256 over the normalized semantic body, EXCLUDING any boule block. */\nexport function contentHash(body: string): Fingerprint {\n const normalized = stripBouleBlock(body)\n .replace(/\\r\\n/g, \"\\n\")\n .replace(/[ \\t]+$/gm, \"\")\n .trim();\n const hex = createHash(\"sha256\").update(normalized, \"utf8\").digest(\"hex\");\n return `sha256:${hex.slice(0, 16)}`;\n}\n\n/** A unique, deterministic dedup label (hashed to stay under GitHub's 50-char label limit). */\nexport function idLabel(id: string): string {\n const hex = createHash(\"sha256\").update(id, \"utf8\").digest(\"hex\");\n return `boule-id-${hex.slice(0, 12)}`;\n}\n\nexport function renderBouleBlock(b: BouleBlock): string {\n return [\n BOULE_BEGIN,\n `kind: ${b.kind}`,\n `boule-id: ${b.bouleId}`,\n `content-hash: ${b.contentHash}`,\n b.parent ? `parent: ${b.parent}` : \"parent:\",\n b.runId ? `run-id: ${b.runId}` : null,\n b.generatedBy ? `generated-by: ${b.generatedBy}` : null,\n BOULE_END,\n ]\n .filter((l): l is string => l !== null)\n .join(\"\\n\");\n}\n\n/** Append the block to a body, recomputing the hash over the body sans-block. */\nexport function withBouleBlock(body: string, meta: Omit<BouleBlock, \"contentHash\">): string {\n const clean = stripBouleBlock(body).trimEnd();\n const block = renderBouleBlock({ ...meta, contentHash: contentHash(clean) });\n return `${clean}\\n\\n${block}\\n`;\n}\n\nexport function parseBouleBlock(body: string): BouleBlock | null {\n const start = body.indexOf(BOULE_BEGIN);\n if (start === -1) return null;\n const end = body.indexOf(BOULE_END, start);\n if (end === -1) return null;\n const inner = body.slice(start + BOULE_BEGIN.length, end);\n const get = (k: string): string | undefined => {\n const m = inner.match(new RegExp(`^${k}:\\\\s*(.+)$`, \"m\"));\n return m?.[1]?.trim() || undefined;\n };\n const kind = get(\"kind\") as ArtifactKind | undefined;\n const id = get(\"boule-id\");\n const hash = get(\"content-hash\");\n if (!kind || !id || !hash) return null;\n return {\n kind,\n bouleId: id,\n contentHash: hash,\n parent: get(\"parent\"),\n runId: get(\"run-id\"),\n generatedBy: get(\"generated-by\"),\n };\n}\n\nexport function stripBouleBlock(body: string): string {\n const start = body.indexOf(BOULE_BEGIN);\n if (start === -1) return body;\n const end = body.indexOf(BOULE_END, start);\n if (end === -1) return body;\n return body.slice(0, start) + body.slice(end + BOULE_END.length);\n}\n\n/** Requirement issue numbers referenced by a `Verifies: #110, #112` link line (Task → Requirement). */\nexport function parseVerifies(body: string): number[] {\n const m = body.match(/^\\s*Verifies:\\s*(.+)$/im);\n if (!m?.[1]) return [];\n return [...m[1].matchAll(/#(\\d+)/g)].map((x) => Number(x[1])).filter((n) => Number.isInteger(n));\n}\n","// src/security/secrets.ts — last-line defense: redact credential-looking strings before any\n// agent-authored text reaches GitHub (a public issue/discussion/PR must never leak a token).\nconst PATTERNS: ReadonlyArray<{ name: string; re: RegExp }> = [\n { name: \"github-token\", re: /\\b(?:gh[pousr]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,})\\b/g },\n { name: \"anthropic-key\", re: /\\bsk-ant-[A-Za-z0-9_-]{20,}\\b/g },\n { name: \"aws-access-key\", re: /\\bAKIA[0-9A-Z]{16}\\b/g },\n { name: \"private-key\", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?-----END [A-Z ]*PRIVATE KEY-----/g },\n { name: \"openai-key\", re: /\\bsk-[A-Za-z0-9]{32,}\\b/g },\n];\n\nexport interface ScrubResult {\n clean: string;\n found: string[]; // distinct credential kinds redacted\n}\n\n/** Replace any credential-looking substrings with `[REDACTED:<kind>]`; report the kinds found. */\nexport function scrubSecrets(text: string): ScrubResult {\n let clean = text;\n const found = new Set<string>();\n for (const { name, re } of PATTERNS) {\n clean = clean.replace(re, () => {\n found.add(name);\n return `[REDACTED:${name}]`;\n });\n }\n return { clean, found: [...found] };\n}\n","// src/security/mentions.ts — neutralize @-mentions so autonomous artifacts never ping people.\n// Wrapping a handle in a backtick code span stops GitHub from sending a notification.\n\n// @handle not preceded by a word char/backtick and not part of an email; 1-39 chars, GitHub rules.\nconst MENTION_RE = /(^|[^A-Za-z0-9_`@/])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?)\\b/g;\n\nexport interface MentionResult {\n clean: string;\n stripped: string[]; // handles neutralized\n}\n\nexport function sanitizeMentions(text: string): MentionResult {\n const stripped: string[] = [];\n const clean = text.replace(MENTION_RE, (_m, pre: string, handle: string) => {\n stripped.push(handle);\n return `${pre}\\`@${handle}\\``;\n });\n return { clean, stripped };\n}\n","// src/security/outbound.ts — the single cleanup applied to every agent-authored string before it\n// reaches GitHub: redact credentials, then neutralize @-mentions.\nimport { sanitizeMentions } from \"./mentions.js\";\nimport { scrubSecrets } from \"./secrets.js\";\n\nexport interface Outbound {\n clean: string;\n secrets: string[]; // kinds of credential redacted\n mentions: string[]; // handles neutralized\n}\n\nexport function cleanOutbound(text: string): Outbound {\n const s = scrubSecrets(text);\n const m = sanitizeMentions(s.clean);\n return { clean: m.clean, secrets: s.found, mentions: m.stripped };\n}\n","// src/github/auth.ts — credential TYPES + token minting only. Each tool resolves its OWN env names\n// into an AuthConfig (Boule uses BOULE_APP_*, Praktor uses PRAKTOR_APP_*) and passes it to the client;\n// koine never reads process.env, so it stays identity-agnostic.\nimport { createAppAuth } from \"@octokit/auth-app\";\n\nexport type GitHubAuth =\n | { kind: \"pat\"; token: string }\n | { kind: \"app\"; appId: string; installationId: string; privateKey: string };\n\nexport interface AuthConfig {\n github: GitHubAuth;\n}\n\n/** Decode a base64-or-PEM private key (CI usually stores a single-line base64 blob). */\nexport function decodePrivateKey(raw: string): string {\n const v = raw.trim();\n if (v.includes(\"BEGIN\") && v.includes(\"PRIVATE KEY\")) return v;\n try {\n return Buffer.from(v, \"base64\").toString(\"utf8\");\n } catch {\n return v;\n }\n}\n\n/** Mint a usable token: a PAT passes through; an App mints a short-lived installation token. */\nexport async function mintToken(auth: GitHubAuth): Promise<string> {\n if (auth.kind === \"pat\") return auth.token;\n const appAuth = createAppAuth({\n appId: auth.appId,\n privateKey: auth.privateKey,\n installationId: Number(auth.installationId),\n });\n const { token } = await appAuth({ type: \"installation\" });\n return token;\n}\n","// src/github/client.ts — THE only path to the GitHub API. All backoff/concurrency/budget live here so\n// both tools share one hardened transport: per-op concurrency caps, retry with rate-limit-aware waits,\n// and GraphQL point-budget tracking piggybacked on read queries.\nimport { graphql as octokitGraphql } from \"@octokit/graphql\";\nimport { throttling } from \"@octokit/plugin-throttling\";\nimport { Octokit } from \"@octokit/rest\";\nimport pLimit, { type LimitFunction } from \"p-limit\";\nimport pRetry, { AbortError } from \"p-retry\";\nimport type { Logger } from \"pino\";\nimport { type AuthConfig, mintToken } from \"./auth.js\";\n\nconst ThrottledOctokit = Octokit.plugin(throttling);\n\ntype OpKind = \"read\" | \"write\";\n\nexport interface BudgetSnapshot {\n limit: number;\n remaining: number;\n resetAt: number;\n}\n\nexport interface GitHubClient {\n rest: Octokit;\n /** Run a REST call through the concurrency+retry gate. */\n withRest<T>(op: OpKind, fn: (o: Octokit) => Promise<T>): Promise<T>;\n /** Run a GraphQL document through the gate; piggybacks rateLimit{} budget tracking on reads. */\n graphql<T = unknown>(op: OpKind, query: string, vars?: Record<string, unknown>): Promise<T>;\n budget(): { rest: BudgetSnapshot; graphql: BudgetSnapshot };\n}\n\nconst jitter = (ms: number): number => ms * (0.5 + Math.random());\nconst sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));\n\nexport interface ClientOptions {\n readConcurrency?: number;\n writeConcurrency?: number;\n maxRetries?: number;\n}\n\nexport async function createGitHubClient(\n auth: AuthConfig,\n log: Logger,\n opts: ClientOptions = {},\n): Promise<GitHubClient> {\n const token = await mintToken(auth.github);\n const rest = new ThrottledOctokit({\n auth: token,\n throttle: {\n onRateLimit: (after, _o, _ok, retryCount) => {\n log.warn({ after, retryCount }, \"primary rate limit\");\n return retryCount < 3;\n },\n onSecondaryRateLimit: (after) => {\n log.warn({ after }, \"secondary rate limit; honoring retry-after\");\n return true;\n },\n },\n });\n const gql = octokitGraphql.defaults({\n headers: {\n authorization: `token ${token}`,\n // Opt into issue-types + sub-issues GraphQL fields (harmless once GA; required while in preview).\n \"GraphQL-Features\": \"issue_types,sub_issues\",\n },\n });\n\n const limits: Record<OpKind, LimitFunction> = {\n read: pLimit(opts.readConcurrency ?? 8),\n write: pLimit(opts.writeConcurrency ?? 1), // serialize writes ⇒ stay under the ~80/min cap\n };\n const budget = {\n rest: { limit: 5000, remaining: 5000, resetAt: 0 } as BudgetSnapshot,\n graphql: { limit: 5000, remaining: 5000, resetAt: 0 } as BudgetSnapshot,\n };\n\n function classifyWait(err: unknown, attempt: number): number {\n const e = err as { status?: number; response?: { status?: number; headers?: Record<string, string> } };\n const status = e.status ?? e.response?.status;\n const headers = e.response?.headers ?? {};\n if (status === 403 || status === 429) {\n const ra = Number(headers[\"retry-after\"]);\n if (Number.isFinite(ra)) return ra * 1000;\n const reset = Number(headers[\"x-ratelimit-reset\"]);\n if (Number.isFinite(reset)) return Math.max(0, reset * 1000 - Date.now());\n return Math.max(60_000, jitter(2 ** attempt * 1000));\n }\n if (status && status >= 500) return jitter(2 ** attempt * 500);\n throw new AbortError(err as Error); // 4xx (non-rate) ⇒ don't retry\n }\n\n const run = <T>(op: OpKind, task: () => Promise<T>): Promise<T> =>\n limits[op](() =>\n pRetry(\n async (attempt) => {\n try {\n return await task();\n } catch (err) {\n await sleep(classifyWait(err, attempt));\n throw err;\n }\n },\n { retries: opts.maxRetries ?? 6, minTimeout: 1000, factor: 2 },\n ),\n );\n\n return {\n rest,\n withRest: (op, fn) => run(op, () => fn(rest)),\n graphql: <T>(op: OpKind, query: string, vars?: Record<string, unknown>) =>\n run<T>(op, async () => {\n // `rateLimit` exists only on Query — NEVER inject it into mutations (op \"write\").\n const track = op === \"read\" && !query.includes(\"rateLimit {\");\n const doc = track ? query.replace(/}\\s*$/, \" rateLimit { limit remaining resetAt }\\n}\") : query;\n const data = (await gql(doc, vars)) as T & {\n rateLimit?: { limit: number; remaining: number; resetAt: string };\n };\n if (track && data?.rateLimit) {\n budget.graphql.remaining = data.rateLimit.remaining;\n budget.graphql.limit = data.rateLimit.limit;\n budget.graphql.resetAt = Math.floor(new Date(data.rateLimit.resetAt).getTime() / 1000);\n }\n return data;\n }),\n budget: () => ({ rest: { ...budget.rest }, graphql: { ...budget.graphql } }),\n };\n}\n","// src/agent/run.ts — the shared Claude Agent SDK run-loop. Resilient to transport noise: the SDK's\n// subprocess can exit non-zero on teardown AFTER emitting a terminal result; that must not override a\n// run whose outcome is already known. A failure BEFORE any result is real and reported as such.\nimport { type Options, query } from \"@anthropic-ai/claude-agent-sdk\";\nimport type { Logger } from \"pino\";\nimport { CostMeter, type ModelTotals, type ModelUsage } from \"../observability/cost.js\";\n\nexport type StopReason = \"success\" | \"error_max_turns\" | \"error_max_budget_usd\" | \"error_during_execution\";\n\nexport interface RunOutcome {\n ok: boolean;\n stopReason: StopReason;\n sessionId: string;\n numTurns: number;\n costUsd: number;\n /** Per-model token + cost totals aggregated across the run's result messages. */\n modelUsage: Record<string, ModelTotals>;\n errors: string[];\n}\n\nexport interface RunHooks {\n log: Logger;\n /** Called once with the SDK session id (at init) — e.g. to checkpoint for resume. */\n onSession?: (sessionId: string) => void;\n}\n\nfunction stopReasonOf(subtype: string): StopReason {\n if (subtype === \"success\") return \"success\";\n if (subtype === \"error_max_turns\") return \"error_max_turns\";\n if (subtype === \"error_max_budget_usd\") return \"error_max_budget_usd\";\n return \"error_during_execution\";\n}\n\n/** Drive one query() to completion, returning a normalized outcome. Never throws on transport noise. */\nexport async function runQuery(prompt: string, options: Options, hooks: RunHooks): Promise<RunOutcome> {\n const { log } = hooks;\n const meter = new CostMeter();\n let stopReason: StopReason = \"error_during_execution\";\n let numTurns = 0;\n let sessionId = \"\";\n const errors: string[] = [];\n let gotResult = false;\n\n try {\n for await (const msg of query({ prompt, options })) {\n if (msg.type === \"system\" && msg.subtype === \"init\") {\n sessionId = msg.session_id;\n log.info({ sessionId }, \"agent run started\");\n hooks.onSession?.(sessionId);\n }\n if (msg.type === \"result\") {\n stopReason = stopReasonOf(msg.subtype);\n numTurns = msg.num_turns;\n meter.record(msg.total_cost_usd, (msg.modelUsage ?? {}) as Record<string, ModelUsage>);\n if (msg.subtype !== \"success\") errors.push(...(msg.errors ?? []));\n gotResult = true;\n log.info({ stopReason, costUsd: msg.total_cost_usd, numTurns }, \"agent run finished\");\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n if (gotResult) {\n log.warn({ err: message }, \"agent transport error after result; keeping captured outcome\");\n } else {\n log.error({ err: message }, \"agent run failed before producing a result\");\n errors.push(message);\n stopReason = \"error_during_execution\";\n }\n }\n\n return {\n ok: stopReason === \"success\",\n stopReason,\n sessionId,\n numTurns,\n costUsd: meter.totalUsd,\n modelUsage: meter.byModel(),\n errors,\n };\n}\n","// src/observability/cost.ts — folds SDK result cost/usage into per-model totals. Estimate-only.\nexport interface ModelUsage {\n inputTokens?: number;\n outputTokens?: number;\n costUSD?: number;\n}\n\nexport interface ModelTotals {\n inputTokens: number;\n outputTokens: number;\n costUsd: number;\n}\n\nexport class CostMeter {\n totalUsd = 0;\n private models: Record<string, ModelTotals> = {};\n\n /** Fold one result message's totals into the meter. */\n record(totalCostUsd: number, modelUsage: Record<string, ModelUsage>): void {\n this.totalUsd += totalCostUsd;\n for (const [model, u] of Object.entries(modelUsage)) {\n const acc = this.models[model] ?? { inputTokens: 0, outputTokens: 0, costUsd: 0 };\n acc.inputTokens += u.inputTokens ?? 0;\n acc.outputTokens += u.outputTokens ?? 0;\n acc.costUsd += u.costUSD ?? 0;\n this.models[model] = acc;\n }\n }\n\n byModel(): Record<string, ModelTotals> {\n return this.models;\n }\n}\n","// src/observability/logger.ts — structured pino logger with credential redaction. Each tool passes its\n// own level + service name; a run-scoped child carries the runId.\nimport pino, { type Logger } from \"pino\";\n\nexport type { Logger };\n\nexport interface LoggerOptions {\n level?: string;\n service?: string;\n runId?: string;\n}\n\nexport function createLogger(opts: LoggerOptions = {}): Logger {\n const base = pino({\n level: opts.level ?? \"info\",\n redact: {\n paths: [\"token\", \"privateKey\", \"*.token\", \"*.privateKey\", \"headers.authorization\"],\n censor: \"[redacted]\",\n },\n });\n const bindings: Record<string, string> = {};\n if (opts.service) bindings.service = opts.service;\n if (opts.runId) bindings.runId = opts.runId;\n return Object.keys(bindings).length ? base.child(bindings) : base;\n}\n"],"mappings":"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","names":["query"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kleroterion/koine",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "The common tongue of the Kleroterion tools — shared building blocks for Boule and Praktor: GitHub-artifact taxonomy, the boule:v1 identity block, a gated GitHub client (App/PAT), the resilient Claude Agent SDK run-loop, structured logging, and outbound secret/mention scrubbing.",
|
|
5
5
|
"keywords": ["kleroterion", "boule", "praktor", "github", "claude", "agent-sdk", "shared", "library"],
|
|
6
6
|
"homepage": "https://github.com/kleroterionlabs/koine#readme",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"@octokit/graphql": "^8.1.0",
|
|
43
43
|
"@octokit/auth-app": "^7.1.0",
|
|
44
44
|
"@octokit/plugin-throttling": "^9.3.0",
|
|
45
|
+
"p-limit": "^6.1.0",
|
|
45
46
|
"p-retry": "^6.2.0",
|
|
46
47
|
"pino": "^9.4.0"
|
|
47
48
|
},
|