@virsanghavi/axis-server 1.1.1 → 1.2.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/dist/mcp-server.mjs +78 -2
- package/package.json +1 -1
package/dist/mcp-server.mjs
CHANGED
|
@@ -827,6 +827,7 @@ var NerveCenter = class {
|
|
|
827
827
|
delete this.state.locks[filePath];
|
|
828
828
|
await this.saveState();
|
|
829
829
|
}
|
|
830
|
+
this.logLockEvent("FORCE_UNLOCKED", filePath, "admin", void 0, reason);
|
|
830
831
|
await this.appendToNotepad(`
|
|
831
832
|
- [FORCE UNLOCK] ${filePath} unlocked by admin. Reason: ${reason}`);
|
|
832
833
|
return `File ${filePath} has been forcibly unlocked.`;
|
|
@@ -849,6 +850,31 @@ ${lockSummary || "No active locks."}
|
|
|
849
850
|
## Live Notepad
|
|
850
851
|
${notepad}`;
|
|
851
852
|
}
|
|
853
|
+
// --- Lock Event Logging ---
|
|
854
|
+
async logLockEvent(eventType, filePath, requestingAgent, blockingAgent, intent) {
|
|
855
|
+
try {
|
|
856
|
+
if (this.contextManager.apiUrl) {
|
|
857
|
+
await this.callCoordination("lock-events", "POST", {
|
|
858
|
+
eventType,
|
|
859
|
+
filePath,
|
|
860
|
+
requestingAgent,
|
|
861
|
+
blockingAgent: blockingAgent || null,
|
|
862
|
+
intent: intent || null
|
|
863
|
+
});
|
|
864
|
+
} else if (this.useSupabase && this.supabase && this._projectId) {
|
|
865
|
+
await this.supabase.from("lock_events").insert({
|
|
866
|
+
project_id: this._projectId,
|
|
867
|
+
event_type: eventType,
|
|
868
|
+
file_path: filePath,
|
|
869
|
+
requesting_agent: requestingAgent,
|
|
870
|
+
blocking_agent: blockingAgent || null,
|
|
871
|
+
intent: intent || null
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
} catch (e) {
|
|
875
|
+
logger.warn(`[logLockEvent] Failed to log ${eventType} event: ${e.message}`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
852
878
|
// --- Decision & Orchestration ---
|
|
853
879
|
async proposeFileAccess(agentId, filePath, intent, userPrompt) {
|
|
854
880
|
return await this.mutex.runExclusive(async () => {
|
|
@@ -864,6 +890,7 @@ ${notepad}`;
|
|
|
864
890
|
});
|
|
865
891
|
if (result.status === "DENIED") {
|
|
866
892
|
logger.info(`[proposeFileAccess] DENIED by server: ${result.message}`);
|
|
893
|
+
this.logLockEvent("BLOCKED", filePath, agentId, result.current_lock?.agent_id, intent);
|
|
867
894
|
return {
|
|
868
895
|
status: "REQUIRES_ORCHESTRATION",
|
|
869
896
|
message: result.message || `File '${filePath}' is locked by another agent`,
|
|
@@ -871,6 +898,7 @@ ${notepad}`;
|
|
|
871
898
|
};
|
|
872
899
|
}
|
|
873
900
|
logger.info(`[proposeFileAccess] GRANTED by server`);
|
|
901
|
+
this.logLockEvent("GRANTED", filePath, agentId, void 0, intent);
|
|
874
902
|
await this.appendToNotepad(`
|
|
875
903
|
- [LOCK] ${agentId} locked ${filePath}
|
|
876
904
|
Intent: ${intent}`);
|
|
@@ -900,6 +928,7 @@ ${notepad}`;
|
|
|
900
928
|
if (error) throw error;
|
|
901
929
|
const row = Array.isArray(data) ? data[0] : data;
|
|
902
930
|
if (row && row.status === "DENIED") {
|
|
931
|
+
this.logLockEvent("BLOCKED", filePath, agentId, row.owner_id, intent);
|
|
903
932
|
return {
|
|
904
933
|
status: "REQUIRES_ORCHESTRATION",
|
|
905
934
|
message: `Conflict: File '${filePath}' is locked by '${row.owner_id}'`,
|
|
@@ -911,6 +940,7 @@ ${notepad}`;
|
|
|
911
940
|
}
|
|
912
941
|
};
|
|
913
942
|
}
|
|
943
|
+
this.logLockEvent("GRANTED", filePath, agentId, void 0, intent);
|
|
914
944
|
await this.appendToNotepad(`
|
|
915
945
|
- [LOCK] ${agentId} locked ${filePath}
|
|
916
946
|
Intent: ${intent}`);
|
|
@@ -923,6 +953,7 @@ ${notepad}`;
|
|
|
923
953
|
if (existing) {
|
|
924
954
|
const isStale = Date.now() - existing.timestamp > this.lockTimeout;
|
|
925
955
|
if (!isStale && existing.agentId !== agentId) {
|
|
956
|
+
this.logLockEvent("BLOCKED", filePath, agentId, existing.agentId, intent);
|
|
926
957
|
return {
|
|
927
958
|
status: "REQUIRES_ORCHESTRATION",
|
|
928
959
|
message: `Conflict: File '${filePath}' is currently locked by '${existing.agentId}'`,
|
|
@@ -932,6 +963,7 @@ ${notepad}`;
|
|
|
932
963
|
}
|
|
933
964
|
this.state.locks[filePath] = { agentId, filePath, intent, userPrompt, timestamp: Date.now() };
|
|
934
965
|
await this.saveState();
|
|
966
|
+
this.logLockEvent("GRANTED", filePath, agentId, void 0, intent);
|
|
935
967
|
await this.appendToNotepad(`
|
|
936
968
|
- [LOCK] ${agentId} locked ${filePath}
|
|
937
969
|
Intent: ${intent}`);
|
|
@@ -1226,8 +1258,52 @@ async function ensureFileSystem() {
|
|
|
1226
1258
|
await fs4.mkdir(axisInstructions, { recursive: true }).catch(() => {
|
|
1227
1259
|
});
|
|
1228
1260
|
const defaults = [
|
|
1229
|
-
["context.md",
|
|
1230
|
-
|
|
1261
|
+
["context.md", `# Project Context
|
|
1262
|
+
|
|
1263
|
+
## Overview
|
|
1264
|
+
This project uses Axis \u2014 an open-source coordination layer for AI agents.
|
|
1265
|
+
Axis provides shared context, atomic file locks, a job board, and real-time
|
|
1266
|
+
activity feeds so that multiple agents (Cursor, Claude, Windsurf, Codex, etc.)
|
|
1267
|
+
can work on the same codebase without conflicts.
|
|
1268
|
+
|
|
1269
|
+
## Architecture
|
|
1270
|
+
- **MCP Server**: Exposes tools (locks, jobs, context, search) via the Model Context Protocol.
|
|
1271
|
+
- **Supabase Backend**: Postgres for state (locks, jobs, profiles); Realtime for live feeds.
|
|
1272
|
+
- **Frontend**: Next.js App Router + Tailwind CSS dashboard at useaxis.dev.
|
|
1273
|
+
- **npm Packages**: @virsanghavi/axis-server (runtime), @virsanghavi/axis-init (scaffolding).
|
|
1274
|
+
|
|
1275
|
+
## Core Features
|
|
1276
|
+
1. File Locking \u2014 atomic, cross-IDE locks with 30-min TTL.
|
|
1277
|
+
2. Job Board \u2014 post / claim / complete tasks with priorities and dependencies.
|
|
1278
|
+
3. Shared Context \u2014 live notepad visible to every agent in real time.
|
|
1279
|
+
4. RAG Search \u2014 vector search over the indexed codebase.
|
|
1280
|
+
5. Soul Files \u2014 context.md, conventions.md, activity.md define project identity.
|
|
1281
|
+
`],
|
|
1282
|
+
["conventions.md", `# Coding Conventions
|
|
1283
|
+
|
|
1284
|
+
## Language & Style
|
|
1285
|
+
- TypeScript everywhere (strict mode).
|
|
1286
|
+
- Tailwind CSS for styling; no raw CSS unless unavoidable.
|
|
1287
|
+
- Functional React components; prefer server components in Next.js App Router.
|
|
1288
|
+
|
|
1289
|
+
## Agent Behavioral Norms
|
|
1290
|
+
|
|
1291
|
+
### Plan Before Write
|
|
1292
|
+
Every non-trivial task must follow: post_job -> claim_next_job -> propose_file_access -> (edit) -> complete_job.
|
|
1293
|
+
Skip only for single-line typo fixes.
|
|
1294
|
+
|
|
1295
|
+
### Force-Unlock Policy
|
|
1296
|
+
force_unlock is a LAST RESORT. Before using it:
|
|
1297
|
+
1. Verify the lock is > 25 minutes old.
|
|
1298
|
+
2. Confirm the locking agent is unresponsive.
|
|
1299
|
+
3. Provide a specific reason string.
|
|
1300
|
+
Never casually unlock files \u2014 always try propose_file_access first.
|
|
1301
|
+
|
|
1302
|
+
### Proactive Tool Usage
|
|
1303
|
+
Agents must use Axis MCP tools by default \u2014 do not wait for the user to say "use Axis".
|
|
1304
|
+
On session start, call get_project_soul or read_context to load project state.
|
|
1305
|
+
After significant progress, call update_shared_context.
|
|
1306
|
+
`],
|
|
1231
1307
|
["activity.md", "# Activity Log\n\n"]
|
|
1232
1308
|
];
|
|
1233
1309
|
for (const [file, content] of defaults) {
|