@ouro.bot/cli 0.1.0-alpha.210 → 0.1.0-alpha.211
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/changelog.json +6 -0
- package/dist/arc/cares.js +7 -46
- package/dist/arc/intentions.js +7 -47
- package/dist/arc/json-store.js +120 -0
- package/dist/arc/obligations.js +23 -107
- package/dist/mind/associative-recall.js +4 -34
- package/dist/mind/diary.js +4 -40
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/nerves/coverage/file-completeness.js +5 -0
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/api-client.js +71 -0
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.211",
|
|
6
|
+
"changes": [
|
|
7
|
+
"refactor: extract duplicated patterns into shared utilities — mind/embedding-provider.ts (shared OpenAI embedding client from diary + associative-recall), arc/json-store.ts (shared JSON file CRUD from obligations + cares + intentions), repertoire/api-client.ts (shared HTTP request helper from graph + ado + github clients). M12 (channel callback factory) skipped: CLI and Teams streaming implementations are too different for clean abstraction."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.210",
|
|
6
12
|
"changes": [
|
package/dist/arc/cares.js
CHANGED
|
@@ -38,24 +38,15 @@ exports.readCares = readCares;
|
|
|
38
38
|
exports.readActiveCares = readActiveCares;
|
|
39
39
|
exports.updateCare = updateCare;
|
|
40
40
|
exports.resolveCare = resolveCare;
|
|
41
|
-
const fs = __importStar(require("fs"));
|
|
42
41
|
const path = __importStar(require("path"));
|
|
43
42
|
const runtime_1 = require("../nerves/runtime");
|
|
44
|
-
const
|
|
43
|
+
const json_store_1 = require("./json-store");
|
|
45
44
|
function caresDir(agentRoot) {
|
|
46
45
|
return path.join(agentRoot, "arc", "cares");
|
|
47
46
|
}
|
|
48
|
-
function careFilePath(agentRoot, id) {
|
|
49
|
-
return path.join(caresDir(agentRoot), `${id}.json`);
|
|
50
|
-
}
|
|
51
|
-
function generateId() {
|
|
52
|
-
const timestamp = Date.now();
|
|
53
|
-
const random = Math.random().toString(36).slice(2, 10);
|
|
54
|
-
return `care-${timestamp}-${random}`;
|
|
55
|
-
}
|
|
56
47
|
function createCare(agentRoot, input) {
|
|
57
48
|
const now = new Date().toISOString();
|
|
58
|
-
const id =
|
|
49
|
+
const id = (0, json_store_1.generateTimestampId)("care");
|
|
59
50
|
const care = {
|
|
60
51
|
id,
|
|
61
52
|
label: input.label,
|
|
@@ -73,11 +64,7 @@ function createCare(agentRoot, input) {
|
|
|
73
64
|
createdAt: now,
|
|
74
65
|
updatedAt: now,
|
|
75
66
|
};
|
|
76
|
-
|
|
77
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
78
|
-
const filePath = careFilePath(agentRoot, id);
|
|
79
|
-
fs.writeFileSync(filePath, JSON.stringify(care, null, 2), "utf-8");
|
|
80
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
67
|
+
(0, json_store_1.writeJsonFile)(caresDir(agentRoot), id, care);
|
|
81
68
|
(0, runtime_1.emitNervesEvent)({
|
|
82
69
|
component: "heart",
|
|
83
70
|
event: "heart.care_created",
|
|
@@ -88,31 +75,11 @@ function createCare(agentRoot, input) {
|
|
|
88
75
|
}
|
|
89
76
|
function readCares(agentRoot) {
|
|
90
77
|
const dir = caresDir(agentRoot);
|
|
91
|
-
|
|
92
|
-
(0, runtime_1.emitNervesEvent)({
|
|
93
|
-
component: "heart",
|
|
94
|
-
event: "heart.cares_read",
|
|
95
|
-
message: "read cares: directory missing, returning empty",
|
|
96
|
-
meta: { count: 0 },
|
|
97
|
-
});
|
|
98
|
-
return [];
|
|
99
|
-
}
|
|
100
|
-
const files = fs.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
101
|
-
const cares = [];
|
|
102
|
-
for (const file of files) {
|
|
103
|
-
try {
|
|
104
|
-
const content = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
105
|
-
const care = JSON.parse(content);
|
|
106
|
-
cares.push(care);
|
|
107
|
-
}
|
|
108
|
-
catch {
|
|
109
|
-
// Skip malformed JSON files gracefully
|
|
110
|
-
}
|
|
111
|
-
}
|
|
78
|
+
const cares = (0, json_store_1.readJsonDir)(dir);
|
|
112
79
|
(0, runtime_1.emitNervesEvent)({
|
|
113
80
|
component: "heart",
|
|
114
81
|
event: "heart.cares_read",
|
|
115
|
-
message: `read ${cares.length} cares`,
|
|
82
|
+
message: cares.length === 0 ? "read cares: directory missing, returning empty" : `read ${cares.length} cares`,
|
|
116
83
|
meta: { count: cares.length },
|
|
117
84
|
});
|
|
118
85
|
return cares;
|
|
@@ -129,16 +96,10 @@ function readActiveCares(agentRoot) {
|
|
|
129
96
|
return active;
|
|
130
97
|
}
|
|
131
98
|
function readCareFile(agentRoot, id) {
|
|
132
|
-
|
|
133
|
-
if (!fs.existsSync(filePath)) {
|
|
134
|
-
throw new Error(`Care not found: ${id}`);
|
|
135
|
-
}
|
|
136
|
-
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
99
|
+
return (0, json_store_1.readJsonFileOrThrow)(caresDir(agentRoot), id, "Care");
|
|
137
100
|
}
|
|
138
101
|
function writeCareFile(agentRoot, care) {
|
|
139
|
-
|
|
140
|
-
fs.writeFileSync(filePath, JSON.stringify(care, null, 2), "utf-8");
|
|
141
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
102
|
+
(0, json_store_1.writeJsonFile)(caresDir(agentRoot), care.id, care);
|
|
142
103
|
}
|
|
143
104
|
function updateCare(agentRoot, id, updates) {
|
|
144
105
|
const care = readCareFile(agentRoot, id);
|
package/dist/arc/intentions.js
CHANGED
|
@@ -37,21 +37,12 @@ exports.captureIntention = captureIntention;
|
|
|
37
37
|
exports.readOpenIntentions = readOpenIntentions;
|
|
38
38
|
exports.resolveIntention = resolveIntention;
|
|
39
39
|
exports.dismissIntention = dismissIntention;
|
|
40
|
-
const fs = __importStar(require("fs"));
|
|
41
40
|
const path = __importStar(require("path"));
|
|
42
41
|
const runtime_1 = require("../nerves/runtime");
|
|
43
|
-
const
|
|
42
|
+
const json_store_1 = require("./json-store");
|
|
44
43
|
function intentionsDir(agentRoot) {
|
|
45
44
|
return path.join(agentRoot, "arc", "intentions");
|
|
46
45
|
}
|
|
47
|
-
function intentionFilePath(agentRoot, id) {
|
|
48
|
-
return path.join(intentionsDir(agentRoot), `${id}.json`);
|
|
49
|
-
}
|
|
50
|
-
function generateId() {
|
|
51
|
-
const timestamp = Date.now();
|
|
52
|
-
const random = Math.random().toString(36).slice(2, 10);
|
|
53
|
-
return `int-${timestamp}-${random}`;
|
|
54
|
-
}
|
|
55
46
|
const SALIENCE_ORDER = {
|
|
56
47
|
high: 3,
|
|
57
48
|
medium: 2,
|
|
@@ -64,7 +55,7 @@ function salienceRank(salience) {
|
|
|
64
55
|
}
|
|
65
56
|
function captureIntention(agentRoot, input) {
|
|
66
57
|
const now = new Date().toISOString();
|
|
67
|
-
const id =
|
|
58
|
+
const id = (0, json_store_1.generateTimestampId)("int");
|
|
68
59
|
const intention = {
|
|
69
60
|
id,
|
|
70
61
|
content: input.content,
|
|
@@ -78,11 +69,7 @@ function captureIntention(agentRoot, input) {
|
|
|
78
69
|
...(input.relatedCareId ? { relatedCareId: input.relatedCareId } : {}),
|
|
79
70
|
...(input.nudgeAfter ? { nudgeAfter: input.nudgeAfter } : {}),
|
|
80
71
|
};
|
|
81
|
-
|
|
82
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
83
|
-
const filePath = intentionFilePath(agentRoot, id);
|
|
84
|
-
fs.writeFileSync(filePath, JSON.stringify(intention, null, 2), "utf-8");
|
|
85
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
72
|
+
(0, json_store_1.writeJsonFile)(intentionsDir(agentRoot), id, intention);
|
|
86
73
|
(0, runtime_1.emitNervesEvent)({
|
|
87
74
|
component: "heart",
|
|
88
75
|
event: "heart.intention_captured",
|
|
@@ -93,30 +80,9 @@ function captureIntention(agentRoot, input) {
|
|
|
93
80
|
}
|
|
94
81
|
function readOpenIntentions(agentRoot, options) {
|
|
95
82
|
const dir = intentionsDir(agentRoot);
|
|
96
|
-
|
|
97
|
-
(0, runtime_1.emitNervesEvent)({
|
|
98
|
-
component: "heart",
|
|
99
|
-
event: "heart.intentions_read",
|
|
100
|
-
message: "read intentions: directory missing, returning empty",
|
|
101
|
-
meta: { count: 0 },
|
|
102
|
-
});
|
|
103
|
-
return [];
|
|
104
|
-
}
|
|
83
|
+
const all = (0, json_store_1.readJsonDir)(dir);
|
|
105
84
|
const limit = options?.limit ?? 20;
|
|
106
|
-
const
|
|
107
|
-
const intentions = [];
|
|
108
|
-
for (const file of files) {
|
|
109
|
-
try {
|
|
110
|
-
const content = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
111
|
-
const intention = JSON.parse(content);
|
|
112
|
-
if (intention.status === "open") {
|
|
113
|
-
intentions.push(intention);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// Skip malformed JSON files
|
|
118
|
-
}
|
|
119
|
-
}
|
|
85
|
+
const intentions = all.filter((i) => i.status === "open");
|
|
120
86
|
// Sort by salience descending, then createdAt descending
|
|
121
87
|
intentions.sort((a, b) => {
|
|
122
88
|
const salienceDiff = salienceRank(b.salience) - salienceRank(a.salience);
|
|
@@ -134,16 +100,10 @@ function readOpenIntentions(agentRoot, options) {
|
|
|
134
100
|
return result;
|
|
135
101
|
}
|
|
136
102
|
function readIntentionFile(agentRoot, id) {
|
|
137
|
-
|
|
138
|
-
if (!fs.existsSync(filePath)) {
|
|
139
|
-
throw new Error(`Intention not found: ${id}`);
|
|
140
|
-
}
|
|
141
|
-
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
103
|
+
return (0, json_store_1.readJsonFileOrThrow)(intentionsDir(agentRoot), id, "Intention");
|
|
142
104
|
}
|
|
143
105
|
function writeIntentionFile(agentRoot, intention) {
|
|
144
|
-
|
|
145
|
-
fs.writeFileSync(filePath, JSON.stringify(intention, null, 2), "utf-8");
|
|
146
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
106
|
+
(0, json_store_1.writeJsonFile)(intentionsDir(agentRoot), intention.id, intention);
|
|
147
107
|
}
|
|
148
108
|
function resolveIntention(agentRoot, id) {
|
|
149
109
|
const intention = readIntentionFile(agentRoot, id);
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared JSON file store helpers for arc/ modules.
|
|
4
|
+
*
|
|
5
|
+
* obligations.ts, cares.ts, and intentions.ts all use the same pattern:
|
|
6
|
+
* individual JSON files in a directory, with read-all / read-one / write-one
|
|
7
|
+
* operations. This module extracts those patterns.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.generateTimestampId = generateTimestampId;
|
|
44
|
+
exports.readJsonDir = readJsonDir;
|
|
45
|
+
exports.readJsonFile = readJsonFile;
|
|
46
|
+
exports.readJsonFileOrThrow = readJsonFileOrThrow;
|
|
47
|
+
exports.writeJsonFile = writeJsonFile;
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const sync_1 = require("../heart/sync");
|
|
51
|
+
/**
|
|
52
|
+
* Generate a timestamped random ID.
|
|
53
|
+
* @param prefix Optional prefix (e.g. "care", "int"). Omit for plain timestamp IDs.
|
|
54
|
+
*/
|
|
55
|
+
function generateTimestampId(prefix) {
|
|
56
|
+
const timestamp = Date.now();
|
|
57
|
+
const random = Math.random().toString(36).slice(2, 10);
|
|
58
|
+
return prefix ? `${prefix}-${timestamp}-${random}` : `${timestamp}-${random}`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Read all JSON files from a directory, parse each, and return an array.
|
|
62
|
+
* Silently skips malformed files. Returns empty array if directory is missing.
|
|
63
|
+
*/
|
|
64
|
+
function readJsonDir(dir) {
|
|
65
|
+
if (!fs.existsSync(dir))
|
|
66
|
+
return [];
|
|
67
|
+
let entries;
|
|
68
|
+
try {
|
|
69
|
+
entries = fs.readdirSync(dir);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
/* v8 ignore next -- defensive: readdirSync race after existsSync @preserve */
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const jsonFiles = entries.filter((entry) => entry.endsWith(".json")).sort();
|
|
76
|
+
const records = [];
|
|
77
|
+
for (const file of jsonFiles) {
|
|
78
|
+
try {
|
|
79
|
+
const raw = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
80
|
+
records.push(JSON.parse(raw));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Skip malformed JSON files
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return records;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Read a single JSON file by ID from a directory.
|
|
90
|
+
* Returns null if the file doesn't exist or is unparseable.
|
|
91
|
+
*/
|
|
92
|
+
function readJsonFile(dir, id) {
|
|
93
|
+
try {
|
|
94
|
+
const raw = fs.readFileSync(path.join(dir, `${id}.json`), "utf-8");
|
|
95
|
+
return JSON.parse(raw);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Read a single JSON file by ID, throwing if not found.
|
|
103
|
+
*/
|
|
104
|
+
function readJsonFileOrThrow(dir, id, label) {
|
|
105
|
+
const filePath = path.join(dir, `${id}.json`);
|
|
106
|
+
if (!fs.existsSync(filePath)) {
|
|
107
|
+
throw new Error(`${label} not found: ${id}`);
|
|
108
|
+
}
|
|
109
|
+
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Write a record to a JSON file, creating the directory if needed.
|
|
113
|
+
* Automatically calls trackSyncWrite.
|
|
114
|
+
*/
|
|
115
|
+
function writeJsonFile(dir, id, record) {
|
|
116
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
117
|
+
const filePath = path.join(dir, `${id}.json`);
|
|
118
|
+
fs.writeFileSync(filePath, JSON.stringify(record, null, 2), "utf-8");
|
|
119
|
+
(0, sync_1.trackSyncWrite)(filePath);
|
|
120
|
+
}
|
package/dist/arc/obligations.js
CHANGED
|
@@ -48,22 +48,13 @@ exports.createReturnObligation = createReturnObligation;
|
|
|
48
48
|
exports.readReturnObligation = readReturnObligation;
|
|
49
49
|
exports.advanceReturnObligation = advanceReturnObligation;
|
|
50
50
|
exports.listActiveReturnObligations = listActiveReturnObligations;
|
|
51
|
-
const fs = __importStar(require("fs"));
|
|
52
51
|
const path = __importStar(require("path"));
|
|
53
52
|
const identity_1 = require("../heart/identity");
|
|
54
53
|
const runtime_1 = require("../nerves/runtime");
|
|
55
|
-
const
|
|
54
|
+
const json_store_1 = require("./json-store");
|
|
56
55
|
function obligationsDir(agentRoot) {
|
|
57
56
|
return path.join(agentRoot, "arc", "obligations");
|
|
58
57
|
}
|
|
59
|
-
function obligationFilePath(agentRoot, id) {
|
|
60
|
-
return path.join(obligationsDir(agentRoot), `${id}.json`);
|
|
61
|
-
}
|
|
62
|
-
function generateId() {
|
|
63
|
-
const timestamp = Date.now();
|
|
64
|
-
const random = Math.random().toString(36).slice(2, 10);
|
|
65
|
-
return `${timestamp}-${random}`;
|
|
66
|
-
}
|
|
67
58
|
function isOpenObligationStatus(status) {
|
|
68
59
|
return status !== "fulfilled";
|
|
69
60
|
}
|
|
@@ -72,7 +63,7 @@ function isOpenObligation(obligation) {
|
|
|
72
63
|
}
|
|
73
64
|
function createObligation(agentRoot, input) {
|
|
74
65
|
const now = new Date().toISOString();
|
|
75
|
-
const id =
|
|
66
|
+
const id = (0, json_store_1.generateTimestampId)();
|
|
76
67
|
const obligation = {
|
|
77
68
|
id,
|
|
78
69
|
origin: input.origin,
|
|
@@ -82,11 +73,7 @@ function createObligation(agentRoot, input) {
|
|
|
82
73
|
createdAt: now,
|
|
83
74
|
updatedAt: now,
|
|
84
75
|
};
|
|
85
|
-
|
|
86
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
87
|
-
const filePath = obligationFilePath(agentRoot, id);
|
|
88
|
-
fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf-8");
|
|
89
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
76
|
+
(0, json_store_1.writeJsonFile)(obligationsDir(agentRoot), id, obligation);
|
|
90
77
|
(0, runtime_1.emitNervesEvent)({
|
|
91
78
|
component: "engine",
|
|
92
79
|
event: "engine.obligation_created",
|
|
@@ -101,46 +88,17 @@ function createObligation(agentRoot, input) {
|
|
|
101
88
|
return obligation;
|
|
102
89
|
}
|
|
103
90
|
function readObligations(agentRoot) {
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
return [];
|
|
107
|
-
let entries;
|
|
108
|
-
try {
|
|
109
|
-
entries = fs.readdirSync(dir);
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
/* v8 ignore next -- defensive: readdirSync race after existsSync @preserve */
|
|
113
|
-
return [];
|
|
114
|
-
}
|
|
115
|
-
const jsonFiles = entries.filter((entry) => entry.endsWith(".json")).sort();
|
|
116
|
-
const obligations = [];
|
|
117
|
-
for (const file of jsonFiles) {
|
|
118
|
-
try {
|
|
119
|
-
const raw = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
120
|
-
const parsed = JSON.parse(raw);
|
|
121
|
-
if (typeof parsed.id === "string" && typeof parsed.content === "string") {
|
|
122
|
-
obligations.push(parsed);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
// skip malformed files
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return obligations;
|
|
91
|
+
const all = (0, json_store_1.readJsonDir)(obligationsDir(agentRoot));
|
|
92
|
+
return all.filter((parsed) => typeof parsed.id === "string" && typeof parsed.content === "string");
|
|
130
93
|
}
|
|
131
94
|
function readPendingObligations(agentRoot) {
|
|
132
95
|
return readObligations(agentRoot).filter(isOpenObligation);
|
|
133
96
|
}
|
|
134
97
|
function advanceObligation(agentRoot, obligationId, update) {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
139
|
-
obligation = JSON.parse(raw);
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
98
|
+
const dir = obligationsDir(agentRoot);
|
|
99
|
+
const obligation = (0, json_store_1.readJsonFile)(dir, obligationId);
|
|
100
|
+
if (!obligation)
|
|
142
101
|
return;
|
|
143
|
-
}
|
|
144
102
|
const previousStatus = obligation.status;
|
|
145
103
|
if (update.status) {
|
|
146
104
|
obligation.status = update.status;
|
|
@@ -161,8 +119,7 @@ function advanceObligation(agentRoot, obligationId, update) {
|
|
|
161
119
|
obligation.latestNote = update.latestNote;
|
|
162
120
|
}
|
|
163
121
|
obligation.updatedAt = new Date().toISOString();
|
|
164
|
-
|
|
165
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
122
|
+
(0, json_store_1.writeJsonFile)(dir, obligationId, obligation);
|
|
166
123
|
(0, runtime_1.emitNervesEvent)({
|
|
167
124
|
component: "engine",
|
|
168
125
|
event: "engine.obligation_advanced",
|
|
@@ -181,15 +138,9 @@ function advanceObligation(agentRoot, obligationId, update) {
|
|
|
181
138
|
}
|
|
182
139
|
function fulfillObligation(agentRoot, obligationId) {
|
|
183
140
|
advanceObligation(agentRoot, obligationId, { status: "fulfilled" });
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
const raw = fs.readFileSync(filePath, "utf-8");
|
|
188
|
-
obligation = JSON.parse(raw);
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
141
|
+
const obligation = (0, json_store_1.readJsonFile)(obligationsDir(agentRoot), obligationId);
|
|
142
|
+
if (!obligation)
|
|
191
143
|
return;
|
|
192
|
-
}
|
|
193
144
|
(0, runtime_1.emitNervesEvent)({
|
|
194
145
|
component: "engine",
|
|
195
146
|
event: "engine.obligation_fulfilled",
|
|
@@ -208,16 +159,15 @@ function findPendingObligationForOrigin(agentRoot, origin) {
|
|
|
208
159
|
&& ob.origin.key === origin.key);
|
|
209
160
|
}
|
|
210
161
|
function enrichObligation(agentRoot, id, meaning) {
|
|
211
|
-
const
|
|
212
|
-
|
|
162
|
+
const dir = obligationsDir(agentRoot);
|
|
163
|
+
const existing = (0, json_store_1.readJsonFile)(dir, id);
|
|
164
|
+
if (!existing) {
|
|
213
165
|
throw new Error(`Obligation not found: ${id}`);
|
|
214
166
|
}
|
|
215
|
-
const
|
|
216
|
-
const obligation = JSON.parse(raw);
|
|
167
|
+
const obligation = existing;
|
|
217
168
|
obligation.meaning = meaning;
|
|
218
169
|
obligation.updatedAt = new Date().toISOString();
|
|
219
|
-
|
|
220
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
170
|
+
(0, json_store_1.writeJsonFile)(dir, id, obligation);
|
|
221
171
|
(0, runtime_1.emitNervesEvent)({
|
|
222
172
|
component: "engine",
|
|
223
173
|
event: "engine.obligation_enriched",
|
|
@@ -238,10 +188,8 @@ function getReturnObligationsDir(agentName) {
|
|
|
238
188
|
}
|
|
239
189
|
function createReturnObligation(agentName, obligation) {
|
|
240
190
|
const dir = getReturnObligationsDir(agentName);
|
|
241
|
-
|
|
191
|
+
(0, json_store_1.writeJsonFile)(dir, obligation.id, obligation);
|
|
242
192
|
const filePath = path.join(dir, `${obligation.id}.json`);
|
|
243
|
-
fs.writeFileSync(filePath, JSON.stringify(obligation, null, 2), "utf8");
|
|
244
|
-
(0, sync_1.trackSyncWrite)(filePath);
|
|
245
193
|
(0, runtime_1.emitNervesEvent)({
|
|
246
194
|
event: "mind.obligation_created",
|
|
247
195
|
component: "mind",
|
|
@@ -255,14 +203,7 @@ function createReturnObligation(agentName, obligation) {
|
|
|
255
203
|
return filePath;
|
|
256
204
|
}
|
|
257
205
|
function readReturnObligation(agentName, obligationId) {
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
const raw = fs.readFileSync(path.join(dir, `${obligationId}.json`), "utf-8");
|
|
261
|
-
return JSON.parse(raw);
|
|
262
|
-
}
|
|
263
|
-
catch {
|
|
264
|
-
return null;
|
|
265
|
-
}
|
|
206
|
+
return (0, json_store_1.readJsonFile)(getReturnObligationsDir(agentName), obligationId);
|
|
266
207
|
}
|
|
267
208
|
function advanceReturnObligation(agentName, obligationId, update) {
|
|
268
209
|
const existing = readReturnObligation(agentName, obligationId);
|
|
@@ -275,10 +216,7 @@ function advanceReturnObligation(agentName, obligationId, update) {
|
|
|
275
216
|
...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
|
|
276
217
|
...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
|
|
277
218
|
};
|
|
278
|
-
|
|
279
|
-
const advancedFilePath = path.join(dir, `${obligationId}.json`);
|
|
280
|
-
fs.writeFileSync(advancedFilePath, JSON.stringify(updated, null, 2), "utf8");
|
|
281
|
-
(0, sync_1.trackSyncWrite)(advancedFilePath);
|
|
219
|
+
(0, json_store_1.writeJsonFile)(getReturnObligationsDir(agentName), obligationId, updated);
|
|
282
220
|
(0, runtime_1.emitNervesEvent)({
|
|
283
221
|
event: "mind.obligation_advanced",
|
|
284
222
|
component: "mind",
|
|
@@ -292,30 +230,8 @@ function advanceReturnObligation(agentName, obligationId, update) {
|
|
|
292
230
|
return updated;
|
|
293
231
|
}
|
|
294
232
|
function listActiveReturnObligations(agentName) {
|
|
295
|
-
const
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
try {
|
|
300
|
-
entries = fs.readdirSync(dir);
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
return [];
|
|
304
|
-
}
|
|
305
|
-
const obligations = [];
|
|
306
|
-
for (const file of entries) {
|
|
307
|
-
if (!file.endsWith(".json"))
|
|
308
|
-
continue;
|
|
309
|
-
try {
|
|
310
|
-
const raw = fs.readFileSync(path.join(dir, file), "utf-8");
|
|
311
|
-
const parsed = JSON.parse(raw);
|
|
312
|
-
if (parsed.status === "queued" || parsed.status === "running") {
|
|
313
|
-
obligations.push(parsed);
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
catch {
|
|
317
|
-
// skip unparseable
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return obligations.sort((a, b) => a.createdAt - b.createdAt);
|
|
233
|
+
const all = (0, json_store_1.readJsonDir)(getReturnObligationsDir(agentName));
|
|
234
|
+
return all
|
|
235
|
+
.filter((parsed) => parsed.status === "queued" || parsed.status === "running")
|
|
236
|
+
.sort((a, b) => a.createdAt - b.createdAt);
|
|
321
237
|
}
|
|
@@ -39,47 +39,17 @@ exports.searchJournalIndex = searchJournalIndex;
|
|
|
39
39
|
exports.injectAssociativeRecall = injectAssociativeRecall;
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
-
const config_1 = require("../heart/config");
|
|
43
42
|
const runtime_1 = require("../nerves/runtime");
|
|
44
43
|
const diary_1 = require("./diary");
|
|
45
|
-
const
|
|
44
|
+
const embedding_provider_1 = require("./embedding-provider");
|
|
46
45
|
const DEFAULT_MIN_SCORE = 0.5;
|
|
47
46
|
const DEFAULT_TOP_K = 3;
|
|
48
|
-
class OpenAIEmbeddingProvider {
|
|
49
|
-
apiKey;
|
|
50
|
-
model;
|
|
51
|
-
constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
|
|
52
|
-
this.apiKey = apiKey;
|
|
53
|
-
this.model = model;
|
|
54
|
-
}
|
|
55
|
-
async embed(texts) {
|
|
56
|
-
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
57
|
-
method: "POST",
|
|
58
|
-
headers: {
|
|
59
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
60
|
-
"Content-Type": "application/json",
|
|
61
|
-
},
|
|
62
|
-
body: JSON.stringify({
|
|
63
|
-
model: this.model,
|
|
64
|
-
input: texts,
|
|
65
|
-
}),
|
|
66
|
-
});
|
|
67
|
-
if (!response.ok) {
|
|
68
|
-
throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
|
|
69
|
-
}
|
|
70
|
-
const payload = (await response.json());
|
|
71
|
-
if (!payload.data || payload.data.length !== texts.length) {
|
|
72
|
-
throw new Error("embedding response missing expected vectors");
|
|
73
|
-
}
|
|
74
|
-
return payload.data.map((entry) => entry.embedding);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
47
|
function createDefaultProvider() {
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
48
|
+
const provider = (0, embedding_provider_1.createDefaultEmbeddingProvider)();
|
|
49
|
+
if (!provider) {
|
|
80
50
|
throw new Error("openaiEmbeddingsApiKey not configured");
|
|
81
51
|
}
|
|
82
|
-
return
|
|
52
|
+
return provider;
|
|
83
53
|
}
|
|
84
54
|
function readFacts(diaryRoot) {
|
|
85
55
|
const factsPath = path.join(diaryRoot, "facts.jsonl");
|
package/dist/mind/diary.js
CHANGED
|
@@ -43,44 +43,14 @@ exports.searchDiaryEntries = searchDiaryEntries;
|
|
|
43
43
|
const fs = __importStar(require("fs"));
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
45
|
const crypto_1 = require("crypto");
|
|
46
|
-
const config_1 = require("../heart/config");
|
|
47
46
|
const identity_1 = require("../heart/identity");
|
|
48
47
|
const runtime_1 = require("../nerves/runtime");
|
|
49
48
|
const sync_1 = require("../heart/sync");
|
|
50
49
|
const associative_recall_1 = require("./associative-recall");
|
|
50
|
+
const embedding_provider_1 = require("./embedding-provider");
|
|
51
51
|
const DEDUP_THRESHOLD = 0.6;
|
|
52
52
|
const SEMANTIC_DEDUP_THRESHOLD = 0.95;
|
|
53
53
|
const ENTITY_TOKEN = /[a-z0-9]+/g;
|
|
54
|
-
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
55
|
-
class OpenAIEmbeddingProvider {
|
|
56
|
-
apiKey;
|
|
57
|
-
model;
|
|
58
|
-
constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
|
|
59
|
-
this.apiKey = apiKey;
|
|
60
|
-
this.model = model;
|
|
61
|
-
}
|
|
62
|
-
async embed(texts) {
|
|
63
|
-
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
64
|
-
method: "POST",
|
|
65
|
-
headers: {
|
|
66
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
67
|
-
"Content-Type": "application/json",
|
|
68
|
-
},
|
|
69
|
-
body: JSON.stringify({
|
|
70
|
-
model: this.model,
|
|
71
|
-
input: texts,
|
|
72
|
-
}),
|
|
73
|
-
});
|
|
74
|
-
if (!response.ok) {
|
|
75
|
-
throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
|
|
76
|
-
}
|
|
77
|
-
const payload = (await response.json());
|
|
78
|
-
if (!payload.data || payload.data.length !== texts.length) {
|
|
79
|
-
throw new Error("embedding response missing expected vectors");
|
|
80
|
-
}
|
|
81
|
-
return payload.data.map((entry) => entry.embedding);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
54
|
function ensureDiaryStorePaths(rootDir) {
|
|
85
55
|
const factsPath = path.join(rootDir, "facts.jsonl");
|
|
86
56
|
const entitiesPath = path.join(rootDir, "entities.json");
|
|
@@ -219,14 +189,8 @@ function appendEntriesWithDedup(stores, incoming, options) {
|
|
|
219
189
|
});
|
|
220
190
|
return { added, skipped };
|
|
221
191
|
}
|
|
222
|
-
function createDefaultEmbeddingProvider() {
|
|
223
|
-
const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
|
|
224
|
-
if (!apiKey)
|
|
225
|
-
return null;
|
|
226
|
-
return new OpenAIEmbeddingProvider(apiKey);
|
|
227
|
-
}
|
|
228
192
|
async function buildEmbedding(text, embeddingProvider) {
|
|
229
|
-
const provider = embeddingProvider ?? createDefaultEmbeddingProvider();
|
|
193
|
+
const provider = embeddingProvider ?? (0, embedding_provider_1.createDefaultEmbeddingProvider)();
|
|
230
194
|
if (!provider) {
|
|
231
195
|
(0, runtime_1.emitNervesEvent)({
|
|
232
196
|
level: "warn",
|
|
@@ -287,7 +251,7 @@ async function backfillEmbeddings(options) {
|
|
|
287
251
|
const needsEmbedding = facts.filter((f) => !Array.isArray(f.embedding) || f.embedding.length === 0);
|
|
288
252
|
if (needsEmbedding.length === 0)
|
|
289
253
|
return { total: facts.length, backfilled: 0, failed: 0 };
|
|
290
|
-
const provider = options?.embeddingProvider ?? createDefaultEmbeddingProvider();
|
|
254
|
+
const provider = options?.embeddingProvider ?? (0, embedding_provider_1.createDefaultEmbeddingProvider)();
|
|
291
255
|
if (!provider) {
|
|
292
256
|
(0, runtime_1.emitNervesEvent)({
|
|
293
257
|
level: "warn",
|
|
@@ -364,7 +328,7 @@ async function searchDiaryEntries(query, facts, embeddingProvider) {
|
|
|
364
328
|
if (embeddedFacts.length === 0) {
|
|
365
329
|
return substringFallback();
|
|
366
330
|
}
|
|
367
|
-
const provider = embeddingProvider ?? createDefaultEmbeddingProvider();
|
|
331
|
+
const provider = embeddingProvider ?? (0, embedding_provider_1.createDefaultEmbeddingProvider)();
|
|
368
332
|
if (!provider) {
|
|
369
333
|
return substringFallback();
|
|
370
334
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared OpenAI embedding provider.
|
|
4
|
+
*
|
|
5
|
+
* Both diary.ts and associative-recall.ts need to call the OpenAI embeddings
|
|
6
|
+
* API. This module provides the shared implementation so neither duplicates
|
|
7
|
+
* the fetch logic.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.OpenAIEmbeddingProvider = void 0;
|
|
11
|
+
exports.createDefaultEmbeddingProvider = createDefaultEmbeddingProvider;
|
|
12
|
+
const config_1 = require("../heart/config");
|
|
13
|
+
const runtime_1 = require("../nerves/runtime");
|
|
14
|
+
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
15
|
+
class OpenAIEmbeddingProvider {
|
|
16
|
+
apiKey;
|
|
17
|
+
model;
|
|
18
|
+
constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
|
|
19
|
+
this.apiKey = apiKey;
|
|
20
|
+
this.model = model;
|
|
21
|
+
}
|
|
22
|
+
async embed(texts) {
|
|
23
|
+
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
model: this.model,
|
|
31
|
+
input: texts,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
const payload = (await response.json());
|
|
38
|
+
if (!payload.data || payload.data.length !== texts.length) {
|
|
39
|
+
throw new Error("embedding response missing expected vectors");
|
|
40
|
+
}
|
|
41
|
+
return payload.data.map((entry) => entry.embedding);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.OpenAIEmbeddingProvider = OpenAIEmbeddingProvider;
|
|
45
|
+
/**
|
|
46
|
+
* Create a default embedding provider from the configured API key.
|
|
47
|
+
* Returns null if no key is configured.
|
|
48
|
+
*/
|
|
49
|
+
function createDefaultEmbeddingProvider() {
|
|
50
|
+
const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
|
|
51
|
+
if (!apiKey)
|
|
52
|
+
return null;
|
|
53
|
+
(0, runtime_1.emitNervesEvent)({
|
|
54
|
+
component: "mind",
|
|
55
|
+
event: "mind.embedding_provider_created",
|
|
56
|
+
message: "default embedding provider created",
|
|
57
|
+
meta: { model: DEFAULT_EMBEDDING_MODEL },
|
|
58
|
+
});
|
|
59
|
+
return new OpenAIEmbeddingProvider(apiKey);
|
|
60
|
+
}
|
|
@@ -61,6 +61,11 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
61
61
|
// cli-parse and cli-render are pure functions with no side effects.
|
|
62
62
|
"daemon/cli-parse",
|
|
63
63
|
"daemon/cli-render",
|
|
64
|
+
// Shared utility modules: pure helpers consumed by modules that own observability.
|
|
65
|
+
"arc/json-store",
|
|
66
|
+
"repertoire/api-client",
|
|
67
|
+
"repertoire/github-client",
|
|
68
|
+
"mind/embedding-provider",
|
|
64
69
|
];
|
|
65
70
|
function isDispatchExempt(filePath) {
|
|
66
71
|
return DISPATCH_EXEMPT_PATTERNS.some((pattern) => filePath.includes(pattern));
|
|
@@ -9,6 +9,7 @@ exports.discoverOrganizations = discoverOrganizations;
|
|
|
9
9
|
exports.discoverProjects = discoverProjects;
|
|
10
10
|
const api_error_1 = require("../heart/api-error");
|
|
11
11
|
const runtime_1 = require("../nerves/runtime");
|
|
12
|
+
const api_client_1 = require("./api-client");
|
|
12
13
|
const ADO_BASE = "https://dev.azure.com";
|
|
13
14
|
const VSSPS_BASE = "https://app.vssps.visualstudio.com";
|
|
14
15
|
const DEFAULT_API_VERSION = "api-version=7.1";
|
|
@@ -30,62 +31,20 @@ function resolveContentType(method, path) {
|
|
|
30
31
|
// Generic ADO API request. Returns response body as pretty-printed JSON string.
|
|
31
32
|
// `host` overrides the base URL for non-standard APIs (e.g. "vsapm.dev.azure.com", "vssps.dev.azure.com").
|
|
32
33
|
async function adoRequest(token, method, org, path, body, host) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Authorization: `Bearer ${token}`,
|
|
48
|
-
"Content-Type": contentType,
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
if (body)
|
|
52
|
-
opts.body = body;
|
|
53
|
-
const res = await fetch(url, opts);
|
|
54
|
-
if (!res.ok) {
|
|
55
|
-
(0, runtime_1.emitNervesEvent)({
|
|
56
|
-
level: "error",
|
|
57
|
-
event: "client.error",
|
|
58
|
-
component: "clients",
|
|
59
|
-
message: "ADO request failed",
|
|
60
|
-
meta: { client: "ado", method, org, path, status: res.status },
|
|
61
|
-
});
|
|
62
|
-
return (0, api_error_1.handleApiError)(res, "ADO", "ado");
|
|
63
|
-
}
|
|
64
|
-
const data = await res.json();
|
|
65
|
-
(0, runtime_1.emitNervesEvent)({
|
|
66
|
-
event: "client.request_end",
|
|
67
|
-
component: "clients",
|
|
68
|
-
message: "ADO request completed",
|
|
69
|
-
meta: { client: "ado", method, org, path, success: true },
|
|
70
|
-
});
|
|
71
|
-
return JSON.stringify(data, null, 2);
|
|
72
|
-
}
|
|
73
|
-
catch (err) {
|
|
74
|
-
(0, runtime_1.emitNervesEvent)({
|
|
75
|
-
level: "error",
|
|
76
|
-
event: "client.error",
|
|
77
|
-
component: "clients",
|
|
78
|
-
message: "ADO request threw exception",
|
|
79
|
-
meta: {
|
|
80
|
-
client: "ado",
|
|
81
|
-
method,
|
|
82
|
-
org,
|
|
83
|
-
path,
|
|
84
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
85
|
-
},
|
|
86
|
-
});
|
|
87
|
-
return (0, api_error_1.handleApiError)(err, "ADO", "ado");
|
|
88
|
-
}
|
|
34
|
+
const base = host ? `https://${host}/${org}` : `${ADO_BASE}/${org}`;
|
|
35
|
+
const fullPath = ensureApiVersion(path);
|
|
36
|
+
return (0, api_client_1.apiRequest)({
|
|
37
|
+
baseUrl: base,
|
|
38
|
+
method,
|
|
39
|
+
path: fullPath,
|
|
40
|
+
token,
|
|
41
|
+
clientName: "ado",
|
|
42
|
+
serviceLabel: "ADO",
|
|
43
|
+
connectionName: "ado",
|
|
44
|
+
body,
|
|
45
|
+
contentType: resolveContentType(method, path),
|
|
46
|
+
eventMeta: { org },
|
|
47
|
+
});
|
|
89
48
|
}
|
|
90
49
|
// Backward-compatible thin wrapper: runs WIQL query and returns formatted work items.
|
|
91
50
|
async function queryWorkItems(token, org, query) {
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared HTTP API client for Graph, ADO, and GitHub clients.
|
|
4
|
+
*
|
|
5
|
+
* All three API clients follow the same pattern: emit request_start event,
|
|
6
|
+
* build request with auth header, fetch, handle errors, emit request_end,
|
|
7
|
+
* and return pretty-printed JSON. This module extracts that shared logic.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.apiRequest = apiRequest;
|
|
11
|
+
const api_error_1 = require("../heart/api-error");
|
|
12
|
+
const runtime_1 = require("../nerves/runtime");
|
|
13
|
+
/**
|
|
14
|
+
* Execute an authenticated API request with standard logging and error handling.
|
|
15
|
+
* Returns the response body as a pretty-printed JSON string.
|
|
16
|
+
*/
|
|
17
|
+
async function apiRequest(options) {
|
|
18
|
+
const { baseUrl, method, path, token, clientName, serviceLabel, connectionName, body, extraHeaders, contentType, eventMeta, } = options;
|
|
19
|
+
try {
|
|
20
|
+
(0, runtime_1.emitNervesEvent)({
|
|
21
|
+
event: "client.request_start",
|
|
22
|
+
component: "clients",
|
|
23
|
+
message: `starting ${serviceLabel} request`,
|
|
24
|
+
meta: { client: clientName, method, path, ...eventMeta },
|
|
25
|
+
});
|
|
26
|
+
const url = `${baseUrl}${path}`;
|
|
27
|
+
const headers = {
|
|
28
|
+
Authorization: `Bearer ${token}`,
|
|
29
|
+
"Content-Type": contentType ?? "application/json",
|
|
30
|
+
...extraHeaders,
|
|
31
|
+
};
|
|
32
|
+
const opts = { method, headers };
|
|
33
|
+
if (body)
|
|
34
|
+
opts.body = body;
|
|
35
|
+
const res = await fetch(url, opts);
|
|
36
|
+
if (!res.ok) {
|
|
37
|
+
(0, runtime_1.emitNervesEvent)({
|
|
38
|
+
level: "error",
|
|
39
|
+
event: "client.error",
|
|
40
|
+
component: "clients",
|
|
41
|
+
message: `${serviceLabel} request failed`,
|
|
42
|
+
meta: { client: clientName, method, path, status: res.status, ...eventMeta },
|
|
43
|
+
});
|
|
44
|
+
return (0, api_error_1.handleApiError)(res, serviceLabel, connectionName);
|
|
45
|
+
}
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
(0, runtime_1.emitNervesEvent)({
|
|
48
|
+
event: "client.request_end",
|
|
49
|
+
component: "clients",
|
|
50
|
+
message: `${serviceLabel} request completed`,
|
|
51
|
+
meta: { client: clientName, method, path, success: true, ...eventMeta },
|
|
52
|
+
});
|
|
53
|
+
return JSON.stringify(data, null, 2);
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
(0, runtime_1.emitNervesEvent)({
|
|
57
|
+
level: "error",
|
|
58
|
+
event: "client.error",
|
|
59
|
+
component: "clients",
|
|
60
|
+
message: `${serviceLabel} request threw exception`,
|
|
61
|
+
meta: {
|
|
62
|
+
client: clientName,
|
|
63
|
+
method,
|
|
64
|
+
path,
|
|
65
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
66
|
+
...eventMeta,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
return (0, api_error_1.handleApiError)(err, serviceLabel, connectionName);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -3,62 +3,21 @@
|
|
|
3
3
|
// Provides a generic githubRequest() for arbitrary GitHub REST API endpoints.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
5
|
exports.githubRequest = githubRequest;
|
|
6
|
-
const
|
|
7
|
-
const runtime_1 = require("../nerves/runtime");
|
|
6
|
+
const api_client_1 = require("./api-client");
|
|
8
7
|
const GITHUB_BASE = "https://api.github.com";
|
|
9
8
|
// Generic GitHub API request. Returns response body as pretty-printed JSON string.
|
|
10
9
|
async function githubRequest(token, method, path, body) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"Content-Type": "application/json",
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
if (body)
|
|
28
|
-
opts.body = body;
|
|
29
|
-
const res = await fetch(url, opts);
|
|
30
|
-
if (!res.ok) {
|
|
31
|
-
(0, runtime_1.emitNervesEvent)({
|
|
32
|
-
level: "error",
|
|
33
|
-
event: "client.error",
|
|
34
|
-
component: "clients",
|
|
35
|
-
message: "GitHub request failed",
|
|
36
|
-
meta: { client: "github", method, path, status: res.status },
|
|
37
|
-
});
|
|
38
|
-
return (0, api_error_1.handleApiError)(res, "GitHub", "github");
|
|
39
|
-
}
|
|
40
|
-
const data = await res.json();
|
|
41
|
-
(0, runtime_1.emitNervesEvent)({
|
|
42
|
-
event: "client.request_end",
|
|
43
|
-
component: "clients",
|
|
44
|
-
message: "GitHub request completed",
|
|
45
|
-
meta: { client: "github", method, path, success: true },
|
|
46
|
-
});
|
|
47
|
-
return JSON.stringify(data, null, 2);
|
|
48
|
-
}
|
|
49
|
-
catch (err) {
|
|
50
|
-
(0, runtime_1.emitNervesEvent)({
|
|
51
|
-
level: "error",
|
|
52
|
-
event: "client.error",
|
|
53
|
-
component: "clients",
|
|
54
|
-
message: "GitHub request threw exception",
|
|
55
|
-
meta: {
|
|
56
|
-
client: "github",
|
|
57
|
-
method,
|
|
58
|
-
path,
|
|
59
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
return (0, api_error_1.handleApiError)(err, "GitHub", "github");
|
|
63
|
-
}
|
|
10
|
+
return (0, api_client_1.apiRequest)({
|
|
11
|
+
baseUrl: GITHUB_BASE,
|
|
12
|
+
method,
|
|
13
|
+
path,
|
|
14
|
+
token,
|
|
15
|
+
clientName: "github",
|
|
16
|
+
serviceLabel: "GitHub",
|
|
17
|
+
connectionName: "github",
|
|
18
|
+
body,
|
|
19
|
+
extraHeaders: {
|
|
20
|
+
Accept: "application/vnd.github+json",
|
|
21
|
+
},
|
|
22
|
+
});
|
|
64
23
|
}
|
|
@@ -7,61 +7,20 @@ exports.graphRequest = graphRequest;
|
|
|
7
7
|
exports.getProfile = getProfile;
|
|
8
8
|
const api_error_1 = require("../heart/api-error");
|
|
9
9
|
const runtime_1 = require("../nerves/runtime");
|
|
10
|
+
const api_client_1 = require("./api-client");
|
|
10
11
|
const GRAPH_BASE = "https://graph.microsoft.com/v1.0";
|
|
11
12
|
// Generic Graph API request. Returns response body as pretty-printed JSON string.
|
|
12
13
|
async function graphRequest(token, method, path, body) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
headers: {
|
|
24
|
-
Authorization: `Bearer ${token}`,
|
|
25
|
-
"Content-Type": "application/json",
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
if (body)
|
|
29
|
-
opts.body = body;
|
|
30
|
-
const res = await fetch(url, opts);
|
|
31
|
-
if (!res.ok) {
|
|
32
|
-
(0, runtime_1.emitNervesEvent)({
|
|
33
|
-
level: "error",
|
|
34
|
-
event: "client.error",
|
|
35
|
-
component: "clients",
|
|
36
|
-
message: "Graph request failed",
|
|
37
|
-
meta: { client: "graph", method, path, status: res.status },
|
|
38
|
-
});
|
|
39
|
-
return (0, api_error_1.handleApiError)(res, "Graph", "graph");
|
|
40
|
-
}
|
|
41
|
-
const data = await res.json();
|
|
42
|
-
(0, runtime_1.emitNervesEvent)({
|
|
43
|
-
event: "client.request_end",
|
|
44
|
-
component: "clients",
|
|
45
|
-
message: "Graph request completed",
|
|
46
|
-
meta: { client: "graph", method, path, success: true },
|
|
47
|
-
});
|
|
48
|
-
return JSON.stringify(data, null, 2);
|
|
49
|
-
}
|
|
50
|
-
catch (err) {
|
|
51
|
-
(0, runtime_1.emitNervesEvent)({
|
|
52
|
-
level: "error",
|
|
53
|
-
event: "client.error",
|
|
54
|
-
component: "clients",
|
|
55
|
-
message: "Graph request threw exception",
|
|
56
|
-
meta: {
|
|
57
|
-
client: "graph",
|
|
58
|
-
method,
|
|
59
|
-
path,
|
|
60
|
-
reason: err instanceof Error ? err.message : String(err),
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
return (0, api_error_1.handleApiError)(err, "Graph", "graph");
|
|
64
|
-
}
|
|
14
|
+
return (0, api_client_1.apiRequest)({
|
|
15
|
+
baseUrl: GRAPH_BASE,
|
|
16
|
+
method,
|
|
17
|
+
path,
|
|
18
|
+
token,
|
|
19
|
+
clientName: "graph",
|
|
20
|
+
serviceLabel: "Graph",
|
|
21
|
+
connectionName: "graph",
|
|
22
|
+
body,
|
|
23
|
+
});
|
|
65
24
|
}
|
|
66
25
|
// Backward-compatible thin wrapper: fetches /me and formats as human-readable text.
|
|
67
26
|
async function getProfile(token) {
|