mcp-coordinator 0.6.0 → 0.7.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 +24 -0
- package/dist/src/agent-activity.d.ts +13 -9
- package/dist/src/agent-activity.js +45 -24
- package/dist/src/agent-registry.d.ts +7 -7
- package/dist/src/agent-registry.js +19 -18
- package/dist/src/announce-workflow.d.ts +1 -0
- package/dist/src/announce-workflow.js +13 -12
- package/dist/src/auth/providers/registry.d.ts +4 -0
- package/dist/src/auth/providers/registry.js +7 -0
- package/dist/src/auth/providers/types.d.ts +11 -0
- package/dist/src/auth/providers/types.js +1 -0
- package/dist/src/auth.d.ts +24 -5
- package/dist/src/auth.js +172 -23
- package/dist/src/conflict-detector.d.ts +1 -0
- package/dist/src/conflict-detector.js +4 -4
- package/dist/src/consultation.d.ts +28 -14
- package/dist/src/consultation.js +101 -68
- package/dist/src/context-provider.d.ts +2 -2
- package/dist/src/context-provider.js +3 -4
- package/dist/src/database.js +203 -4
- package/dist/src/dependency-map.d.ts +25 -4
- package/dist/src/dependency-map.js +49 -11
- package/dist/src/file-tracker.d.ts +5 -4
- package/dist/src/file-tracker.js +16 -14
- package/dist/src/git-cochange-builder.d.ts +11 -2
- package/dist/src/git-cochange-builder.js +15 -7
- package/dist/src/http/handle-health.d.ts +9 -5
- package/dist/src/http/handle-health.js +22 -8
- package/dist/src/http/handle-rest.d.ts +3 -0
- package/dist/src/http/handle-rest.js +86 -57
- package/dist/src/http/utils.d.ts +4 -0
- package/dist/src/http/utils.js +7 -1
- package/dist/src/impact-scorer.d.ts +3 -0
- package/dist/src/impact-scorer.js +65 -51
- package/dist/src/introspection.d.ts +13 -7
- package/dist/src/introspection.js +34 -11
- package/dist/src/metrics.js +2 -1
- package/dist/src/mqtt-bridge.d.ts +3 -2
- package/dist/src/mqtt-bridge.js +33 -23
- package/dist/src/mqtt-broker.d.ts +16 -7
- package/dist/src/mqtt-broker.js +57 -15
- package/dist/src/security/audit.d.ts +11 -0
- package/dist/src/security/audit.js +7 -0
- package/dist/src/security/encryption.d.ts +17 -0
- package/dist/src/security/encryption.js +5 -0
- package/dist/src/serve-http.js +136 -57
- package/dist/src/server-setup.d.ts +12 -2
- package/dist/src/server-setup.js +33 -15
- package/dist/src/sse-emitter.d.ts +7 -4
- package/dist/src/sse-emitter.js +27 -21
- package/dist/src/tools/agents-tools.d.ts +2 -1
- package/dist/src/tools/agents-tools.js +36 -12
- package/dist/src/tools/consultation-tools.d.ts +2 -1
- package/dist/src/tools/consultation-tools.js +106 -40
- package/dist/src/tools/dependencies-tools.d.ts +2 -1
- package/dist/src/tools/dependencies-tools.js +25 -7
- package/dist/src/tools/files-tools.d.ts +2 -1
- package/dist/src/tools/files-tools.js +26 -8
- package/dist/src/tools/mqtt-tools.d.ts +7 -1
- package/dist/src/tools/mqtt-tools.js +27 -4
- package/dist/src/tools/status-tools.d.ts +7 -1
- package/dist/src/tools/status-tools.js +26 -9
- package/dist/src/types.d.ts +2 -0
- package/dist/src/working-files-tracker.d.ts +21 -11
- package/dist/src/working-files-tracker.js +32 -21
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -818,6 +818,30 @@ curl -X POST http://localhost:3100/api/auth/revoke \
|
|
|
818
818
|
|
|
819
819
|
`GET /health`, `POST /api/auth/register`, `POST /api/auth/refresh`, `GET /api/events` (SSE).
|
|
820
820
|
|
|
821
|
+
### Migration to v0.7.0
|
|
822
|
+
|
|
823
|
+
v0.7.0 reworks auth foundation: schema gains `org_id` everywhere, JWTs gain `user_id`/`org` claims, MQTT topics gain an org prefix.
|
|
824
|
+
|
|
825
|
+
**Steps for an existing v0.6.x deployment**:
|
|
826
|
+
|
|
827
|
+
1. Backup `coordinator.db`.
|
|
828
|
+
2. Set `COORDINATOR_JWT_SECRET` explicitly (32+ chars). Without it the coordinator generates a random secret per boot — every restart invalidates all sessions.
|
|
829
|
+
3. Deploy v0.7.0. Migration runs on first boot (~30-60s of locked writes on a large DB while `ALTER TABLE` runs).
|
|
830
|
+
4. Existing clients keep working unchanged under `AUTH_ENABLED=false` (the default).
|
|
831
|
+
5. External MQTT subscribers: update topic patterns from `coordinator/agents/...` to `coordinator/default/agents/...`.
|
|
832
|
+
|
|
833
|
+
**Rolling JWT secrets without downtime**:
|
|
834
|
+
|
|
835
|
+
```bash
|
|
836
|
+
# In prod env config:
|
|
837
|
+
export COORDINATOR_JWT_SECRET=new-secret-here
|
|
838
|
+
export COORDINATOR_JWT_PREV_SECRET=old-secret-here
|
|
839
|
+
# Restart coordinator. Wait for one JWT TTL (24h default).
|
|
840
|
+
# Then remove COORDINATOR_JWT_PREV_SECRET and restart again.
|
|
841
|
+
```
|
|
842
|
+
|
|
843
|
+
**Rolling back to v0.6**: requires restoring from backup. The v0.6 binary refuses to boot a v0.7 DB (PRAGMA user_version guard).
|
|
844
|
+
|
|
821
845
|
---
|
|
822
846
|
|
|
823
847
|
## Test Results
|
|
@@ -10,18 +10,22 @@ interface GetActivityOptions {
|
|
|
10
10
|
export declare class AgentActivityTracker {
|
|
11
11
|
private registry;
|
|
12
12
|
constructor(registry: AgentRegistry);
|
|
13
|
-
/** Report file edit activity
|
|
14
|
-
reportFileActivity(agentId: string, filePath: string): void;
|
|
13
|
+
/** Report file edit activity -> status becomes "working" (org-scoped) */
|
|
14
|
+
reportFileActivity(orgId: string, agentId: string, filePath: string): void;
|
|
15
15
|
/** Report agent is waiting on a consultation thread */
|
|
16
16
|
reportWaiting(agentId: string, threadId: string): void;
|
|
17
|
-
/** Report agent went offline
|
|
18
|
-
reportOffline(agentId: string): void;
|
|
19
|
-
/** Enriched heartbeat
|
|
17
|
+
/** Report agent went offline -> clear all activity (org-scoped) */
|
|
18
|
+
reportOffline(orgId: string, agentId: string): void;
|
|
19
|
+
/** Enriched heartbeat -- derives status from current state */
|
|
20
20
|
heartbeat(agentId: string, payload: HeartbeatPayload): void;
|
|
21
|
-
/** Get
|
|
22
|
-
|
|
23
|
-
/** List
|
|
24
|
-
|
|
21
|
+
/** Get raw status row for a single agent (org-scoped) */
|
|
22
|
+
getStatus(orgId: string, agentId: string): AgentActivity | null;
|
|
23
|
+
/** List all active agents in an org */
|
|
24
|
+
listActive(orgId: string): AgentActivity[];
|
|
25
|
+
/** Get activity for a single agent, with optional idle timeout (org-scoped) */
|
|
26
|
+
getActivity(orgId: string, agentId: string, options?: GetActivityOptions): AgentActivity;
|
|
27
|
+
/** List activity for all online agents in an org */
|
|
28
|
+
listAll(orgId: string, options?: GetActivityOptions): AgentActivity[];
|
|
25
29
|
private upsert;
|
|
26
30
|
}
|
|
27
31
|
export {};
|
|
@@ -4,19 +4,29 @@ export class AgentActivityTracker {
|
|
|
4
4
|
constructor(registry) {
|
|
5
5
|
this.registry = registry;
|
|
6
6
|
}
|
|
7
|
-
/** Report file edit activity
|
|
8
|
-
reportFileActivity(agentId, filePath) {
|
|
9
|
-
|
|
7
|
+
/** Report file edit activity -> status becomes "working" (org-scoped) */
|
|
8
|
+
reportFileActivity(orgId, agentId, filePath) {
|
|
9
|
+
const db = getDb();
|
|
10
|
+
// After Task 5.5, agent_activity_status PK is (org_id, agent_id). Composite conflict target.
|
|
11
|
+
db.prepare(`INSERT INTO agent_activity_status (org_id, agent_id, activity_status, current_file, last_activity_at)
|
|
12
|
+
VALUES (?, ?, 'working', ?, CURRENT_TIMESTAMP)
|
|
13
|
+
ON CONFLICT(org_id, agent_id) DO UPDATE SET
|
|
14
|
+
activity_status = 'working',
|
|
15
|
+
current_file = excluded.current_file,
|
|
16
|
+
current_thread = NULL,
|
|
17
|
+
last_activity_at = CURRENT_TIMESTAMP`).run(orgId, agentId, filePath);
|
|
10
18
|
}
|
|
11
19
|
/** Report agent is waiting on a consultation thread */
|
|
12
20
|
reportWaiting(agentId, threadId) {
|
|
13
|
-
|
|
21
|
+
// TODO(Task 23.5): thread real org_id from MCP session claims; for now MCP uses 'default' (cross-org leak window — single-tenant only)
|
|
22
|
+
this.upsert("default", agentId, "waiting", null, threadId);
|
|
14
23
|
}
|
|
15
|
-
/** Report agent went offline
|
|
16
|
-
reportOffline(agentId) {
|
|
17
|
-
|
|
24
|
+
/** Report agent went offline -> clear all activity (org-scoped) */
|
|
25
|
+
reportOffline(orgId, agentId) {
|
|
26
|
+
const db = getDb();
|
|
27
|
+
db.prepare("UPDATE agent_activity_status SET activity_status = 'offline', current_file = NULL, current_thread = NULL WHERE org_id = ? AND agent_id = ?").run(orgId, agentId);
|
|
18
28
|
}
|
|
19
|
-
/** Enriched heartbeat
|
|
29
|
+
/** Enriched heartbeat -- derives status from current state */
|
|
20
30
|
heartbeat(agentId, payload) {
|
|
21
31
|
let status;
|
|
22
32
|
if (payload.currentFile) {
|
|
@@ -28,20 +38,31 @@ export class AgentActivityTracker {
|
|
|
28
38
|
else {
|
|
29
39
|
status = "idle";
|
|
30
40
|
}
|
|
31
|
-
|
|
41
|
+
// TODO(Task 23.5): thread real org_id from MCP session claims; for now MCP uses 'default' (cross-org leak window — single-tenant only)
|
|
42
|
+
this.upsert("default", agentId, status, payload.currentFile, payload.currentThread);
|
|
43
|
+
}
|
|
44
|
+
/** Get raw status row for a single agent (org-scoped) */
|
|
45
|
+
getStatus(orgId, agentId) {
|
|
46
|
+
const db = getDb();
|
|
47
|
+
return db.prepare("SELECT * FROM agent_activity_status WHERE org_id = ? AND agent_id = ?").get(orgId, agentId);
|
|
48
|
+
}
|
|
49
|
+
/** List all active agents in an org */
|
|
50
|
+
listActive(orgId) {
|
|
51
|
+
const db = getDb();
|
|
52
|
+
return db.prepare("SELECT * FROM agent_activity_status WHERE org_id = ? AND activity_status = 'working' ORDER BY last_activity_at DESC").all(orgId);
|
|
32
53
|
}
|
|
33
|
-
/** Get activity for a single agent, with optional idle timeout */
|
|
34
|
-
getActivity(agentId, options) {
|
|
35
|
-
const agent = this.registry.get(agentId);
|
|
54
|
+
/** Get activity for a single agent, with optional idle timeout (org-scoped) */
|
|
55
|
+
getActivity(orgId, agentId, options) {
|
|
56
|
+
const agent = this.registry.get(orgId, agentId);
|
|
36
57
|
if (!agent || agent.status === "offline") {
|
|
37
58
|
return { agent_id: agentId, activity_status: "offline", current_file: null, current_thread: null, last_activity_at: new Date().toISOString() };
|
|
38
59
|
}
|
|
39
60
|
const db = getDb();
|
|
40
|
-
const row = db.prepare("SELECT * FROM agent_activity_status WHERE agent_id = ?").get(agentId);
|
|
61
|
+
const row = db.prepare("SELECT * FROM agent_activity_status WHERE org_id = ? AND agent_id = ?").get(orgId, agentId);
|
|
41
62
|
if (!row) {
|
|
42
63
|
return { agent_id: agentId, activity_status: "idle", current_file: null, current_thread: null, last_activity_at: new Date().toISOString() };
|
|
43
64
|
}
|
|
44
|
-
// Check idle timeout: if working but no activity for X minutes
|
|
65
|
+
// Check idle timeout: if working but no activity for X minutes -> idle
|
|
45
66
|
if (row.activity_status === "working" && options?.idleAfterMinutes) {
|
|
46
67
|
const lastActivity = new Date(row.last_activity_at.replace(" ", "T") + "Z").getTime();
|
|
47
68
|
const threshold = options.idleAfterMinutes * 60 * 1000;
|
|
@@ -51,20 +72,20 @@ export class AgentActivityTracker {
|
|
|
51
72
|
}
|
|
52
73
|
return row;
|
|
53
74
|
}
|
|
54
|
-
/** List activity for all online agents */
|
|
55
|
-
listAll(options) {
|
|
56
|
-
const onlineAgents = this.registry.listOnline();
|
|
57
|
-
return onlineAgents.map((agent) => this.getActivity(agent.id, options));
|
|
75
|
+
/** List activity for all online agents in an org */
|
|
76
|
+
listAll(orgId, options) {
|
|
77
|
+
const onlineAgents = this.registry.listOnline(orgId);
|
|
78
|
+
return onlineAgents.map((agent) => this.getActivity(orgId, agent.id, options));
|
|
58
79
|
}
|
|
59
|
-
//
|
|
60
|
-
upsert(agentId, status, file, thread) {
|
|
80
|
+
// -- Private --
|
|
81
|
+
upsert(orgId, agentId, status, file, thread) {
|
|
61
82
|
const db = getDb();
|
|
62
|
-
db.prepare(`INSERT INTO agent_activity_status (agent_id, activity_status, current_file, current_thread, last_activity_at)
|
|
63
|
-
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
64
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
83
|
+
db.prepare(`INSERT INTO agent_activity_status (org_id, agent_id, activity_status, current_file, current_thread, last_activity_at)
|
|
84
|
+
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
85
|
+
ON CONFLICT(org_id, agent_id) DO UPDATE SET
|
|
65
86
|
activity_status = excluded.activity_status,
|
|
66
87
|
current_file = excluded.current_file,
|
|
67
88
|
current_thread = excluded.current_thread,
|
|
68
|
-
last_activity_at = CURRENT_TIMESTAMP`).run(agentId, status, file, thread);
|
|
89
|
+
last_activity_at = CURRENT_TIMESTAMP`).run(orgId, agentId, status, file, thread);
|
|
69
90
|
}
|
|
70
91
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Agent } from "./types.js";
|
|
2
2
|
export declare class AgentRegistry {
|
|
3
|
-
register(agentId: string, name: string, modules: string[]): Agent;
|
|
4
|
-
get(agentId: string): Agent | undefined;
|
|
5
|
-
listOnline(): Agent[];
|
|
6
|
-
listAll(): Agent[];
|
|
7
|
-
setOnline(agentId: string): void;
|
|
8
|
-
setOffline(agentId: string): void;
|
|
9
|
-
heartbeat(agentId: string): void;
|
|
3
|
+
register(orgId: string, agentId: string, name: string, modules: string[]): Agent;
|
|
4
|
+
get(orgId: string, agentId: string): Agent | undefined;
|
|
5
|
+
listOnline(orgId: string): Agent[];
|
|
6
|
+
listAll(orgId: string): Agent[];
|
|
7
|
+
setOnline(orgId: string, agentId: string): void;
|
|
8
|
+
setOffline(orgId: string, agentId: string): void;
|
|
9
|
+
heartbeat(orgId: string, agentId: string): void;
|
|
10
10
|
}
|
|
@@ -1,38 +1,39 @@
|
|
|
1
1
|
import { getDb } from "./database.js";
|
|
2
2
|
export class AgentRegistry {
|
|
3
|
-
register(agentId, name, modules) {
|
|
3
|
+
register(orgId, agentId, name, modules) {
|
|
4
4
|
const db = getDb();
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
// After Task 5.5, agents PK is (org_id, id). Conflict target MUST be the composite key.
|
|
6
|
+
db.prepare(`INSERT INTO agents (id, org_id, name, modules, status, registered_at, last_seen_at)
|
|
7
|
+
VALUES (?, ?, ?, ?, 'online', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
|
8
|
+
ON CONFLICT(org_id, id) DO UPDATE SET
|
|
8
9
|
name = excluded.name,
|
|
9
10
|
modules = excluded.modules,
|
|
10
11
|
status = 'online',
|
|
11
|
-
last_seen_at = CURRENT_TIMESTAMP`).run(agentId, name, JSON.stringify(modules));
|
|
12
|
-
return this.get(agentId);
|
|
12
|
+
last_seen_at = CURRENT_TIMESTAMP`).run(agentId, orgId, name, JSON.stringify(modules));
|
|
13
|
+
return this.get(orgId, agentId);
|
|
13
14
|
}
|
|
14
|
-
get(agentId) {
|
|
15
|
+
get(orgId, agentId) {
|
|
15
16
|
const db = getDb();
|
|
16
|
-
return db.prepare("SELECT * FROM agents WHERE id = ?").get(agentId);
|
|
17
|
+
return db.prepare("SELECT * FROM agents WHERE org_id = ? AND id = ?").get(orgId, agentId);
|
|
17
18
|
}
|
|
18
|
-
listOnline() {
|
|
19
|
+
listOnline(orgId) {
|
|
19
20
|
const db = getDb();
|
|
20
|
-
return db.prepare("SELECT * FROM agents WHERE status = 'online' ORDER BY name").all();
|
|
21
|
+
return db.prepare("SELECT * FROM agents WHERE org_id = ? AND status = 'online' ORDER BY name").all(orgId);
|
|
21
22
|
}
|
|
22
|
-
listAll() {
|
|
23
|
+
listAll(orgId) {
|
|
23
24
|
const db = getDb();
|
|
24
|
-
return db.prepare("SELECT * FROM agents ORDER BY last_seen_at DESC").all();
|
|
25
|
+
return db.prepare("SELECT * FROM agents WHERE org_id = ? ORDER BY last_seen_at DESC").all(orgId);
|
|
25
26
|
}
|
|
26
|
-
setOnline(agentId) {
|
|
27
|
+
setOnline(orgId, agentId) {
|
|
27
28
|
const db = getDb();
|
|
28
|
-
db.prepare("UPDATE agents SET status = 'online', last_seen_at = CURRENT_TIMESTAMP WHERE id = ?").run(agentId);
|
|
29
|
+
db.prepare("UPDATE agents SET status = 'online', last_seen_at = CURRENT_TIMESTAMP WHERE org_id = ? AND id = ?").run(orgId, agentId);
|
|
29
30
|
}
|
|
30
|
-
setOffline(agentId) {
|
|
31
|
+
setOffline(orgId, agentId) {
|
|
31
32
|
const db = getDb();
|
|
32
|
-
db.prepare("UPDATE agents SET status = 'offline', last_seen_at = CURRENT_TIMESTAMP WHERE id = ?").run(agentId);
|
|
33
|
+
db.prepare("UPDATE agents SET status = 'offline', last_seen_at = CURRENT_TIMESTAMP WHERE org_id = ? AND id = ?").run(orgId, agentId);
|
|
33
34
|
}
|
|
34
|
-
heartbeat(agentId) {
|
|
35
|
+
heartbeat(orgId, agentId) {
|
|
35
36
|
const db = getDb();
|
|
36
|
-
db.prepare("UPDATE agents SET last_seen_at = CURRENT_TIMESTAMP WHERE id = ?").run(agentId);
|
|
37
|
+
db.prepare("UPDATE agents SET last_seen_at = CURRENT_TIMESTAMP WHERE org_id = ? AND id = ?").run(orgId, agentId);
|
|
37
38
|
}
|
|
38
39
|
}
|
|
@@ -10,6 +10,7 @@ export function runCommonAnnounceFlow(services, threadId, params) {
|
|
|
10
10
|
const { registry, consultation, impactScorer, introspection, sseEmitter } = services;
|
|
11
11
|
// 1. Score impact: categorize all online agents into concerned / gray_zone / pass.
|
|
12
12
|
const categorized = impactScorer.categorize({
|
|
13
|
+
org_id: params.org_id,
|
|
13
14
|
agent_id: params.agent_id,
|
|
14
15
|
target_modules: params.target_modules,
|
|
15
16
|
target_files: params.target_files,
|
|
@@ -31,14 +32,14 @@ export function runCommonAnnounceFlow(services, threadId, params) {
|
|
|
31
32
|
// announce can match via Layer 0. Thread will timeout naturally if no one joins.
|
|
32
33
|
const db = getDb();
|
|
33
34
|
const concernedIds = categorized.concerned.map((s) => s.agent_id);
|
|
34
|
-
db.prepare("UPDATE threads SET expected_respondents = ? WHERE id = ?")
|
|
35
|
-
.run(JSON.stringify(concernedIds), threadId);
|
|
36
|
-
const otherOnlineCount = registry.listOnline().filter((a) => a.id !== params.agent_id).length;
|
|
35
|
+
db.prepare("UPDATE threads SET expected_respondents = ? WHERE id = ? AND org_id = ?")
|
|
36
|
+
.run(JSON.stringify(concernedIds), threadId, params.org_id);
|
|
37
|
+
const otherOnlineCount = registry.listOnline(params.org_id).filter((a) => a.id !== params.agent_id).length;
|
|
37
38
|
const shouldAutoResolve = concernedIds.length === 0 && otherOnlineCount === 0;
|
|
38
|
-
const currentThread = consultation.getThread(threadId);
|
|
39
|
+
const currentThread = consultation.getThread(params.org_id, threadId);
|
|
39
40
|
if (shouldAutoResolve && currentThread.status === "open" && !params.keep_open) {
|
|
40
|
-
db.prepare("UPDATE threads SET status = 'resolved', resolved_at = ? WHERE id = ?")
|
|
41
|
-
.run(new Date().toISOString(), threadId);
|
|
41
|
+
db.prepare("UPDATE threads SET status = 'resolved', resolved_at = ? WHERE id = ? AND org_id = ?")
|
|
42
|
+
.run(new Date().toISOString(), threadId, params.org_id);
|
|
42
43
|
consultation.emitResolution(threadId, "auto_resolved");
|
|
43
44
|
}
|
|
44
45
|
// 3. Emit impact_scored SSE events for every scored agent.
|
|
@@ -50,18 +51,18 @@ export function runCommonAnnounceFlow(services, threadId, params) {
|
|
|
50
51
|
score: s.score,
|
|
51
52
|
reasons: s.reasons,
|
|
52
53
|
category: scoredCategory(s),
|
|
53
|
-
});
|
|
54
|
+
}, { org_id: params.org_id });
|
|
54
55
|
}
|
|
55
56
|
// 4. Create introspection records and emit introspection_requested for gray_zone agents.
|
|
56
57
|
for (const s of categorized.gray_zone) {
|
|
57
|
-
introspection.create({ thread_id: threadId, agent_id: s.agent_id, score: s.score, reasons: s.reasons });
|
|
58
|
+
introspection.create(params.org_id, { thread_id: threadId, agent_id: s.agent_id, score: s.score, reasons: s.reasons });
|
|
58
59
|
sseEmitter.emit("introspection_requested", {
|
|
59
60
|
thread_id: threadId,
|
|
60
61
|
agent_id: s.agent_id,
|
|
61
62
|
agent_name: s.agent_name,
|
|
62
63
|
score: s.score,
|
|
63
64
|
reasons: s.reasons,
|
|
64
|
-
});
|
|
65
|
+
}, { org_id: params.org_id });
|
|
65
66
|
}
|
|
66
67
|
// 5. Plan quality downgrade event — both transports emit this when a plan
|
|
67
68
|
// was provided but quality was insufficient. Callers can re-emit if their
|
|
@@ -71,13 +72,13 @@ export function runCommonAnnounceFlow(services, threadId, params) {
|
|
|
71
72
|
sseEmitter.emit("impact_scored", {
|
|
72
73
|
thread_id: threadId,
|
|
73
74
|
agent_id: params.agent_id,
|
|
74
|
-
agent_name: registry.get(params.agent_id)?.name || params.agent_id,
|
|
75
|
+
agent_name: registry.get(params.org_id, params.agent_id)?.name || params.agent_id,
|
|
75
76
|
score: planQuality.score,
|
|
76
77
|
reasons: [planDowngradeReason(planQuality)],
|
|
77
78
|
category: "plan_quality",
|
|
78
|
-
});
|
|
79
|
+
}, { org_id: params.org_id });
|
|
79
80
|
}
|
|
80
|
-
const updated = consultation.getThread(threadId);
|
|
81
|
+
const updated = consultation.getThread(params.org_id, threadId);
|
|
81
82
|
const respondents = JSON.parse(updated.expected_respondents || "[]");
|
|
82
83
|
return { updated, categorized, respondents, planQuality };
|
|
83
84
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface IdpUserInfo {
|
|
2
|
+
idp_user_id: string;
|
|
3
|
+
email: string;
|
|
4
|
+
name?: string;
|
|
5
|
+
idp_org_id?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface IdPProvider {
|
|
8
|
+
name: string;
|
|
9
|
+
buildAuthUrl(state: string, redirectUri: string, codeChallenge?: string): string;
|
|
10
|
+
exchangeCode(code: string, redirectUri: string, codeVerifier?: string): Promise<IdpUserInfo>;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/src/auth.d.ts
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
import type { IncomingMessage } from "http";
|
|
2
2
|
import { type Logger } from "./logger.js";
|
|
3
3
|
export declare function setAuthLogger(logger: Logger): void;
|
|
4
|
+
export type AuthRole = "agent" | "admin" | "member";
|
|
4
5
|
export interface AuthClaims {
|
|
5
6
|
sub: string;
|
|
6
|
-
|
|
7
|
+
user_id: string;
|
|
8
|
+
org: string;
|
|
9
|
+
role: AuthRole;
|
|
10
|
+
jti: string;
|
|
7
11
|
}
|
|
8
|
-
export
|
|
9
|
-
|
|
12
|
+
export interface CreateTokenOptions {
|
|
13
|
+
user_id?: string;
|
|
14
|
+
org?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface InitAuthOptions {
|
|
17
|
+
prevSecret?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface AuthenticateOptions {
|
|
20
|
+
authEnabled: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function initAuth(secret: string, expiry?: string, options?: InitAuthOptions): void;
|
|
23
|
+
export declare function createToken(agentId: string, role: AuthRole, expiry?: string, options?: CreateTokenOptions): Promise<string>;
|
|
10
24
|
export declare function verifyToken(token: string): Promise<AuthClaims>;
|
|
11
|
-
export declare function
|
|
25
|
+
export declare function verifyTokenStrict(token: string): Promise<{
|
|
26
|
+
claims: AuthClaims;
|
|
27
|
+
wasLegacy: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
export declare function refreshToken(token: string, options: AuthenticateOptions, gracePeriod?: string): Promise<string>;
|
|
12
30
|
export declare function isRevoked(agentId: string): boolean;
|
|
13
31
|
export declare function revokeAgent(agentId: string, revokedBy: string): void;
|
|
14
32
|
export type AuthResult = {
|
|
@@ -18,5 +36,6 @@ export type AuthResult = {
|
|
|
18
36
|
ok: false;
|
|
19
37
|
status: 401 | 403;
|
|
20
38
|
error: string;
|
|
39
|
+
wwwAuthenticate?: string;
|
|
21
40
|
};
|
|
22
|
-
export declare function authenticateRequest(req: IncomingMessage): Promise<AuthResult>;
|
|
41
|
+
export declare function authenticateRequest(req: IncomingMessage, options?: AuthenticateOptions): Promise<AuthResult>;
|