@stackmemoryai/stackmemory 1.10.5 → 1.12.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/README.md +104 -23
- package/dist/src/cli/claude-sm.js +266 -84
- package/dist/src/cli/codex-sm.js +185 -33
- package/dist/src/cli/commands/bench.js +209 -2
- package/dist/src/cli/commands/cache.js +126 -0
- package/dist/src/cli/commands/daemon.js +41 -0
- package/dist/src/cli/commands/handoff.js +40 -9
- package/dist/src/cli/commands/onboard.js +70 -3
- package/dist/src/cli/commands/optimize.js +117 -0
- package/dist/src/cli/commands/orchestrate.js +230 -5
- package/dist/src/cli/commands/orchestrator.js +312 -24
- package/dist/src/cli/commands/pack.js +322 -0
- package/dist/src/cli/commands/search.js +40 -1
- package/dist/src/cli/commands/setup.js +177 -7
- package/dist/src/cli/commands/skills.js +10 -1
- package/dist/src/cli/commands/state.js +265 -0
- package/dist/src/cli/gemini-sm.js +19 -29
- package/dist/src/cli/index.js +90 -29
- package/dist/src/cli/opencode-sm.js +38 -21
- package/dist/src/cli/utils/determinism-watcher.js +66 -0
- package/dist/src/cli/utils/real-cli-bin.js +44 -0
- package/dist/src/core/cache/content-cache.js +238 -0
- package/dist/src/core/cache/index.js +11 -0
- package/dist/src/core/cache/token-estimator.js +16 -0
- package/dist/src/core/context/frame-database.js +38 -30
- package/dist/src/core/cross-search/cross-project-search.js +269 -0
- package/dist/src/core/{merge → cross-search}/index.js +6 -4
- package/dist/src/core/database/sqlite-adapter.js +0 -83
- package/dist/src/core/extensions/provider-adapter.js +5 -0
- package/dist/src/core/models/model-router.js +22 -2
- package/dist/src/core/monitoring/logger.js +2 -1
- package/dist/src/core/optimization/trace-optimizer.js +413 -0
- package/dist/src/core/provenance/confidence-scorer.js +128 -0
- package/dist/src/core/provenance/index.js +40 -0
- package/dist/src/core/provenance/provenance-store.js +194 -0
- package/dist/src/core/provenance/types.js +82 -0
- package/dist/src/core/session/project-handoff.js +64 -0
- package/dist/src/core/session/session-manager.js +28 -0
- package/dist/src/core/shared-state/canonical-store.js +564 -0
- package/dist/src/core/skill-packs/index.js +18 -0
- package/dist/src/core/skill-packs/parser.js +42 -0
- package/dist/src/core/skill-packs/registry.js +224 -0
- package/dist/src/core/skill-packs/types.js +66 -0
- package/dist/src/core/trace/trace-event-store.js +282 -0
- package/dist/src/core/trace/trace-event.js +4 -0
- package/dist/src/daemon/daemon-config.js +7 -0
- package/dist/src/daemon/services/github-service.js +126 -0
- package/dist/src/daemon/unified-daemon.js +30 -0
- package/dist/src/features/sweep/pty-wrapper.js +13 -5
- package/dist/src/hooks/schemas.js +2 -0
- package/dist/src/integrations/claude-code/subagent-client.js +89 -0
- package/dist/src/integrations/github/pr-state.js +158 -0
- package/dist/src/integrations/linear/client.js +4 -1
- package/dist/src/integrations/mcp/handlers/cross-search-handlers.js +188 -0
- package/dist/src/integrations/mcp/handlers/index.js +40 -59
- package/dist/src/integrations/mcp/server.js +425 -311
- package/dist/src/integrations/mcp/tool-alias-registry.js +370 -0
- package/dist/src/integrations/mcp/tool-definitions.js +98 -229
- package/dist/src/integrations/ralph/context/stackmemory-context-loader.js +3 -40
- package/dist/src/integrations/ralph/learning/pattern-learner.js +1 -20
- package/dist/src/integrations/ralph/swarm/swarm-coordinator.js +0 -2
- package/dist/src/mcp/stackmemory-mcp-server.js +315 -0
- package/dist/src/orchestrators/multimodal/determinism.js +243 -0
- package/dist/src/orchestrators/multimodal/harness.js +147 -77
- package/dist/src/orchestrators/multimodal/providers.js +44 -3
- package/dist/src/utils/hook-installer.js +0 -8
- package/package.json +10 -1
- package/packs/coding/python-fastapi/instructions.md +60 -0
- package/packs/coding/python-fastapi/pack.yaml +28 -0
- package/packs/coding/typescript-react/instructions.md +47 -0
- package/packs/coding/typescript-react/pack.yaml +28 -0
- package/packs/core/commands/capture.md +32 -0
- package/packs/core/commands/learn.md +73 -0
- package/packs/core/commands/next.md +36 -0
- package/packs/core/commands/restart.md +58 -0
- package/packs/core/commands/restore.md +29 -0
- package/packs/core/commands/start.md +57 -0
- package/packs/core/commands/stop.md +65 -0
- package/packs/core/commands/summary.md +40 -0
- package/packs/core/manifest.json +24 -0
- package/packs/ops/decision-recovery/instructions.md +65 -0
- package/packs/ops/decision-recovery/pack.yaml +89 -0
- package/dist/src/cli/commands/team.js +0 -168
- package/dist/src/core/context/shared-context-layer.js +0 -620
- package/dist/src/core/context/stack-merge-resolver.js +0 -748
- package/dist/src/core/merge/conflict-detector.js +0 -430
- package/dist/src/core/merge/resolution-engine.js +0 -557
- package/dist/src/core/merge/stack-diff.js +0 -531
- package/dist/src/core/merge/unified-merge-resolver.js +0 -302
- package/dist/src/integrations/mcp/handlers/cord-handlers.js +0 -397
- package/dist/src/integrations/mcp/handlers/team-handlers.js +0 -211
- /package/dist/src/core/{merge → cache}/types.js +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { refreshCurrentRepoPullRequestState } from "../../integrations/github/pr-state.js";
|
|
9
|
+
import { canonicalStateStore } from "../../core/shared-state/canonical-store.js";
|
|
10
|
+
class DaemonGitHubService {
|
|
11
|
+
config;
|
|
12
|
+
state;
|
|
13
|
+
intervalId;
|
|
14
|
+
isRunning = false;
|
|
15
|
+
onLog;
|
|
16
|
+
constructor(config, onLog) {
|
|
17
|
+
this.config = config;
|
|
18
|
+
this.onLog = onLog;
|
|
19
|
+
this.state = {
|
|
20
|
+
lastSyncTime: 0,
|
|
21
|
+
syncCount: 0,
|
|
22
|
+
errors: []
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
async start() {
|
|
26
|
+
if (this.isRunning || !this.config.enabled) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!this.isGitHubConfigured()) {
|
|
30
|
+
this.onLog("WARN", "GitHub CLI not configured, skipping github service");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.isRunning = true;
|
|
34
|
+
const intervalMs = this.config.interval * 60 * 1e3;
|
|
35
|
+
this.onLog("INFO", "GitHub service started", {
|
|
36
|
+
interval: this.config.interval
|
|
37
|
+
});
|
|
38
|
+
await this.performSync();
|
|
39
|
+
this.intervalId = setInterval(async () => {
|
|
40
|
+
await this.performSync();
|
|
41
|
+
}, intervalMs);
|
|
42
|
+
}
|
|
43
|
+
stop() {
|
|
44
|
+
if (this.intervalId) {
|
|
45
|
+
clearInterval(this.intervalId);
|
|
46
|
+
this.intervalId = void 0;
|
|
47
|
+
}
|
|
48
|
+
this.isRunning = false;
|
|
49
|
+
this.onLog("INFO", "GitHub service stopped");
|
|
50
|
+
}
|
|
51
|
+
getState() {
|
|
52
|
+
return {
|
|
53
|
+
...this.state,
|
|
54
|
+
nextSyncTime: this.isRunning ? this.state.lastSyncTime + this.config.interval * 60 * 1e3 : void 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
async forceSync() {
|
|
58
|
+
await this.performSync();
|
|
59
|
+
}
|
|
60
|
+
async performSync() {
|
|
61
|
+
if (!this.isRunning) return;
|
|
62
|
+
try {
|
|
63
|
+
const projectRoots = await this.getProjectRoots();
|
|
64
|
+
this.state.lastProjectsScanned = projectRoots.length;
|
|
65
|
+
if (projectRoots.length === 0) {
|
|
66
|
+
this.onLog("DEBUG", "No active project roots found for GitHub sync");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
let synced = false;
|
|
70
|
+
for (const projectRoot of projectRoots) {
|
|
71
|
+
const projection = await refreshCurrentRepoPullRequestState(projectRoot);
|
|
72
|
+
if (!projection) {
|
|
73
|
+
this.onLog("DEBUG", "No GitHub PR projection available", {
|
|
74
|
+
projectRoot
|
|
75
|
+
});
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
synced = true;
|
|
79
|
+
this.state.syncCount++;
|
|
80
|
+
this.state.lastSyncTime = Date.now();
|
|
81
|
+
this.state.lastProjectionState = projection.state;
|
|
82
|
+
this.onLog("INFO", "GitHub PR projection refreshed", {
|
|
83
|
+
projectRoot,
|
|
84
|
+
repo: projection.repo,
|
|
85
|
+
branch: projection.branch,
|
|
86
|
+
prNumber: projection.prNumber,
|
|
87
|
+
state: projection.state
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (!synced) {
|
|
91
|
+
this.state.lastSyncTime = Date.now();
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
95
|
+
this.state.errors.push(errorMsg);
|
|
96
|
+
this.onLog("ERROR", "GitHub sync failed", { error: errorMsg });
|
|
97
|
+
if (this.state.errors.length > 10) {
|
|
98
|
+
this.state.errors = this.state.errors.slice(-10);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
isGitHubConfigured() {
|
|
103
|
+
try {
|
|
104
|
+
return existsSync(join(homedir(), ".config", "gh", "hosts.yml"));
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async getProjectRoots() {
|
|
110
|
+
const roots = /* @__PURE__ */ new Set();
|
|
111
|
+
const activeProjectPaths = await canonicalStateStore.listActiveProjectPaths();
|
|
112
|
+
for (const projectPath of activeProjectPaths) {
|
|
113
|
+
if (existsSync(join(projectPath, ".git"))) {
|
|
114
|
+
roots.add(projectPath);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const cwd = process.cwd();
|
|
118
|
+
if (existsSync(join(cwd, ".git"))) {
|
|
119
|
+
roots.add(cwd);
|
|
120
|
+
}
|
|
121
|
+
return Array.from(roots).sort();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
DaemonGitHubService
|
|
126
|
+
};
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
} from "./daemon-config.js";
|
|
19
19
|
import { DaemonContextService } from "./services/context-service.js";
|
|
20
20
|
import { DaemonLinearService } from "./services/linear-service.js";
|
|
21
|
+
import { DaemonGitHubService } from "./services/github-service.js";
|
|
21
22
|
import { DaemonMaintenanceService } from "./services/maintenance-service.js";
|
|
22
23
|
import { DaemonMemoryService } from "./services/memory-service.js";
|
|
23
24
|
class UnifiedDaemon {
|
|
@@ -25,6 +26,7 @@ class UnifiedDaemon {
|
|
|
25
26
|
paths;
|
|
26
27
|
contextService;
|
|
27
28
|
linearService;
|
|
29
|
+
githubService;
|
|
28
30
|
maintenanceService;
|
|
29
31
|
memoryService;
|
|
30
32
|
heartbeatInterval;
|
|
@@ -41,6 +43,10 @@ class UnifiedDaemon {
|
|
|
41
43
|
this.config.linear,
|
|
42
44
|
(level, msg, data) => this.log(level, "linear", msg, data)
|
|
43
45
|
);
|
|
46
|
+
this.githubService = new DaemonGitHubService(
|
|
47
|
+
this.config.github,
|
|
48
|
+
(level, msg, data) => this.log(level, "github", msg, data)
|
|
49
|
+
);
|
|
44
50
|
this.maintenanceService = new DaemonMaintenanceService(
|
|
45
51
|
this.config.maintenance,
|
|
46
52
|
(level, msg, data) => this.log(level, "maintenance", msg, data)
|
|
@@ -101,6 +107,7 @@ class UnifiedDaemon {
|
|
|
101
107
|
updateStatus() {
|
|
102
108
|
const maintenanceState = this.maintenanceService.getState();
|
|
103
109
|
const memoryState = this.memoryService.getState();
|
|
110
|
+
const githubState = this.githubService.getState();
|
|
104
111
|
const status = {
|
|
105
112
|
running: true,
|
|
106
113
|
pid: process.pid,
|
|
@@ -117,6 +124,12 @@ class UnifiedDaemon {
|
|
|
117
124
|
lastRun: this.linearService.getState().lastSyncTime || void 0,
|
|
118
125
|
syncCount: this.linearService.getState().syncCount
|
|
119
126
|
},
|
|
127
|
+
github: {
|
|
128
|
+
enabled: this.config.github.enabled,
|
|
129
|
+
lastRun: githubState.lastSyncTime || void 0,
|
|
130
|
+
syncCount: githubState.syncCount,
|
|
131
|
+
lastProjectionState: githubState.lastProjectionState
|
|
132
|
+
},
|
|
120
133
|
maintenance: {
|
|
121
134
|
enabled: this.config.maintenance.enabled,
|
|
122
135
|
lastRun: maintenanceState.lastRunTime || void 0,
|
|
@@ -139,6 +152,7 @@ class UnifiedDaemon {
|
|
|
139
152
|
errors: [
|
|
140
153
|
...this.contextService.getState().errors.slice(-5),
|
|
141
154
|
...this.linearService.getState().errors.slice(-5),
|
|
155
|
+
...githubState.errors.slice(-5),
|
|
142
156
|
...maintenanceState.errors.slice(-5),
|
|
143
157
|
...memoryState.errors.slice(-5)
|
|
144
158
|
]
|
|
@@ -190,6 +204,11 @@ class UnifiedDaemon {
|
|
|
190
204
|
enabled: false,
|
|
191
205
|
syncCount: this.linearService.getState().syncCount
|
|
192
206
|
},
|
|
207
|
+
github: {
|
|
208
|
+
enabled: false,
|
|
209
|
+
syncCount: this.githubService.getState().syncCount,
|
|
210
|
+
lastProjectionState: this.githubService.getState().lastProjectionState
|
|
211
|
+
},
|
|
193
212
|
maintenance: {
|
|
194
213
|
enabled: false,
|
|
195
214
|
staleFramesCleaned: this.maintenanceService.getState().staleFramesCleaned,
|
|
@@ -213,6 +232,7 @@ class UnifiedDaemon {
|
|
|
213
232
|
uptime: Date.now() - this.startTime,
|
|
214
233
|
contextSaves: this.contextService.getState().saveCount,
|
|
215
234
|
linearSyncs: this.linearService.getState().syncCount,
|
|
235
|
+
githubSyncs: this.githubService.getState().syncCount,
|
|
216
236
|
maintenanceRuns: this.maintenanceService.getState().ftsRebuilds,
|
|
217
237
|
memoryTriggers: this.memoryService.getState().triggerCount
|
|
218
238
|
});
|
|
@@ -222,6 +242,7 @@ class UnifiedDaemon {
|
|
|
222
242
|
}
|
|
223
243
|
this.contextService.stop();
|
|
224
244
|
this.linearService.stop();
|
|
245
|
+
this.githubService.stop();
|
|
225
246
|
this.maintenanceService.stop();
|
|
226
247
|
this.memoryService.stop();
|
|
227
248
|
this.cleanup();
|
|
@@ -241,6 +262,7 @@ class UnifiedDaemon {
|
|
|
241
262
|
config: {
|
|
242
263
|
context: this.config.context.enabled,
|
|
243
264
|
linear: this.config.linear.enabled,
|
|
265
|
+
github: this.config.github.enabled,
|
|
244
266
|
maintenance: this.config.maintenance.enabled,
|
|
245
267
|
memory: this.config.memory.enabled,
|
|
246
268
|
fileWatch: this.config.fileWatch.enabled
|
|
@@ -248,6 +270,7 @@ class UnifiedDaemon {
|
|
|
248
270
|
});
|
|
249
271
|
this.contextService.start();
|
|
250
272
|
await this.linearService.start();
|
|
273
|
+
await this.githubService.start();
|
|
251
274
|
this.maintenanceService.start();
|
|
252
275
|
this.memoryService.start();
|
|
253
276
|
this.heartbeatInterval = setInterval(() => {
|
|
@@ -258,6 +281,7 @@ class UnifiedDaemon {
|
|
|
258
281
|
getStatus() {
|
|
259
282
|
const maintenanceState = this.maintenanceService.getState();
|
|
260
283
|
const memoryState = this.memoryService.getState();
|
|
284
|
+
const githubState = this.githubService.getState();
|
|
261
285
|
return {
|
|
262
286
|
running: !this.isShuttingDown,
|
|
263
287
|
pid: process.pid,
|
|
@@ -274,6 +298,12 @@ class UnifiedDaemon {
|
|
|
274
298
|
lastRun: this.linearService.getState().lastSyncTime || void 0,
|
|
275
299
|
syncCount: this.linearService.getState().syncCount
|
|
276
300
|
},
|
|
301
|
+
github: {
|
|
302
|
+
enabled: this.config.github.enabled,
|
|
303
|
+
lastRun: githubState.lastSyncTime || void 0,
|
|
304
|
+
syncCount: githubState.syncCount,
|
|
305
|
+
lastProjectionState: githubState.lastProjectionState
|
|
306
|
+
},
|
|
277
307
|
maintenance: {
|
|
278
308
|
enabled: this.config.maintenance.enabled,
|
|
279
309
|
lastRun: maintenanceState.lastRunTime || void 0,
|
|
@@ -30,7 +30,9 @@ class PtyWrapper {
|
|
|
30
30
|
claudeBin: config.claudeBin || this.findClaude(),
|
|
31
31
|
claudeArgs: config.claudeArgs || [],
|
|
32
32
|
stateFile: config.stateFile || getSweepPath("sweep-state.json"),
|
|
33
|
-
initialInput: config.initialInput || ""
|
|
33
|
+
initialInput: config.initialInput || "",
|
|
34
|
+
onExit: config.onExit || (() => void 0),
|
|
35
|
+
onSignal: config.onSignal || (() => void 0)
|
|
34
36
|
};
|
|
35
37
|
this.stateWatcher = new SweepStateWatcher(this.config.stateFile);
|
|
36
38
|
this.statusBar = new StatusBar();
|
|
@@ -115,8 +117,9 @@ class PtyWrapper {
|
|
|
115
117
|
this.ptyProcess?.resize(newCols, newRows - 1);
|
|
116
118
|
this.statusBar.resize(newRows, newCols);
|
|
117
119
|
});
|
|
118
|
-
this.ptyProcess.onExit(({ exitCode }) => {
|
|
120
|
+
this.ptyProcess.onExit(async ({ exitCode }) => {
|
|
119
121
|
this.cleanup();
|
|
122
|
+
await this.config.onExit(exitCode);
|
|
120
123
|
if (process.env["LINEAR_API_KEY"]) {
|
|
121
124
|
try {
|
|
122
125
|
execSync("stackmemory linear sync", {
|
|
@@ -128,12 +131,17 @@ class PtyWrapper {
|
|
|
128
131
|
}
|
|
129
132
|
process.exit(exitCode);
|
|
130
133
|
});
|
|
131
|
-
const onSignal = () => {
|
|
134
|
+
const onSignal = async (signal) => {
|
|
132
135
|
this.cleanup();
|
|
136
|
+
await this.config.onSignal(signal);
|
|
133
137
|
process.exit(0);
|
|
134
138
|
};
|
|
135
|
-
process.on("SIGINT",
|
|
136
|
-
|
|
139
|
+
process.on("SIGINT", () => {
|
|
140
|
+
void onSignal("SIGINT");
|
|
141
|
+
});
|
|
142
|
+
process.on("SIGTERM", () => {
|
|
143
|
+
void onSignal("SIGTERM");
|
|
144
|
+
});
|
|
137
145
|
}
|
|
138
146
|
acceptPrediction() {
|
|
139
147
|
if (!this.currentPrediction || !this.ptyProcess) return;
|
|
@@ -19,6 +19,7 @@ const ModelProviderSchema = z.enum([
|
|
|
19
19
|
"cerebras",
|
|
20
20
|
"deepinfra",
|
|
21
21
|
"openrouter",
|
|
22
|
+
"moonshot",
|
|
22
23
|
"anthropic-batch",
|
|
23
24
|
"custom"
|
|
24
25
|
]);
|
|
@@ -59,6 +60,7 @@ const ModelRouterConfigSchema = z.object({
|
|
|
59
60
|
cerebras: ModelConfigSchema.optional(),
|
|
60
61
|
deepinfra: ModelConfigSchema.optional(),
|
|
61
62
|
openrouter: ModelConfigSchema.optional(),
|
|
63
|
+
moonshot: ModelConfigSchema.optional(),
|
|
62
64
|
"anthropic-batch": ModelConfigSchema.optional(),
|
|
63
65
|
custom: ModelConfigSchema.optional()
|
|
64
66
|
}).optional().default({}),
|
|
@@ -17,6 +17,17 @@ import {
|
|
|
17
17
|
createProvider
|
|
18
18
|
} from "../../core/extensions/provider-adapter.js";
|
|
19
19
|
import { AnthropicBatchClient } from "../anthropic/batch-client.js";
|
|
20
|
+
const QUOTA_ERROR_PATTERNS = [
|
|
21
|
+
/rate.?limit/i,
|
|
22
|
+
/quota.?exceeded/i,
|
|
23
|
+
/too many requests/i,
|
|
24
|
+
/429/,
|
|
25
|
+
/capacity/i,
|
|
26
|
+
/billing/i,
|
|
27
|
+
/usage.?limit/i,
|
|
28
|
+
/plan.?limit/i,
|
|
29
|
+
/max.*requests/i
|
|
30
|
+
];
|
|
20
31
|
class ClaudeCodeSubagentClient {
|
|
21
32
|
tempDir;
|
|
22
33
|
mockMode;
|
|
@@ -108,6 +119,12 @@ class ClaudeCodeSubagentClient {
|
|
|
108
119
|
tokens: result.usage.inputTokens + result.usage.outputTokens
|
|
109
120
|
};
|
|
110
121
|
} catch (error) {
|
|
122
|
+
if (optimal.provider === "anthropic" && this.isQuotaError(error.message)) {
|
|
123
|
+
logger.warn("Anthropic API quota hit, overflowing to Kimi", {
|
|
124
|
+
error: error.message
|
|
125
|
+
});
|
|
126
|
+
return this.executeKimiOverflow(request, startTime, subagentId);
|
|
127
|
+
}
|
|
111
128
|
logger.warn(`Direct API failed for ${optimal.provider}, falling back`, {
|
|
112
129
|
error: error.message
|
|
113
130
|
});
|
|
@@ -176,6 +193,13 @@ Context (JSON): ${JSON.stringify(request.context)}`;
|
|
|
176
193
|
tokens: this.estimateTokens(fullPrompt + result.text)
|
|
177
194
|
};
|
|
178
195
|
} catch (error) {
|
|
196
|
+
if (this.isQuotaError(error.message)) {
|
|
197
|
+
logger.warn("Claude quota/rate limit hit, overflowing to Kimi", {
|
|
198
|
+
subagentId,
|
|
199
|
+
error: error.message
|
|
200
|
+
});
|
|
201
|
+
return this.executeKimiOverflow(request, startTime, subagentId);
|
|
202
|
+
}
|
|
179
203
|
logger.error(`Subagent CLI execution failed: ${request.type}`, {
|
|
180
204
|
error,
|
|
181
205
|
subagentId
|
|
@@ -210,6 +234,71 @@ Context (JSON): ${JSON.stringify(request.context)}`;
|
|
|
210
234
|
}
|
|
211
235
|
});
|
|
212
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Check if an error message indicates quota/rate limit exhaustion
|
|
239
|
+
*/
|
|
240
|
+
isQuotaError(message) {
|
|
241
|
+
return QUOTA_ERROR_PATTERNS.some((pattern) => pattern.test(message));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Execute via Kimi/Moonshot API as overflow when Claude quota is exhausted.
|
|
245
|
+
* Uses OpenAI-compatible API at api.moonshot.ai/v1.
|
|
246
|
+
*/
|
|
247
|
+
async executeKimiOverflow(request, startTime, subagentId) {
|
|
248
|
+
const apiKey = process.env["MOONSHOT_API_KEY"] || "";
|
|
249
|
+
if (!apiKey) {
|
|
250
|
+
logger.warn("No MOONSHOT_API_KEY set, cannot overflow to Kimi");
|
|
251
|
+
return {
|
|
252
|
+
success: false,
|
|
253
|
+
result: null,
|
|
254
|
+
error: "Claude quota exceeded and no MOONSHOT_API_KEY configured",
|
|
255
|
+
duration: Date.now() - startTime,
|
|
256
|
+
subagentType: request.type
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
const adapter = createProvider("moonshot", {
|
|
261
|
+
apiKey,
|
|
262
|
+
baseUrl: "https://api.moonshot.ai/v1"
|
|
263
|
+
});
|
|
264
|
+
const prompt = this.buildSubagentPrompt(request);
|
|
265
|
+
const result = await adapter.complete(
|
|
266
|
+
[{ role: "user", content: prompt }],
|
|
267
|
+
{ model: "kimi-k2.6", maxTokens: 8192 }
|
|
268
|
+
);
|
|
269
|
+
const text = result.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
270
|
+
let parsed;
|
|
271
|
+
try {
|
|
272
|
+
parsed = JSON.parse(text);
|
|
273
|
+
} catch {
|
|
274
|
+
parsed = { rawOutput: text };
|
|
275
|
+
}
|
|
276
|
+
logger.info("Kimi overflow completed", {
|
|
277
|
+
subagentId,
|
|
278
|
+
tokens: result.usage.inputTokens + result.usage.outputTokens
|
|
279
|
+
});
|
|
280
|
+
return {
|
|
281
|
+
success: true,
|
|
282
|
+
result: parsed,
|
|
283
|
+
output: text,
|
|
284
|
+
duration: Date.now() - startTime,
|
|
285
|
+
subagentType: request.type,
|
|
286
|
+
tokens: result.usage.inputTokens + result.usage.outputTokens
|
|
287
|
+
};
|
|
288
|
+
} catch (kimiError) {
|
|
289
|
+
logger.error("Kimi overflow also failed", {
|
|
290
|
+
subagentId,
|
|
291
|
+
error: kimiError.message
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
result: null,
|
|
296
|
+
error: `Claude quota exceeded, Kimi fallback failed: ${kimiError.message}`,
|
|
297
|
+
duration: Date.now() - startTime,
|
|
298
|
+
subagentType: request.type
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
213
302
|
/**
|
|
214
303
|
* Build subagent prompt based on type
|
|
215
304
|
*/
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { fileURLToPath as __fileURLToPath } from 'url';
|
|
2
|
+
import { dirname as __pathDirname } from 'path';
|
|
3
|
+
const __filename = __fileURLToPath(import.meta.url);
|
|
4
|
+
const __dirname = __pathDirname(__filename);
|
|
5
|
+
import { execFileSync } from "child_process";
|
|
6
|
+
import {
|
|
7
|
+
canonicalStateStore
|
|
8
|
+
} from "../../core/shared-state/canonical-store.js";
|
|
9
|
+
import { projectIdFromIdentifier } from "../../core/shared-state/canonical-store.js";
|
|
10
|
+
function runGit(args, cwd) {
|
|
11
|
+
return execFileSync("git", args, {
|
|
12
|
+
cwd,
|
|
13
|
+
encoding: "utf8",
|
|
14
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
15
|
+
}).trim();
|
|
16
|
+
}
|
|
17
|
+
function runGh(args, cwd) {
|
|
18
|
+
return execFileSync("gh", args, {
|
|
19
|
+
cwd,
|
|
20
|
+
encoding: "utf8",
|
|
21
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
22
|
+
}).trim();
|
|
23
|
+
}
|
|
24
|
+
function normalizeRemoteToRepo(remote) {
|
|
25
|
+
const cleaned = remote.replace(/\.git$/, "").trim();
|
|
26
|
+
if (cleaned.startsWith("git@github.com:")) {
|
|
27
|
+
return cleaned.replace("git@github.com:", "");
|
|
28
|
+
}
|
|
29
|
+
if (cleaned.startsWith("https://github.com/")) {
|
|
30
|
+
return cleaned.replace("https://github.com/", "");
|
|
31
|
+
}
|
|
32
|
+
if (cleaned.startsWith("http://github.com/")) {
|
|
33
|
+
return cleaned.replace("http://github.com/", "");
|
|
34
|
+
}
|
|
35
|
+
throw new Error(`Unsupported GitHub remote: ${remote}`);
|
|
36
|
+
}
|
|
37
|
+
function summarizeStatusCheckRollup(rollup) {
|
|
38
|
+
if (!rollup || rollup.length === 0) {
|
|
39
|
+
return void 0;
|
|
40
|
+
}
|
|
41
|
+
const states = rollup.map((item) => item.conclusion || item.status || item.state).filter(Boolean);
|
|
42
|
+
if (states.length === 0) {
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
if (states.every((state) => state === "SUCCESS")) {
|
|
46
|
+
return "SUCCESS";
|
|
47
|
+
}
|
|
48
|
+
if (states.some((state) => state === "FAILURE" || state === "ERROR")) {
|
|
49
|
+
return "FAILURE";
|
|
50
|
+
}
|
|
51
|
+
if (states.some(
|
|
52
|
+
(state) => state === "PENDING" || state === "IN_PROGRESS" || state === "EXPECTED"
|
|
53
|
+
)) {
|
|
54
|
+
return "PENDING";
|
|
55
|
+
}
|
|
56
|
+
return states[0];
|
|
57
|
+
}
|
|
58
|
+
function getCurrentRepoGitHubInfo(cwd = process.cwd()) {
|
|
59
|
+
try {
|
|
60
|
+
const projectPath = runGit(["rev-parse", "--show-toplevel"], cwd);
|
|
61
|
+
const branch = runGit(["rev-parse", "--abbrev-ref", "HEAD"], projectPath);
|
|
62
|
+
const remote = runGit(
|
|
63
|
+
["config", "--get", "remote.origin.url"],
|
|
64
|
+
projectPath
|
|
65
|
+
);
|
|
66
|
+
const repo = normalizeRemoteToRepo(remote);
|
|
67
|
+
return {
|
|
68
|
+
repo,
|
|
69
|
+
branch,
|
|
70
|
+
projectPath,
|
|
71
|
+
projectId: projectIdFromIdentifier(remote)
|
|
72
|
+
};
|
|
73
|
+
} catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function refreshCurrentRepoPullRequestState(cwd = process.cwd()) {
|
|
78
|
+
const info = getCurrentRepoGitHubInfo(cwd);
|
|
79
|
+
if (!info) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const output = runGh(
|
|
84
|
+
[
|
|
85
|
+
"pr",
|
|
86
|
+
"view",
|
|
87
|
+
"--repo",
|
|
88
|
+
info.repo,
|
|
89
|
+
"--json",
|
|
90
|
+
[
|
|
91
|
+
"number",
|
|
92
|
+
"title",
|
|
93
|
+
"state",
|
|
94
|
+
"isDraft",
|
|
95
|
+
"url",
|
|
96
|
+
"baseRefName",
|
|
97
|
+
"headRefName",
|
|
98
|
+
"headRefOid",
|
|
99
|
+
"mergedAt",
|
|
100
|
+
"updatedAt",
|
|
101
|
+
"reviewDecision",
|
|
102
|
+
"statusCheckRollup"
|
|
103
|
+
].join(",")
|
|
104
|
+
],
|
|
105
|
+
info.projectPath
|
|
106
|
+
);
|
|
107
|
+
const parsed = JSON.parse(output);
|
|
108
|
+
const projection = {
|
|
109
|
+
repo: info.repo,
|
|
110
|
+
branch: info.branch,
|
|
111
|
+
projectId: info.projectId,
|
|
112
|
+
projectPath: info.projectPath,
|
|
113
|
+
prNumber: parsed.number,
|
|
114
|
+
title: parsed.title,
|
|
115
|
+
state: parsed.mergedAt && parsed.state === "MERGED" ? "MERGED" : parsed.state,
|
|
116
|
+
isDraft: parsed.isDraft,
|
|
117
|
+
url: parsed.url,
|
|
118
|
+
baseRefName: parsed.baseRefName,
|
|
119
|
+
headRefName: parsed.headRefName,
|
|
120
|
+
headRefOid: parsed.headRefOid,
|
|
121
|
+
mergedAt: parsed.mergedAt || void 0,
|
|
122
|
+
updatedAt: parsed.updatedAt,
|
|
123
|
+
reviewDecision: parsed.reviewDecision || void 0,
|
|
124
|
+
statusCheckRollup: summarizeStatusCheckRollup(parsed.statusCheckRollup),
|
|
125
|
+
lastSyncedAt: Date.now()
|
|
126
|
+
};
|
|
127
|
+
await canonicalStateStore.saveGitHubPullRequest(projection);
|
|
128
|
+
if (projection.state === "MERGED" || projection.state === "CLOSED") {
|
|
129
|
+
await canonicalStateStore.releaseClaims({
|
|
130
|
+
projectId: info.projectId,
|
|
131
|
+
projectPath: info.projectPath,
|
|
132
|
+
branch: info.branch,
|
|
133
|
+
reason: `github_pr_${projection.state.toLowerCase()}`
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
await canonicalStateStore.appendEvent({
|
|
137
|
+
type: "github_pr_refreshed",
|
|
138
|
+
tool: "stackmemory",
|
|
139
|
+
projectId: info.projectId,
|
|
140
|
+
projectPath: info.projectPath,
|
|
141
|
+
branch: info.branch,
|
|
142
|
+
payload: {
|
|
143
|
+
repo: info.repo,
|
|
144
|
+
prNumber: projection.prNumber,
|
|
145
|
+
state: projection.state,
|
|
146
|
+
reviewDecision: projection.reviewDecision,
|
|
147
|
+
statusCheckRollup: projection.statusCheckRollup
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
return projection;
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
export {
|
|
156
|
+
getCurrentRepoGitHubInfo,
|
|
157
|
+
refreshCurrentRepoPullRequestState
|
|
158
|
+
};
|
|
@@ -496,7 +496,10 @@ class LinearClient {
|
|
|
496
496
|
filter: Object.keys(filter).length > 0 ? filter : void 0,
|
|
497
497
|
first: options?.limit || 50
|
|
498
498
|
});
|
|
499
|
-
return result.issues.nodes
|
|
499
|
+
return result.issues.nodes.map((issue) => ({
|
|
500
|
+
...issue,
|
|
501
|
+
labels: Array.isArray(issue.labels) ? issue.labels : issue.labels?.nodes || []
|
|
502
|
+
}));
|
|
500
503
|
}
|
|
501
504
|
/**
|
|
502
505
|
* Assign an issue to a user
|