gitdone-agent 0.6.8 → 0.6.10
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/index.js +67 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -27,7 +27,7 @@ import { randomUUID } from 'node:crypto'
|
|
|
27
27
|
// Reported to the server on every sync so the web UI can flag outdated agents.
|
|
28
28
|
// Keep in lockstep with packages/agent/package.json "version" AND
|
|
29
29
|
// src/lib/agentVersion.ts LATEST_AGENT_VERSION.
|
|
30
|
-
const AGENT_VERSION = '0.6.
|
|
30
|
+
const AGENT_VERSION = '0.6.10'
|
|
31
31
|
|
|
32
32
|
const AGENT_DIR = join(homedir(), '.gitdone-agent')
|
|
33
33
|
const CONFIG_PATH = join(AGENT_DIR, 'config.json')
|
|
@@ -783,6 +783,18 @@ function ensureAiMcpConfig(cfg) {
|
|
|
783
783
|
return AI_MCP_CONFIG_PATH
|
|
784
784
|
}
|
|
785
785
|
|
|
786
|
+
// Sanitise the model value from a command payload into a safe `--model`
|
|
787
|
+
// argument. gitDone sends a stable CLI alias ("opus"/"sonnet"/"haiku"), but we
|
|
788
|
+
// also allow a full model id (letters, digits, dot, dash). Anything else — or a
|
|
789
|
+
// missing value — yields null, so the agent falls back to the machine's own
|
|
790
|
+
// default model. Bounds the length as a belt-and-braces guard (gd-354).
|
|
791
|
+
function aiModelArg(raw) {
|
|
792
|
+
if (typeof raw !== 'string') return null
|
|
793
|
+
const m = raw.trim()
|
|
794
|
+
if (!m || m.length > 100) return null
|
|
795
|
+
return /^[A-Za-z0-9._-]+$/.test(m) ? m : null
|
|
796
|
+
}
|
|
797
|
+
|
|
786
798
|
// Run a headless Claude Code session for an `ai_run` command and stream its
|
|
787
799
|
// output back. Long-running and fire-and-forget: it wires up async handlers and
|
|
788
800
|
// returns immediately so the agent's snapshot loop is never blocked.
|
|
@@ -790,6 +802,7 @@ function runAiCommand(cfg, cmd, repoPath) {
|
|
|
790
802
|
const runId = cmd.payload?.runId
|
|
791
803
|
const prompt = cmd.payload?.prompt ?? ''
|
|
792
804
|
const allowCommit = cmd.payload?.allowCommit === true
|
|
805
|
+
const model = aiModelArg(cmd.payload?.model)
|
|
793
806
|
if (!runId || !prompt) {
|
|
794
807
|
reportCommandResult(cfg, cmd.id, 'error', 'invalid ai_run payload')
|
|
795
808
|
return
|
|
@@ -819,6 +832,8 @@ function runAiCommand(cfg, cmd, repoPath) {
|
|
|
819
832
|
'-p',
|
|
820
833
|
'--output-format', 'stream-json',
|
|
821
834
|
'--verbose',
|
|
835
|
+
// Model chosen in gitDone (project default / task); omitted → machine default (gd-354).
|
|
836
|
+
...(model ? ['--model', model] : []),
|
|
822
837
|
'--permission-mode', 'acceptEdits',
|
|
823
838
|
'--allowedTools', 'Read,Edit,Write,Bash,mcp__gitdone__*',
|
|
824
839
|
// Block git commit/push unless this repo opted in (per-repo aiAutoCommit).
|
|
@@ -961,6 +976,7 @@ async function runAiChat(cfg, cmd, repoPath) {
|
|
|
961
976
|
const images = Array.isArray(cmd.payload?.images) ? cmd.payload.images.filter((u) => typeof u === 'string' && u) : []
|
|
962
977
|
const claudeSessionId = cmd.payload?.claudeSessionId || null
|
|
963
978
|
const allowCommit = cmd.payload?.allowCommit === true
|
|
979
|
+
const model = aiModelArg(cmd.payload?.model)
|
|
964
980
|
// A turn may be text-only, image-only, or both — but needs at least one.
|
|
965
981
|
if (!sessionId || (!prompt && images.length === 0)) {
|
|
966
982
|
reportCommandResult(cfg, cmd.id, 'error', 'invalid ai_chat payload')
|
|
@@ -988,6 +1004,9 @@ async function runAiChat(cfg, cmd, repoPath) {
|
|
|
988
1004
|
// Stream the reply token-by-token so the console can show it being written
|
|
989
1005
|
// live, not only once each block is complete (gd-302).
|
|
990
1006
|
'--include-partial-messages',
|
|
1007
|
+
// Model resolved for this session (picker / project default); omitted →
|
|
1008
|
+
// machine default (gd-354). Kept consistent across the session's turns.
|
|
1009
|
+
...(model ? ['--model', model] : []),
|
|
991
1010
|
'--permission-mode', 'acceptEdits',
|
|
992
1011
|
'--allowedTools', 'Read,Edit,Write,Bash,mcp__gitdone__*',
|
|
993
1012
|
// Block git commit/push unless this repo opted in (per-repo aiAutoCommit).
|
|
@@ -1241,13 +1260,60 @@ async function executeCommand(cfg, cmd, repoPath) {
|
|
|
1241
1260
|
}
|
|
1242
1261
|
|
|
1243
1262
|
// Report discovered repos + machine info, get back which repos to track.
|
|
1263
|
+
// ─── Claude plan usage (gd-355) ────────────────────────────────────────────
|
|
1264
|
+
// Claude Code persists an OAuth token at ~/.claude/.credentials.json and keeps
|
|
1265
|
+
// it fresh. We reuse it to ask Anthropic for the SAME plan-usage numbers the
|
|
1266
|
+
// /usage command shows (account-level: a 5-hour "session" window + a weekly
|
|
1267
|
+
// limit). The АИ Конзола then renders these live. Best-effort: on a missing or
|
|
1268
|
+
// expired token, being offline, or any error we return null and the console
|
|
1269
|
+
// simply keeps the last snapshot. Cached ~60s so the 30s sync loop doesn't hit
|
|
1270
|
+
// the endpoint twice as often as it changes.
|
|
1271
|
+
let usageCache = { at: 0, data: null }
|
|
1272
|
+
async function readClaudeUsage() {
|
|
1273
|
+
const now = Date.now()
|
|
1274
|
+
if (usageCache.data && now - usageCache.at < 60_000) return usageCache.data
|
|
1275
|
+
try {
|
|
1276
|
+
const creds = JSON.parse(readFileSync(join(homedir(), '.claude', '.credentials.json'), 'utf8'))
|
|
1277
|
+
const token = creds?.claudeAiOauth?.accessToken
|
|
1278
|
+
if (!token) return null
|
|
1279
|
+
const ctrl = new AbortController()
|
|
1280
|
+
const to = setTimeout(() => ctrl.abort(), 8000)
|
|
1281
|
+
let res
|
|
1282
|
+
try {
|
|
1283
|
+
res = await fetch('https://api.anthropic.com/api/oauth/usage', {
|
|
1284
|
+
headers: {
|
|
1285
|
+
Authorization: `Bearer ${token}`,
|
|
1286
|
+
'Content-Type': 'application/json',
|
|
1287
|
+
'anthropic-beta': 'oauth-2025-04-20',
|
|
1288
|
+
'User-Agent': 'claude-cli',
|
|
1289
|
+
},
|
|
1290
|
+
signal: ctrl.signal,
|
|
1291
|
+
})
|
|
1292
|
+
} finally { clearTimeout(to) }
|
|
1293
|
+
if (!res.ok) return null // 401 = token expired; Claude Code refreshes it — skip this tick
|
|
1294
|
+
const j = await res.json()
|
|
1295
|
+
const data = {
|
|
1296
|
+
sessionPct: Math.round(j?.five_hour?.utilization ?? 0),
|
|
1297
|
+
sessionResetsAt: j?.five_hour?.resets_at ?? null,
|
|
1298
|
+
weeklyPct: Math.round(j?.seven_day?.utilization ?? 0),
|
|
1299
|
+
weeklyResetsAt: j?.seven_day?.resets_at ?? null,
|
|
1300
|
+
}
|
|
1301
|
+
usageCache = { at: now, data }
|
|
1302
|
+
return data
|
|
1303
|
+
} catch {
|
|
1304
|
+
return null // no creds file / malformed / offline — stay quiet
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1244
1308
|
async function sync(cfg, discovered) {
|
|
1309
|
+
const usage = await readClaudeUsage()
|
|
1245
1310
|
const data = await api(cfg, '/api/v1/agent/sync', {
|
|
1246
1311
|
machineId: cfg.machineId,
|
|
1247
1312
|
hostname: cfg.hostname,
|
|
1248
1313
|
agentVersion: AGENT_VERSION,
|
|
1249
1314
|
roots: cfg.roots,
|
|
1250
1315
|
repos: discovered,
|
|
1316
|
+
...(usage ? { usage } : {}),
|
|
1251
1317
|
})
|
|
1252
1318
|
return data.tracked ?? []
|
|
1253
1319
|
}
|