harper-knowledge 0.1.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/LICENSE +21 -0
- package/README.md +276 -0
- package/config.yaml +17 -0
- package/dist/core/embeddings.d.ts +29 -0
- package/dist/core/embeddings.js +199 -0
- package/dist/core/entries.d.ts +85 -0
- package/dist/core/entries.js +235 -0
- package/dist/core/history.d.ts +30 -0
- package/dist/core/history.js +119 -0
- package/dist/core/search.d.ts +23 -0
- package/dist/core/search.js +306 -0
- package/dist/core/tags.d.ts +32 -0
- package/dist/core/tags.js +76 -0
- package/dist/core/triage.d.ts +55 -0
- package/dist/core/triage.js +126 -0
- package/dist/http-utils.d.ts +37 -0
- package/dist/http-utils.js +132 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +76 -0
- package/dist/mcp/server.d.ts +24 -0
- package/dist/mcp/server.js +124 -0
- package/dist/mcp/tools.d.ts +13 -0
- package/dist/mcp/tools.js +497 -0
- package/dist/oauth/authorize.d.ts +27 -0
- package/dist/oauth/authorize.js +438 -0
- package/dist/oauth/github.d.ts +28 -0
- package/dist/oauth/github.js +62 -0
- package/dist/oauth/keys.d.ts +33 -0
- package/dist/oauth/keys.js +100 -0
- package/dist/oauth/metadata.d.ts +21 -0
- package/dist/oauth/metadata.js +55 -0
- package/dist/oauth/middleware.d.ts +22 -0
- package/dist/oauth/middleware.js +64 -0
- package/dist/oauth/register.d.ts +14 -0
- package/dist/oauth/register.js +83 -0
- package/dist/oauth/token.d.ts +15 -0
- package/dist/oauth/token.js +178 -0
- package/dist/oauth/validate.d.ts +30 -0
- package/dist/oauth/validate.js +52 -0
- package/dist/resources/HistoryResource.d.ts +38 -0
- package/dist/resources/HistoryResource.js +38 -0
- package/dist/resources/KnowledgeEntryResource.d.ts +64 -0
- package/dist/resources/KnowledgeEntryResource.js +157 -0
- package/dist/resources/QueryLogResource.d.ts +20 -0
- package/dist/resources/QueryLogResource.js +57 -0
- package/dist/resources/ServiceKeyResource.d.ts +51 -0
- package/dist/resources/ServiceKeyResource.js +132 -0
- package/dist/resources/TagResource.d.ts +25 -0
- package/dist/resources/TagResource.js +32 -0
- package/dist/resources/TriageResource.d.ts +51 -0
- package/dist/resources/TriageResource.js +107 -0
- package/dist/types.d.ts +317 -0
- package/dist/types.js +7 -0
- package/dist/webhooks/datadog.d.ts +26 -0
- package/dist/webhooks/datadog.js +120 -0
- package/dist/webhooks/github.d.ts +24 -0
- package/dist/webhooks/github.js +167 -0
- package/dist/webhooks/middleware.d.ts +14 -0
- package/dist/webhooks/middleware.js +161 -0
- package/dist/webhooks/types.d.ts +17 -0
- package/dist/webhooks/types.js +4 -0
- package/package.json +72 -0
- package/schema/knowledge.graphql +134 -0
- package/web/index.html +735 -0
- package/web/js/app.js +461 -0
- package/web/js/detail.js +223 -0
- package/web/js/editor.js +303 -0
- package/web/js/search.js +238 -0
- package/web/js/triage.js +305 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Entry Resource
|
|
3
|
+
*
|
|
4
|
+
* REST endpoint for knowledge base entries.
|
|
5
|
+
* GET is public; POST, PUT, DELETE require authentication.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* GET /Knowledge/<id> — return single entry
|
|
9
|
+
* GET /Knowledge/?query=.. — search entries
|
|
10
|
+
* POST /Knowledge/ — create new entry (auth required)
|
|
11
|
+
* PUT /Knowledge/<id> — update entry (auth required)
|
|
12
|
+
* DELETE /Knowledge/<id> — deprecate entry (team role required)
|
|
13
|
+
*/
|
|
14
|
+
import { createEntry, getEntry, updateEntry, deprecateEntry, stripEmbedding, } from "../core/entries.js";
|
|
15
|
+
import { search } from "../core/search.js";
|
|
16
|
+
function getResourceClass() {
|
|
17
|
+
return globalThis.Resource;
|
|
18
|
+
}
|
|
19
|
+
export class KnowledgeEntryResource extends getResourceClass() {
|
|
20
|
+
static loadAsInstance = false;
|
|
21
|
+
/**
|
|
22
|
+
* GET /Knowledge/<id> — return a single entry by ID.
|
|
23
|
+
* GET /Knowledge/?query=... — search the knowledge base.
|
|
24
|
+
* PUBLIC — no auth required.
|
|
25
|
+
*/
|
|
26
|
+
async get(target) {
|
|
27
|
+
const id = this.getId();
|
|
28
|
+
if (id) {
|
|
29
|
+
const entry = await getEntry(String(id));
|
|
30
|
+
if (!entry) {
|
|
31
|
+
return { status: 404, data: { error: "Entry not found" } };
|
|
32
|
+
}
|
|
33
|
+
return stripEmbedding(entry);
|
|
34
|
+
}
|
|
35
|
+
// Search mode: extract query params from target
|
|
36
|
+
const query = target?.get?.("query") || target?.query;
|
|
37
|
+
if (!query) {
|
|
38
|
+
return { error: "Provide an id or query parameter" };
|
|
39
|
+
}
|
|
40
|
+
const tagsParam = target?.get?.("tags") || target?.tags;
|
|
41
|
+
const limitParam = target?.get?.("limit") || target?.limit;
|
|
42
|
+
const contextParam = target?.get?.("context") || target?.context;
|
|
43
|
+
const modeParam = target?.get?.("mode") || target?.mode;
|
|
44
|
+
let context;
|
|
45
|
+
if (contextParam) {
|
|
46
|
+
try {
|
|
47
|
+
context =
|
|
48
|
+
typeof contextParam === "string"
|
|
49
|
+
? JSON.parse(contextParam)
|
|
50
|
+
: contextParam;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return {
|
|
54
|
+
status: 400,
|
|
55
|
+
data: { error: "Invalid context parameter: must be valid JSON" },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const params = {
|
|
60
|
+
query: String(query),
|
|
61
|
+
tags: tagsParam ? String(tagsParam).split(",") : undefined,
|
|
62
|
+
limit: limitParam ? parseInt(String(limitParam), 10) : undefined,
|
|
63
|
+
context,
|
|
64
|
+
mode: modeParam,
|
|
65
|
+
};
|
|
66
|
+
const results = await search(params);
|
|
67
|
+
return results.map(stripEmbedding);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* POST /Knowledge/ — create a new knowledge entry.
|
|
71
|
+
* AUTH REQUIRED. AI agents have their confidence forced to "ai-generated".
|
|
72
|
+
*/
|
|
73
|
+
async post(_target, data) {
|
|
74
|
+
const user = this.getCurrentUser();
|
|
75
|
+
if (!user) {
|
|
76
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
77
|
+
}
|
|
78
|
+
if (!data?.title || !data?.content) {
|
|
79
|
+
return { status: 400, data: { error: "title and content are required" } };
|
|
80
|
+
}
|
|
81
|
+
if (data.title.length > 500 || data.content.length > 100_000) {
|
|
82
|
+
return {
|
|
83
|
+
status: 400,
|
|
84
|
+
data: { error: "Title max 500 chars, content max 100,000 chars" },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Force ai-generated confidence for ai-agent role
|
|
88
|
+
if (user.role === "ai-agent") {
|
|
89
|
+
data.confidence = "ai-generated";
|
|
90
|
+
}
|
|
91
|
+
if (!data.addedBy) {
|
|
92
|
+
data.addedBy = user.username || user.id || "unknown";
|
|
93
|
+
}
|
|
94
|
+
return createEntry(data);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* PUT /Knowledge/<id> — create or update an entry.
|
|
98
|
+
* AUTH REQUIRED. AI agents have their confidence forced to "ai-generated".
|
|
99
|
+
*/
|
|
100
|
+
async put(_target, data) {
|
|
101
|
+
const user = this.getCurrentUser();
|
|
102
|
+
if (!user) {
|
|
103
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
104
|
+
}
|
|
105
|
+
const id = this.getId();
|
|
106
|
+
if (!id) {
|
|
107
|
+
return { status: 400, data: { error: "Entry ID required" } };
|
|
108
|
+
}
|
|
109
|
+
if (user.role === "ai-agent") {
|
|
110
|
+
data.confidence = "ai-generated";
|
|
111
|
+
}
|
|
112
|
+
if (!data.addedBy) {
|
|
113
|
+
data.addedBy = user.username || user.id || "unknown";
|
|
114
|
+
}
|
|
115
|
+
// Upsert: try update first, create if not found
|
|
116
|
+
try {
|
|
117
|
+
return await updateEntry(String(id), data);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
if (error.message.includes("not found")) {
|
|
121
|
+
// Entry doesn't exist — create if full data provided, otherwise 404
|
|
122
|
+
if (!data.title || !data.content) {
|
|
123
|
+
return { status: 404, data: { error: "Entry not found" } };
|
|
124
|
+
}
|
|
125
|
+
return createEntry({ ...data, id: String(id) });
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* DELETE /Knowledge/<id> — deprecate an entry (soft delete).
|
|
132
|
+
* AUTH REQUIRED: team role only.
|
|
133
|
+
*/
|
|
134
|
+
async delete(_target) {
|
|
135
|
+
const user = this.getCurrentUser();
|
|
136
|
+
if (!user) {
|
|
137
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
138
|
+
}
|
|
139
|
+
if (user.role !== "team") {
|
|
140
|
+
return { status: 403, data: { error: "Team role required" } };
|
|
141
|
+
}
|
|
142
|
+
const id = this.getId();
|
|
143
|
+
if (!id) {
|
|
144
|
+
return { status: 400, data: { error: "Entry ID required" } };
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
await deprecateEntry(String(id));
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
if (error.message.includes("not found")) {
|
|
152
|
+
return { status: 404, data: { error: error.message } };
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryLog Resource
|
|
3
|
+
*
|
|
4
|
+
* REST endpoint for search query analytics.
|
|
5
|
+
*
|
|
6
|
+
* Routes:
|
|
7
|
+
* GET /QueryLog/ — list recent query logs (team role required)
|
|
8
|
+
* GET /QueryLog/<id> — get a single query log entry (team role required)
|
|
9
|
+
*/
|
|
10
|
+
declare const QueryLogResource_base: any;
|
|
11
|
+
export declare class QueryLogResource extends QueryLogResource_base {
|
|
12
|
+
static loadAsInstance: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* GET /QueryLog/ — list query logs, optionally filtered.
|
|
15
|
+
* GET /QueryLog/<id> — get a single query log entry.
|
|
16
|
+
* AUTH REQUIRED: team role only.
|
|
17
|
+
*/
|
|
18
|
+
get(target?: any): Promise<Record<string, unknown> | Record<string, unknown>[]>;
|
|
19
|
+
}
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryLog Resource
|
|
3
|
+
*
|
|
4
|
+
* REST endpoint for search query analytics.
|
|
5
|
+
*
|
|
6
|
+
* Routes:
|
|
7
|
+
* GET /QueryLog/ — list recent query logs (team role required)
|
|
8
|
+
* GET /QueryLog/<id> — get a single query log entry (team role required)
|
|
9
|
+
*/
|
|
10
|
+
function getResourceClass() {
|
|
11
|
+
return globalThis.Resource;
|
|
12
|
+
}
|
|
13
|
+
export class QueryLogResource extends getResourceClass() {
|
|
14
|
+
static loadAsInstance = false;
|
|
15
|
+
/**
|
|
16
|
+
* GET /QueryLog/ — list query logs, optionally filtered.
|
|
17
|
+
* GET /QueryLog/<id> — get a single query log entry.
|
|
18
|
+
* AUTH REQUIRED: team role only.
|
|
19
|
+
*/
|
|
20
|
+
async get(target) {
|
|
21
|
+
const user = this.getCurrentUser();
|
|
22
|
+
if (!user) {
|
|
23
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
24
|
+
}
|
|
25
|
+
if (user.role !== "team") {
|
|
26
|
+
return { status: 403, data: { error: "Team role required" } };
|
|
27
|
+
}
|
|
28
|
+
const id = this.getId();
|
|
29
|
+
if (id) {
|
|
30
|
+
const entry = await databases.kb.QueryLog.get(String(id));
|
|
31
|
+
if (!entry) {
|
|
32
|
+
return { status: 404, data: { error: "Query log entry not found" } };
|
|
33
|
+
}
|
|
34
|
+
return entry;
|
|
35
|
+
}
|
|
36
|
+
// List mode: support optional limit and query filter
|
|
37
|
+
const limitParam = target?.get?.("limit") || target?.limit;
|
|
38
|
+
const queryFilter = target?.get?.("query") || target?.query;
|
|
39
|
+
const limit = limitParam ? parseInt(String(limitParam), 10) : 50;
|
|
40
|
+
const conditions = [];
|
|
41
|
+
if (queryFilter) {
|
|
42
|
+
conditions.push({
|
|
43
|
+
attribute: "query",
|
|
44
|
+
comparator: "contains",
|
|
45
|
+
value: String(queryFilter),
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const results = [];
|
|
49
|
+
for await (const item of databases.kb.QueryLog.search({
|
|
50
|
+
conditions: conditions.length > 0 ? conditions : undefined,
|
|
51
|
+
limit,
|
|
52
|
+
})) {
|
|
53
|
+
results.push(item);
|
|
54
|
+
}
|
|
55
|
+
return results;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServiceKey Resource
|
|
3
|
+
*
|
|
4
|
+
* REST endpoint for managing API keys for webhooks and service accounts.
|
|
5
|
+
* All operations require team role.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* GET /ServiceKey/ — list all keys (team role, keyHash never returned)
|
|
9
|
+
* GET /ServiceKey/<id> — get a single key (team role, keyHash never returned)
|
|
10
|
+
* POST /ServiceKey/ — create a new key (team role, returns plaintext key once)
|
|
11
|
+
* DELETE /ServiceKey/<id> — delete a key (team role)
|
|
12
|
+
*/
|
|
13
|
+
declare const ServiceKeyResource_base: any;
|
|
14
|
+
export declare class ServiceKeyResource extends ServiceKeyResource_base {
|
|
15
|
+
static loadAsInstance: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* GET /ServiceKey/ — list all service keys (keyHash excluded).
|
|
18
|
+
* GET /ServiceKey/<id> — get a single key (keyHash excluded).
|
|
19
|
+
* AUTH REQUIRED: team role only.
|
|
20
|
+
*/
|
|
21
|
+
get(target?: any): Promise<Record<string, unknown> | Record<string, unknown>[]>;
|
|
22
|
+
/**
|
|
23
|
+
* POST /ServiceKey/ — create a new API key.
|
|
24
|
+
* AUTH REQUIRED: team role only.
|
|
25
|
+
*
|
|
26
|
+
* Body: { name: string, role: "service_account" | "ai_agent", permissions?: object }
|
|
27
|
+
*
|
|
28
|
+
* Returns the plaintext key exactly once. It is never stored or retrievable again.
|
|
29
|
+
*/
|
|
30
|
+
post(_target: any, data: any): Promise<{
|
|
31
|
+
status: number;
|
|
32
|
+
data: {
|
|
33
|
+
error: string;
|
|
34
|
+
};
|
|
35
|
+
} | {
|
|
36
|
+
key: string;
|
|
37
|
+
status?: undefined;
|
|
38
|
+
data?: undefined;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* DELETE /ServiceKey/<id> — delete a service key.
|
|
42
|
+
* AUTH REQUIRED: team role only.
|
|
43
|
+
*/
|
|
44
|
+
delete(_target?: any): Promise<true | {
|
|
45
|
+
status: number;
|
|
46
|
+
data: {
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ServiceKey Resource
|
|
3
|
+
*
|
|
4
|
+
* REST endpoint for managing API keys for webhooks and service accounts.
|
|
5
|
+
* All operations require team role.
|
|
6
|
+
*
|
|
7
|
+
* Routes:
|
|
8
|
+
* GET /ServiceKey/ — list all keys (team role, keyHash never returned)
|
|
9
|
+
* GET /ServiceKey/<id> — get a single key (team role, keyHash never returned)
|
|
10
|
+
* POST /ServiceKey/ — create a new key (team role, returns plaintext key once)
|
|
11
|
+
* DELETE /ServiceKey/<id> — delete a key (team role)
|
|
12
|
+
*/
|
|
13
|
+
import crypto from "node:crypto";
|
|
14
|
+
function getResourceClass() {
|
|
15
|
+
return globalThis.Resource;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Hash an API key using scrypt with the given salt.
|
|
19
|
+
* Returns the hex-encoded hash.
|
|
20
|
+
*/
|
|
21
|
+
function hashKey(key, salt) {
|
|
22
|
+
return crypto.scryptSync(key, salt, 64).toString("hex");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Strip keyHash from a service key record before returning to the client.
|
|
26
|
+
*/
|
|
27
|
+
function sanitizeKey(record) {
|
|
28
|
+
const { keyHash: _keyHash, ...safe } = record;
|
|
29
|
+
return safe;
|
|
30
|
+
}
|
|
31
|
+
export class ServiceKeyResource extends getResourceClass() {
|
|
32
|
+
static loadAsInstance = false;
|
|
33
|
+
/**
|
|
34
|
+
* GET /ServiceKey/ — list all service keys (keyHash excluded).
|
|
35
|
+
* GET /ServiceKey/<id> — get a single key (keyHash excluded).
|
|
36
|
+
* AUTH REQUIRED: team role only.
|
|
37
|
+
*/
|
|
38
|
+
async get(target) {
|
|
39
|
+
const user = this.getCurrentUser();
|
|
40
|
+
if (!user) {
|
|
41
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
42
|
+
}
|
|
43
|
+
if (user.role !== "team") {
|
|
44
|
+
return { status: 403, data: { error: "Team role required" } };
|
|
45
|
+
}
|
|
46
|
+
const id = this.getId();
|
|
47
|
+
if (id) {
|
|
48
|
+
const record = await databases.kb.ServiceKey.get(String(id));
|
|
49
|
+
if (!record) {
|
|
50
|
+
return { status: 404, data: { error: "Service key not found" } };
|
|
51
|
+
}
|
|
52
|
+
return sanitizeKey(record);
|
|
53
|
+
}
|
|
54
|
+
// List mode
|
|
55
|
+
const limitParam = target?.get?.("limit") || target?.limit;
|
|
56
|
+
const limit = limitParam ? parseInt(String(limitParam), 10) : 100;
|
|
57
|
+
const results = [];
|
|
58
|
+
for await (const item of databases.kb.ServiceKey.search({ limit })) {
|
|
59
|
+
results.push(sanitizeKey(item));
|
|
60
|
+
}
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* POST /ServiceKey/ — create a new API key.
|
|
65
|
+
* AUTH REQUIRED: team role only.
|
|
66
|
+
*
|
|
67
|
+
* Body: { name: string, role: "service_account" | "ai_agent", permissions?: object }
|
|
68
|
+
*
|
|
69
|
+
* Returns the plaintext key exactly once. It is never stored or retrievable again.
|
|
70
|
+
*/
|
|
71
|
+
async post(_target, data) {
|
|
72
|
+
const user = this.getCurrentUser();
|
|
73
|
+
if (!user) {
|
|
74
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
75
|
+
}
|
|
76
|
+
if (user.role !== "team") {
|
|
77
|
+
return { status: 403, data: { error: "Team role required" } };
|
|
78
|
+
}
|
|
79
|
+
if (!data?.name) {
|
|
80
|
+
return { status: 400, data: { error: "name is required" } };
|
|
81
|
+
}
|
|
82
|
+
if (!data?.role ||
|
|
83
|
+
(data.role !== "service_account" && data.role !== "ai_agent")) {
|
|
84
|
+
return {
|
|
85
|
+
status: 400,
|
|
86
|
+
data: { error: 'role must be "service_account" or "ai_agent"' },
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
// Generate a random API key and hash it
|
|
90
|
+
const plaintextKey = crypto.randomBytes(32).toString("hex");
|
|
91
|
+
const salt = crypto.randomBytes(16).toString("hex");
|
|
92
|
+
const hash = hashKey(plaintextKey, salt);
|
|
93
|
+
const id = crypto.randomUUID();
|
|
94
|
+
const record = {
|
|
95
|
+
id,
|
|
96
|
+
name: data.name,
|
|
97
|
+
keyHash: `${salt}:${hash}`,
|
|
98
|
+
role: data.role,
|
|
99
|
+
permissions: data.permissions || null,
|
|
100
|
+
createdBy: user.username || user.id || "unknown",
|
|
101
|
+
};
|
|
102
|
+
await databases.kb.ServiceKey.put(record);
|
|
103
|
+
// Return the record without keyHash, plus the plaintext key (shown only once)
|
|
104
|
+
return {
|
|
105
|
+
...sanitizeKey(record),
|
|
106
|
+
key: plaintextKey,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* DELETE /ServiceKey/<id> — delete a service key.
|
|
111
|
+
* AUTH REQUIRED: team role only.
|
|
112
|
+
*/
|
|
113
|
+
async delete(_target) {
|
|
114
|
+
const user = this.getCurrentUser();
|
|
115
|
+
if (!user) {
|
|
116
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
117
|
+
}
|
|
118
|
+
if (user.role !== "team") {
|
|
119
|
+
return { status: 403, data: { error: "Team role required" } };
|
|
120
|
+
}
|
|
121
|
+
const id = this.getId();
|
|
122
|
+
if (!id) {
|
|
123
|
+
return { status: 400, data: { error: "Service key ID required" } };
|
|
124
|
+
}
|
|
125
|
+
const existing = await databases.kb.ServiceKey.get(String(id));
|
|
126
|
+
if (!existing) {
|
|
127
|
+
return { status: 404, data: { error: "Service key not found" } };
|
|
128
|
+
}
|
|
129
|
+
await databases.kb.ServiceKey.delete(String(id));
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tag Resource
|
|
3
|
+
*
|
|
4
|
+
* REST endpoint for knowledge base tags.
|
|
5
|
+
*
|
|
6
|
+
* Routes:
|
|
7
|
+
* GET /KnowledgeTag/ — list all tags (public)
|
|
8
|
+
* GET /KnowledgeTag/<id> — get a single tag by name (public)
|
|
9
|
+
*/
|
|
10
|
+
declare const TagResource_base: any;
|
|
11
|
+
export declare class TagResource extends TagResource_base {
|
|
12
|
+
static loadAsInstance: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* GET /KnowledgeTag/ — list all tags.
|
|
15
|
+
* GET /KnowledgeTag/<id> — get a single tag by name.
|
|
16
|
+
* PUBLIC — no auth required.
|
|
17
|
+
*/
|
|
18
|
+
get(_target?: any): Promise<import("../types.ts").KnowledgeTag | import("../types.ts").KnowledgeTag[] | {
|
|
19
|
+
status: number;
|
|
20
|
+
data: {
|
|
21
|
+
error: string;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tag Resource
|
|
3
|
+
*
|
|
4
|
+
* REST endpoint for knowledge base tags.
|
|
5
|
+
*
|
|
6
|
+
* Routes:
|
|
7
|
+
* GET /KnowledgeTag/ — list all tags (public)
|
|
8
|
+
* GET /KnowledgeTag/<id> — get a single tag by name (public)
|
|
9
|
+
*/
|
|
10
|
+
import { listTags, getTag } from "../core/tags.js";
|
|
11
|
+
function getResourceClass() {
|
|
12
|
+
return globalThis.Resource;
|
|
13
|
+
}
|
|
14
|
+
export class TagResource extends getResourceClass() {
|
|
15
|
+
static loadAsInstance = false;
|
|
16
|
+
/**
|
|
17
|
+
* GET /KnowledgeTag/ — list all tags.
|
|
18
|
+
* GET /KnowledgeTag/<id> — get a single tag by name.
|
|
19
|
+
* PUBLIC — no auth required.
|
|
20
|
+
*/
|
|
21
|
+
async get(_target) {
|
|
22
|
+
const id = this.getId();
|
|
23
|
+
if (id) {
|
|
24
|
+
const tag = await getTag(String(id));
|
|
25
|
+
if (!tag) {
|
|
26
|
+
return { status: 404, data: { error: "Tag not found" } };
|
|
27
|
+
}
|
|
28
|
+
return tag;
|
|
29
|
+
}
|
|
30
|
+
return listTags();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triage Resource
|
|
3
|
+
*
|
|
4
|
+
* REST endpoint for the triage intake queue.
|
|
5
|
+
*
|
|
6
|
+
* Routes:
|
|
7
|
+
* GET /Triage/ — list pending triage items (team role required)
|
|
8
|
+
* POST /Triage/ — submit a new triage item (service_account or ai_agent role)
|
|
9
|
+
* PUT /Triage/<id> — process a triage item (team role required)
|
|
10
|
+
*/
|
|
11
|
+
declare const TriageResource_base: any;
|
|
12
|
+
export declare class TriageResource extends TriageResource_base {
|
|
13
|
+
static loadAsInstance: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* GET /Triage/ — list pending triage items.
|
|
16
|
+
* AUTH REQUIRED: team role only.
|
|
17
|
+
*/
|
|
18
|
+
get(_target?: any): Promise<import("../types.ts").TriageItem[] | {
|
|
19
|
+
status: number;
|
|
20
|
+
data: {
|
|
21
|
+
error: string;
|
|
22
|
+
};
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* POST /Triage/ — submit a new triage item.
|
|
26
|
+
* AUTH REQUIRED: service_account or ai_agent role.
|
|
27
|
+
*/
|
|
28
|
+
post(_target: any, data: any): Promise<import("../types.ts").TriageItem | {
|
|
29
|
+
status: number;
|
|
30
|
+
data: {
|
|
31
|
+
error: string;
|
|
32
|
+
};
|
|
33
|
+
}>;
|
|
34
|
+
/**
|
|
35
|
+
* PUT /Triage/<id> — process a triage item.
|
|
36
|
+
* AUTH REQUIRED: team role only.
|
|
37
|
+
*
|
|
38
|
+
* Body should include:
|
|
39
|
+
* { action: "accepted" | "dismissed" | "linked",
|
|
40
|
+
* processedBy?: string,
|
|
41
|
+
* entryData?: KnowledgeEntryInput,
|
|
42
|
+
* linkedEntryId?: string }
|
|
43
|
+
*/
|
|
44
|
+
put(_target: any, data: any): Promise<import("../types.ts").TriageItem | {
|
|
45
|
+
status: number;
|
|
46
|
+
data: {
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triage Resource
|
|
3
|
+
*
|
|
4
|
+
* REST endpoint for the triage intake queue.
|
|
5
|
+
*
|
|
6
|
+
* Routes:
|
|
7
|
+
* GET /Triage/ — list pending triage items (team role required)
|
|
8
|
+
* POST /Triage/ — submit a new triage item (service_account or ai_agent role)
|
|
9
|
+
* PUT /Triage/<id> — process a triage item (team role required)
|
|
10
|
+
*/
|
|
11
|
+
import { submitTriage, processTriage, listPending } from "../core/triage.js";
|
|
12
|
+
function getResourceClass() {
|
|
13
|
+
return globalThis.Resource;
|
|
14
|
+
}
|
|
15
|
+
export class TriageResource extends getResourceClass() {
|
|
16
|
+
static loadAsInstance = false;
|
|
17
|
+
/**
|
|
18
|
+
* GET /Triage/ — list pending triage items.
|
|
19
|
+
* AUTH REQUIRED: team role only.
|
|
20
|
+
*/
|
|
21
|
+
async get(_target) {
|
|
22
|
+
const user = this.getCurrentUser();
|
|
23
|
+
if (!user) {
|
|
24
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
25
|
+
}
|
|
26
|
+
if (user.role !== "team") {
|
|
27
|
+
return { status: 403, data: { error: "Team role required" } };
|
|
28
|
+
}
|
|
29
|
+
return listPending();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* POST /Triage/ — submit a new triage item.
|
|
33
|
+
* AUTH REQUIRED: service_account or ai_agent role.
|
|
34
|
+
*/
|
|
35
|
+
async post(_target, data) {
|
|
36
|
+
const user = this.getCurrentUser();
|
|
37
|
+
if (!user) {
|
|
38
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
39
|
+
}
|
|
40
|
+
if (user.role !== "service_account" && user.role !== "ai_agent") {
|
|
41
|
+
return {
|
|
42
|
+
status: 403,
|
|
43
|
+
data: { error: "service_account or ai_agent role required" },
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (!data?.source || !data?.summary) {
|
|
47
|
+
return {
|
|
48
|
+
status: 400,
|
|
49
|
+
data: { error: "source and summary are required" },
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return submitTriage(data.source, data.summary, data.rawPayload);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* PUT /Triage/<id> — process a triage item.
|
|
56
|
+
* AUTH REQUIRED: team role only.
|
|
57
|
+
*
|
|
58
|
+
* Body should include:
|
|
59
|
+
* { action: "accepted" | "dismissed" | "linked",
|
|
60
|
+
* processedBy?: string,
|
|
61
|
+
* entryData?: KnowledgeEntryInput,
|
|
62
|
+
* linkedEntryId?: string }
|
|
63
|
+
*/
|
|
64
|
+
async put(_target, data) {
|
|
65
|
+
const user = this.getCurrentUser();
|
|
66
|
+
if (!user) {
|
|
67
|
+
return { status: 401, data: { error: "Authentication required" } };
|
|
68
|
+
}
|
|
69
|
+
if (user.role !== "team") {
|
|
70
|
+
return { status: 403, data: { error: "Team role required" } };
|
|
71
|
+
}
|
|
72
|
+
const id = this.getId();
|
|
73
|
+
if (!id) {
|
|
74
|
+
return { status: 400, data: { error: "Triage item ID required" } };
|
|
75
|
+
}
|
|
76
|
+
if (!data?.action) {
|
|
77
|
+
return {
|
|
78
|
+
status: 400,
|
|
79
|
+
data: { error: "action is required (accepted, dismissed, or linked)" },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (!["accepted", "dismissed", "linked"].includes(data.action)) {
|
|
83
|
+
return {
|
|
84
|
+
status: 400,
|
|
85
|
+
data: { error: "action must be accepted, dismissed, or linked" },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const action = data.action;
|
|
89
|
+
const processedBy = data.processedBy || user.username || user.id || "unknown";
|
|
90
|
+
const options = {};
|
|
91
|
+
if (data.entryData) {
|
|
92
|
+
options.entryData = data.entryData;
|
|
93
|
+
}
|
|
94
|
+
if (data.linkedEntryId) {
|
|
95
|
+
options.linkedEntryId = data.linkedEntryId;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return await processTriage(String(id), action, processedBy, options);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error.message.includes("not found")) {
|
|
102
|
+
return { status: 404, data: { error: error.message } };
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|