@xdsjs/dossierx-daemon 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +611 -122
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { mkdir as
|
|
4
|
+
import { mkdir as mkdir7, stat as stat4 } from "fs/promises";
|
|
5
5
|
import os3 from "os";
|
|
6
|
-
import
|
|
6
|
+
import path10 from "path";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import { DOSSIERX_DEFAULT_WORKSPACE_PATH } from "@xdsjs/dossierx-workspace";
|
|
9
9
|
|
|
@@ -30,8 +30,8 @@ var ApiClient = class {
|
|
|
30
30
|
this.machineKey = options.machineKey;
|
|
31
31
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
32
32
|
}
|
|
33
|
-
async post(
|
|
34
|
-
const response = await this.fetchImpl(`${this.serverUrl}${
|
|
33
|
+
async post(path11, body, schema) {
|
|
34
|
+
const response = await this.fetchImpl(`${this.serverUrl}${path11}`, {
|
|
35
35
|
method: "POST",
|
|
36
36
|
headers: {
|
|
37
37
|
authorization: `Bearer ${this.machineKey}`,
|
|
@@ -495,9 +495,103 @@ function createCodexRunnerFromOptions(options, env = process.env) {
|
|
|
495
495
|
});
|
|
496
496
|
}
|
|
497
497
|
|
|
498
|
+
// src/git-mirror.ts
|
|
499
|
+
import path2 from "path";
|
|
500
|
+
import {
|
|
501
|
+
publishCompanySnapshot
|
|
502
|
+
} from "@xdsjs/dossierx-git-mirror";
|
|
503
|
+
|
|
504
|
+
// src/local-config.ts
|
|
505
|
+
import { chmod, mkdir, readFile, writeFile } from "fs/promises";
|
|
506
|
+
import os from "os";
|
|
507
|
+
import path from "path";
|
|
508
|
+
import { z } from "zod";
|
|
509
|
+
import { DOSSIERX_CONFIG_DIR } from "@xdsjs/dossierx-workspace";
|
|
510
|
+
var DaemonLocalConfigSchema = z.object({
|
|
511
|
+
machineId: z.string().uuid().optional(),
|
|
512
|
+
machineKey: z.string().startsWith("dx_machine_"),
|
|
513
|
+
serverUrl: z.string().min(1),
|
|
514
|
+
supabaseUrl: z.string().min(1),
|
|
515
|
+
supabaseAnonKey: z.string().min(1),
|
|
516
|
+
workspacePath: z.string().min(1),
|
|
517
|
+
investWikiMode: z.string().optional(),
|
|
518
|
+
investWikiLocalRepo: z.string().optional(),
|
|
519
|
+
investWikiCommand: z.string().optional(),
|
|
520
|
+
codexCommand: z.string().optional(),
|
|
521
|
+
codexModel: z.string().optional(),
|
|
522
|
+
codexSandbox: z.string().optional(),
|
|
523
|
+
gitMirrorRoot: z.string().optional(),
|
|
524
|
+
gitMirrorRemote: z.string().optional(),
|
|
525
|
+
gitMirrorBranch: z.string().optional(),
|
|
526
|
+
gitCommand: z.string().optional(),
|
|
527
|
+
localApiPort: z.number().int().positive().optional()
|
|
528
|
+
});
|
|
529
|
+
function expandHomePath(input) {
|
|
530
|
+
if (input === "~") {
|
|
531
|
+
return os.homedir();
|
|
532
|
+
}
|
|
533
|
+
if (input.startsWith("~/")) {
|
|
534
|
+
return path.join(os.homedir(), input.slice(2));
|
|
535
|
+
}
|
|
536
|
+
return input;
|
|
537
|
+
}
|
|
538
|
+
function getDaemonConfigDir(env = process.env) {
|
|
539
|
+
return path.resolve(
|
|
540
|
+
expandHomePath(env.DOSSIERX_CONFIG_DIR ?? DOSSIERX_CONFIG_DIR)
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
function daemonConfigPath(configDir = getDaemonConfigDir()) {
|
|
544
|
+
return path.join(configDir, "config.json");
|
|
545
|
+
}
|
|
546
|
+
async function readDaemonLocalConfig(configDir = getDaemonConfigDir()) {
|
|
547
|
+
try {
|
|
548
|
+
return DaemonLocalConfigSchema.parse(
|
|
549
|
+
JSON.parse(await readFile(daemonConfigPath(configDir), "utf8"))
|
|
550
|
+
);
|
|
551
|
+
} catch (error) {
|
|
552
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function writeDaemonLocalConfig(config, configDir = getDaemonConfigDir()) {
|
|
559
|
+
const parsed = DaemonLocalConfigSchema.parse(config);
|
|
560
|
+
await mkdir(configDir, { recursive: true, mode: 448 });
|
|
561
|
+
const target = daemonConfigPath(configDir);
|
|
562
|
+
await writeFile(target, `${JSON.stringify(parsed, null, 2)}
|
|
563
|
+
`, {
|
|
564
|
+
mode: 384
|
|
565
|
+
});
|
|
566
|
+
await chmod(target, 384);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/git-mirror.ts
|
|
570
|
+
function createGitMirrorFromOptions(options) {
|
|
571
|
+
if (!options.gitMirrorRoot && !options.gitMirrorRemote) {
|
|
572
|
+
return null;
|
|
573
|
+
}
|
|
574
|
+
const mirrorRoot = path2.resolve(
|
|
575
|
+
expandHomePath(
|
|
576
|
+
options.gitMirrorRoot ?? path2.join(getDaemonConfigDir(), "git-mirror")
|
|
577
|
+
)
|
|
578
|
+
);
|
|
579
|
+
return {
|
|
580
|
+
publishCompanySnapshot(input) {
|
|
581
|
+
return publishCompanySnapshot({
|
|
582
|
+
...input,
|
|
583
|
+
mirrorRoot,
|
|
584
|
+
remoteUrl: options.gitMirrorRemote,
|
|
585
|
+
branch: options.gitMirrorBranch,
|
|
586
|
+
gitCommand: options.gitCommand
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
|
|
498
592
|
// src/invest-wiki/runner.ts
|
|
499
593
|
import { access } from "fs/promises";
|
|
500
|
-
import
|
|
594
|
+
import path3 from "path";
|
|
501
595
|
import { execa as execa2 } from "execa";
|
|
502
596
|
|
|
503
597
|
// src/invest-wiki/config.ts
|
|
@@ -591,7 +685,7 @@ function createLocalRepoRunner(config) {
|
|
|
591
685
|
return {
|
|
592
686
|
kind: "local-repo",
|
|
593
687
|
async run(args, options) {
|
|
594
|
-
const cliPath =
|
|
688
|
+
const cliPath = path3.join(config.localRepo, "dist", "cli.js");
|
|
595
689
|
await assertFileExists(cliPath);
|
|
596
690
|
const result = await runInvestWikiCommand(
|
|
597
691
|
process.execPath,
|
|
@@ -638,73 +732,13 @@ function createInvestWikiRunnerFromOptions(options, env = process.env) {
|
|
|
638
732
|
return createInvestWikiRunner(resolveInvestWikiConfig(options, env));
|
|
639
733
|
}
|
|
640
734
|
|
|
641
|
-
// src/local-config.ts
|
|
642
|
-
import { chmod, mkdir, readFile, writeFile } from "fs/promises";
|
|
643
|
-
import os from "os";
|
|
644
|
-
import path2 from "path";
|
|
645
|
-
import { z } from "zod";
|
|
646
|
-
import { DOSSIERX_CONFIG_DIR } from "@xdsjs/dossierx-workspace";
|
|
647
|
-
var DaemonLocalConfigSchema = z.object({
|
|
648
|
-
machineId: z.string().uuid().optional(),
|
|
649
|
-
machineKey: z.string().startsWith("dx_machine_"),
|
|
650
|
-
serverUrl: z.string().min(1),
|
|
651
|
-
supabaseUrl: z.string().min(1),
|
|
652
|
-
supabaseAnonKey: z.string().min(1),
|
|
653
|
-
workspacePath: z.string().min(1),
|
|
654
|
-
investWikiMode: z.string().optional(),
|
|
655
|
-
investWikiLocalRepo: z.string().optional(),
|
|
656
|
-
investWikiCommand: z.string().optional(),
|
|
657
|
-
codexCommand: z.string().optional(),
|
|
658
|
-
codexModel: z.string().optional(),
|
|
659
|
-
codexSandbox: z.string().optional(),
|
|
660
|
-
localApiPort: z.number().int().positive().optional()
|
|
661
|
-
});
|
|
662
|
-
function expandHomePath(input) {
|
|
663
|
-
if (input === "~") {
|
|
664
|
-
return os.homedir();
|
|
665
|
-
}
|
|
666
|
-
if (input.startsWith("~/")) {
|
|
667
|
-
return path2.join(os.homedir(), input.slice(2));
|
|
668
|
-
}
|
|
669
|
-
return input;
|
|
670
|
-
}
|
|
671
|
-
function getDaemonConfigDir(env = process.env) {
|
|
672
|
-
return path2.resolve(
|
|
673
|
-
expandHomePath(env.DOSSIERX_CONFIG_DIR ?? DOSSIERX_CONFIG_DIR)
|
|
674
|
-
);
|
|
675
|
-
}
|
|
676
|
-
function daemonConfigPath(configDir = getDaemonConfigDir()) {
|
|
677
|
-
return path2.join(configDir, "config.json");
|
|
678
|
-
}
|
|
679
|
-
async function readDaemonLocalConfig(configDir = getDaemonConfigDir()) {
|
|
680
|
-
try {
|
|
681
|
-
return DaemonLocalConfigSchema.parse(
|
|
682
|
-
JSON.parse(await readFile(daemonConfigPath(configDir), "utf8"))
|
|
683
|
-
);
|
|
684
|
-
} catch (error) {
|
|
685
|
-
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
throw error;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
async function writeDaemonLocalConfig(config, configDir = getDaemonConfigDir()) {
|
|
692
|
-
const parsed = DaemonLocalConfigSchema.parse(config);
|
|
693
|
-
await mkdir(configDir, { recursive: true, mode: 448 });
|
|
694
|
-
const target = daemonConfigPath(configDir);
|
|
695
|
-
await writeFile(target, `${JSON.stringify(parsed, null, 2)}
|
|
696
|
-
`, {
|
|
697
|
-
mode: 384
|
|
698
|
-
});
|
|
699
|
-
await chmod(target, 384);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
735
|
// src/local-api/server.ts
|
|
703
736
|
import { createServer } from "http";
|
|
704
737
|
import { readFile as readFile3, realpath, stat } from "fs/promises";
|
|
705
|
-
import
|
|
738
|
+
import path5 from "path";
|
|
706
739
|
import { execa as execa3 } from "execa";
|
|
707
740
|
import {
|
|
741
|
+
readCoverageContractBundle,
|
|
708
742
|
resolveInsideWorkspace as resolveInsideWorkspace2,
|
|
709
743
|
scanCompanyManifest
|
|
710
744
|
} from "@xdsjs/dossierx-workspace";
|
|
@@ -712,7 +746,7 @@ import {
|
|
|
712
746
|
// src/task-archive.ts
|
|
713
747
|
import { randomUUID } from "crypto";
|
|
714
748
|
import { appendFile, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|
|
715
|
-
import
|
|
749
|
+
import path4 from "path";
|
|
716
750
|
import { resolveInsideWorkspace } from "@xdsjs/dossierx-workspace";
|
|
717
751
|
var TASK_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
718
752
|
function assertTaskId(taskId) {
|
|
@@ -724,7 +758,7 @@ function taskDirectory(workspaceRoot, taskId) {
|
|
|
724
758
|
assertTaskId(taskId);
|
|
725
759
|
return resolveInsideWorkspace(
|
|
726
760
|
workspaceRoot,
|
|
727
|
-
|
|
761
|
+
path4.posix.join(".dossierx", "tasks", taskId)
|
|
728
762
|
);
|
|
729
763
|
}
|
|
730
764
|
async function ensureTaskDirectory(workspaceRoot, taskId) {
|
|
@@ -757,7 +791,7 @@ function createTaskArchive(options) {
|
|
|
757
791
|
created_at: now().toISOString()
|
|
758
792
|
};
|
|
759
793
|
await appendFile(
|
|
760
|
-
|
|
794
|
+
path4.join(directory, "events.jsonl"),
|
|
761
795
|
`${JSON.stringify(archivedEvent)}
|
|
762
796
|
`,
|
|
763
797
|
"utf8"
|
|
@@ -766,13 +800,13 @@ function createTaskArchive(options) {
|
|
|
766
800
|
},
|
|
767
801
|
async appendRawOutput(taskId, stream, chunk) {
|
|
768
802
|
const directory = await ensureTaskDirectory(options.workspaceRoot, taskId);
|
|
769
|
-
await appendFile(
|
|
803
|
+
await appendFile(path4.join(directory, `${stream}.log`), chunk, "utf8");
|
|
770
804
|
},
|
|
771
805
|
async listEvents(taskId) {
|
|
772
806
|
const directory = taskDirectory(options.workspaceRoot, taskId);
|
|
773
807
|
let content = "";
|
|
774
808
|
try {
|
|
775
|
-
content = await readFile2(
|
|
809
|
+
content = await readFile2(path4.join(directory, "events.jsonl"), "utf8");
|
|
776
810
|
} catch (error) {
|
|
777
811
|
if (error.code === "ENOENT") {
|
|
778
812
|
return [];
|
|
@@ -786,7 +820,7 @@ function createTaskArchive(options) {
|
|
|
786
820
|
|
|
787
821
|
// src/version.ts
|
|
788
822
|
var DAEMON_PACKAGE_NAME = "@xdsjs/dossierx-daemon";
|
|
789
|
-
var DAEMON_VERSION = "0.1.
|
|
823
|
+
var DAEMON_VERSION = "0.1.4";
|
|
790
824
|
|
|
791
825
|
// src/local-api/server.ts
|
|
792
826
|
var DEFAULT_MAX_FILE_BYTES = 5 * 1024 * 1024;
|
|
@@ -813,7 +847,7 @@ function requestUrl(request) {
|
|
|
813
847
|
return new URL(request.url ?? "/", "http://127.0.0.1");
|
|
814
848
|
}
|
|
815
849
|
async function readWorkspaceFile(options) {
|
|
816
|
-
const extension =
|
|
850
|
+
const extension = path5.extname(options.relativePath).toLowerCase();
|
|
817
851
|
if (!ALLOWED_EXTENSIONS.has(extension)) {
|
|
818
852
|
throw new Error("Only markdown, text, and json files can be previewed");
|
|
819
853
|
}
|
|
@@ -865,7 +899,7 @@ function uniqueProbes(probes) {
|
|
|
865
899
|
}
|
|
866
900
|
async function resolveCommand(command) {
|
|
867
901
|
const normalizedCommand = command.trim();
|
|
868
|
-
if (
|
|
902
|
+
if (path5.isAbsolute(normalizedCommand)) {
|
|
869
903
|
return realpath(normalizedCommand).catch(() => normalizedCommand);
|
|
870
904
|
}
|
|
871
905
|
const result = await execa3("which", [normalizedCommand], {
|
|
@@ -1158,6 +1192,34 @@ async function startWorkspaceReadServer(options) {
|
|
|
1158
1192
|
}
|
|
1159
1193
|
return;
|
|
1160
1194
|
}
|
|
1195
|
+
if (request.method === "GET" && url.pathname === "/coverage/bundle") {
|
|
1196
|
+
const bundlePath = url.searchParams.get("path");
|
|
1197
|
+
if (!bundlePath) {
|
|
1198
|
+
json(response, 400, { error: "Missing coverage bundle path" }, origin);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
try {
|
|
1202
|
+
json(
|
|
1203
|
+
response,
|
|
1204
|
+
200,
|
|
1205
|
+
await readCoverageContractBundle({
|
|
1206
|
+
workspaceRoot: options.workspaceRoot,
|
|
1207
|
+
bundlePath
|
|
1208
|
+
}),
|
|
1209
|
+
origin
|
|
1210
|
+
);
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
json(
|
|
1213
|
+
response,
|
|
1214
|
+
400,
|
|
1215
|
+
{
|
|
1216
|
+
error: error instanceof Error ? error.message : "Coverage bundle read failed"
|
|
1217
|
+
},
|
|
1218
|
+
origin
|
|
1219
|
+
);
|
|
1220
|
+
}
|
|
1221
|
+
return;
|
|
1222
|
+
}
|
|
1161
1223
|
if (request.method !== "GET" || url.pathname !== "/workspace/read") {
|
|
1162
1224
|
json(response, 404, { error: "Not found" }, origin);
|
|
1163
1225
|
return;
|
|
@@ -1213,6 +1275,7 @@ import {
|
|
|
1213
1275
|
TaskEventInputSchema,
|
|
1214
1276
|
TaskSchema
|
|
1215
1277
|
} from "@xdsjs/dossierx-shared";
|
|
1278
|
+
import { GitMirrorError } from "@xdsjs/dossierx-git-mirror";
|
|
1216
1279
|
|
|
1217
1280
|
// src/errors.ts
|
|
1218
1281
|
function failTaskError(error, code = "UNKNOWN", step) {
|
|
@@ -1426,20 +1489,364 @@ async function runCodexTask(task, context) {
|
|
|
1426
1489
|
});
|
|
1427
1490
|
return {
|
|
1428
1491
|
generatedFiles: [],
|
|
1492
|
+
blockingReasons: [],
|
|
1429
1493
|
manifestPath: companyManifestPath(ticker),
|
|
1430
1494
|
manifest
|
|
1431
1495
|
};
|
|
1432
1496
|
}
|
|
1433
1497
|
|
|
1434
|
-
// src/executors/
|
|
1435
|
-
import {
|
|
1436
|
-
import
|
|
1498
|
+
// src/executors/financialReports.ts
|
|
1499
|
+
import { createHash } from "crypto";
|
|
1500
|
+
import { mkdir as mkdir3, readFile as readFile4, writeFile as writeFile2 } from "fs/promises";
|
|
1501
|
+
import path6 from "path";
|
|
1502
|
+
import {
|
|
1503
|
+
FinancialReportManifestSchema,
|
|
1504
|
+
assessReportCoverage,
|
|
1505
|
+
downloadCninfoReports,
|
|
1506
|
+
downloadSecReports,
|
|
1507
|
+
primaryMarket,
|
|
1508
|
+
primaryTicker,
|
|
1509
|
+
runNotebookLmFinancialReportsIngest
|
|
1510
|
+
} from "@xdsjs/dossier-financial-reports";
|
|
1437
1511
|
import {
|
|
1438
1512
|
buildCompanyManifest as buildCompanyManifest2,
|
|
1439
1513
|
companyManifestPath as companyManifestPath2,
|
|
1514
|
+
financialReportsIndexPath,
|
|
1515
|
+
resolveInsideWorkspace as resolveInsideWorkspace4
|
|
1516
|
+
} from "@xdsjs/dossierx-workspace";
|
|
1517
|
+
var FinancialReportsRuntimeError = class extends Error {
|
|
1518
|
+
code;
|
|
1519
|
+
step;
|
|
1520
|
+
constructor(code, message, step) {
|
|
1521
|
+
super(message);
|
|
1522
|
+
this.name = "FinancialReportsRuntimeError";
|
|
1523
|
+
this.code = code;
|
|
1524
|
+
this.step = step;
|
|
1525
|
+
}
|
|
1526
|
+
};
|
|
1527
|
+
function defaultFetch() {
|
|
1528
|
+
return async (url, init) => fetch(url, init);
|
|
1529
|
+
}
|
|
1530
|
+
function defaultRenderHtmlToPdf() {
|
|
1531
|
+
return async () => {
|
|
1532
|
+
throw new FinancialReportsRuntimeError(
|
|
1533
|
+
"RUNTIME_NOT_FOUND",
|
|
1534
|
+
"SEC HTML-to-PDF renderer is not configured",
|
|
1535
|
+
"financial_reports.render_html_to_pdf"
|
|
1536
|
+
);
|
|
1537
|
+
};
|
|
1538
|
+
}
|
|
1539
|
+
function requireNotebookLmClient(context) {
|
|
1540
|
+
const client = context.financialReports?.notebookLmClient;
|
|
1541
|
+
if (!client) {
|
|
1542
|
+
throw new FinancialReportsRuntimeError(
|
|
1543
|
+
"RUNTIME_NOT_FOUND",
|
|
1544
|
+
"NotebookLM client is not configured",
|
|
1545
|
+
"financial_reports.notebooklm.resolve"
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
return client;
|
|
1549
|
+
}
|
|
1550
|
+
function marketToAppMarket(market) {
|
|
1551
|
+
if (market === "US") {
|
|
1552
|
+
return "us";
|
|
1553
|
+
}
|
|
1554
|
+
if (market === "H") {
|
|
1555
|
+
return "hk";
|
|
1556
|
+
}
|
|
1557
|
+
return "cn";
|
|
1558
|
+
}
|
|
1559
|
+
function sha256Json(value) {
|
|
1560
|
+
return `sha256:${createHash("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
1561
|
+
}
|
|
1562
|
+
function writeWorkspaceJson(context, relativePath, value) {
|
|
1563
|
+
return writeWorkspaceBytes(
|
|
1564
|
+
context,
|
|
1565
|
+
relativePath,
|
|
1566
|
+
new TextEncoder().encode(`${JSON.stringify(value, null, 2)}
|
|
1567
|
+
`)
|
|
1568
|
+
);
|
|
1569
|
+
}
|
|
1570
|
+
async function writeWorkspaceBytes(context, relativePath, bytes) {
|
|
1571
|
+
const absolutePath = resolveInsideWorkspace4(context.workspaceRoot, relativePath);
|
|
1572
|
+
await mkdir3(path6.dirname(absolutePath), { recursive: true });
|
|
1573
|
+
if (!context.dryRun) {
|
|
1574
|
+
await writeFile2(absolutePath, bytes);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
function createWorkspaceStorage(context) {
|
|
1578
|
+
return {
|
|
1579
|
+
async writeFile(relativePath, bytes) {
|
|
1580
|
+
await writeWorkspaceBytes(context, relativePath, bytes);
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
}
|
|
1584
|
+
function readWorkspaceJson(context, relativePath) {
|
|
1585
|
+
const absolutePath = resolveInsideWorkspace4(context.workspaceRoot, relativePath);
|
|
1586
|
+
return readFile4(absolutePath, "utf8").then((content) => JSON.parse(content));
|
|
1587
|
+
}
|
|
1588
|
+
function parseReportManifest(payload) {
|
|
1589
|
+
const maybeRecord = typeof payload === "object" && payload !== null ? payload : {};
|
|
1590
|
+
if (maybeRecord.schemaVersion === "financial-reports/manifest/v1") {
|
|
1591
|
+
return FinancialReportManifestSchema.parse(payload);
|
|
1592
|
+
}
|
|
1593
|
+
return FinancialReportManifestSchema.parse(maybeRecord.manifest);
|
|
1594
|
+
}
|
|
1595
|
+
async function readReportManifest(context, manifestPath) {
|
|
1596
|
+
try {
|
|
1597
|
+
return parseReportManifest(await readWorkspaceJson(context, manifestPath));
|
|
1598
|
+
} catch (error) {
|
|
1599
|
+
if (error instanceof SyntaxError) {
|
|
1600
|
+
throw new FinancialReportsRuntimeError(
|
|
1601
|
+
"VALIDATION_ERROR",
|
|
1602
|
+
"Financial reports manifest is not valid JSON",
|
|
1603
|
+
"financial_reports.manifest.read"
|
|
1604
|
+
);
|
|
1605
|
+
}
|
|
1606
|
+
throw error;
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
function assertManifestIssuerMatchesTaskIssuer(manifestIssuer, taskIssuer) {
|
|
1610
|
+
const mismatches = [];
|
|
1611
|
+
if (manifestIssuer.issuerId !== taskIssuer.issuerId) {
|
|
1612
|
+
mismatches.push("issuerId");
|
|
1613
|
+
}
|
|
1614
|
+
if (manifestIssuer.companyId !== taskIssuer.companyId) {
|
|
1615
|
+
mismatches.push("companyId");
|
|
1616
|
+
}
|
|
1617
|
+
if (primaryTicker(manifestIssuer) !== primaryTicker(taskIssuer)) {
|
|
1618
|
+
mismatches.push("primary ticker");
|
|
1619
|
+
}
|
|
1620
|
+
if (primaryMarket(manifestIssuer) !== primaryMarket(taskIssuer)) {
|
|
1621
|
+
mismatches.push("primary market");
|
|
1622
|
+
}
|
|
1623
|
+
if (mismatches.length > 0) {
|
|
1624
|
+
throw new FinancialReportsRuntimeError(
|
|
1625
|
+
"VALIDATION_ERROR",
|
|
1626
|
+
`Financial reports manifest issuer does not match task payload issuer: ${mismatches.join(", ")}`,
|
|
1627
|
+
"financial_reports.manifest.validate"
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
function financialTaskResult(input) {
|
|
1632
|
+
const ticker = primaryTicker(input.manifest.issuer);
|
|
1633
|
+
const coverage = assessReportCoverage(input.manifest);
|
|
1634
|
+
return {
|
|
1635
|
+
schemaVersion: "financial-reports/task-result/v1",
|
|
1636
|
+
issuerId: input.manifest.issuer.issuerId,
|
|
1637
|
+
companyId: input.manifest.issuer.companyId,
|
|
1638
|
+
ticker,
|
|
1639
|
+
manifestPath: input.manifestPath,
|
|
1640
|
+
manifestHash: input.manifestHash,
|
|
1641
|
+
notebookPath: input.notebookPath,
|
|
1642
|
+
factsBundlePath: input.factsBundlePath,
|
|
1643
|
+
reportCount: input.manifest.reports.length,
|
|
1644
|
+
coverageStatus: coverage.status,
|
|
1645
|
+
blockingReasons: coverage.knownGaps
|
|
1646
|
+
};
|
|
1647
|
+
}
|
|
1648
|
+
async function runSyncReports(task, context) {
|
|
1649
|
+
const issuer = task.payload.issuer;
|
|
1650
|
+
const market = primaryMarket(issuer);
|
|
1651
|
+
const fetch2 = context.financialReports?.fetch ?? defaultFetch();
|
|
1652
|
+
const storage = createWorkspaceStorage(context);
|
|
1653
|
+
const now = context.financialReports?.now;
|
|
1654
|
+
let result;
|
|
1655
|
+
await context.appendEvent({
|
|
1656
|
+
level: "info",
|
|
1657
|
+
message: "Starting financial reports sync",
|
|
1658
|
+
data: { issuerId: issuer.issuerId, market }
|
|
1659
|
+
});
|
|
1660
|
+
if (market === "US") {
|
|
1661
|
+
result = await downloadSecReports({
|
|
1662
|
+
issuer,
|
|
1663
|
+
fiscalYears: task.payload.fiscalYears,
|
|
1664
|
+
reportTypes: task.payload.reportTypes,
|
|
1665
|
+
fetch: fetch2,
|
|
1666
|
+
storage,
|
|
1667
|
+
renderHtmlToPdf: context.financialReports?.renderHtmlToPdf ?? defaultRenderHtmlToPdf(),
|
|
1668
|
+
now
|
|
1669
|
+
});
|
|
1670
|
+
} else {
|
|
1671
|
+
result = await downloadCninfoReports({
|
|
1672
|
+
issuer,
|
|
1673
|
+
fiscalYears: task.payload.fiscalYears,
|
|
1674
|
+
reportTypes: task.payload.reportTypes,
|
|
1675
|
+
fetch: fetch2,
|
|
1676
|
+
storage,
|
|
1677
|
+
now
|
|
1678
|
+
});
|
|
1679
|
+
}
|
|
1680
|
+
const ticker = primaryTicker(result.manifest.issuer);
|
|
1681
|
+
const reportIndexPath = financialReportsIndexPath(ticker);
|
|
1682
|
+
const manifestHash = sha256Json(result.manifest);
|
|
1683
|
+
const reportIndex = {
|
|
1684
|
+
schemaVersion: "financial-reports/report-index/v1",
|
|
1685
|
+
generatedAt: result.manifest.generatedAt,
|
|
1686
|
+
issuer: result.manifest.issuer,
|
|
1687
|
+
manifestHash,
|
|
1688
|
+
manifest: result.manifest,
|
|
1689
|
+
coverage: result.coverage,
|
|
1690
|
+
skippedReports: result.skippedReports,
|
|
1691
|
+
missingReports: result.missingReports
|
|
1692
|
+
};
|
|
1693
|
+
await writeWorkspaceJson(context, reportIndexPath, reportIndex);
|
|
1694
|
+
const companyManifest = context.dryRun ? void 0 : await buildCompanyManifest2({
|
|
1695
|
+
workspaceRoot: context.workspaceRoot,
|
|
1696
|
+
ticker,
|
|
1697
|
+
market: marketToAppMarket(market)
|
|
1698
|
+
});
|
|
1699
|
+
await context.appendEvent({
|
|
1700
|
+
level: "info",
|
|
1701
|
+
message: "Financial reports sync completed",
|
|
1702
|
+
data: {
|
|
1703
|
+
issuerId: issuer.issuerId,
|
|
1704
|
+
coverageStatus: result.coverage.status,
|
|
1705
|
+
reportCount: result.manifest.reports.length,
|
|
1706
|
+
manifestPath: reportIndexPath
|
|
1707
|
+
}
|
|
1708
|
+
});
|
|
1709
|
+
return {
|
|
1710
|
+
generatedFiles: [
|
|
1711
|
+
...result.manifest.reports.map((report) => report.localPath),
|
|
1712
|
+
reportIndexPath,
|
|
1713
|
+
companyManifestPath2(ticker)
|
|
1714
|
+
],
|
|
1715
|
+
manifestPath: companyManifestPath2(ticker),
|
|
1716
|
+
manifest: companyManifest,
|
|
1717
|
+
blockingReasons: [],
|
|
1718
|
+
financialReports: financialTaskResult({
|
|
1719
|
+
manifest: result.manifest,
|
|
1720
|
+
manifestPath: reportIndexPath,
|
|
1721
|
+
manifestHash
|
|
1722
|
+
})
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
async function runNotebookIngest(task, context) {
|
|
1726
|
+
const client = requireNotebookLmClient(context);
|
|
1727
|
+
const manifest = await readReportManifest(context, task.payload.manifestPath);
|
|
1728
|
+
assertManifestIssuerMatchesTaskIssuer(manifest.issuer, task.payload.issuer);
|
|
1729
|
+
const ticker = primaryTicker(manifest.issuer);
|
|
1730
|
+
const manifestHash = sha256Json(manifest);
|
|
1731
|
+
const factsAsOf = task.payload.factsAsOf ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1732
|
+
await context.appendEvent({
|
|
1733
|
+
level: "info",
|
|
1734
|
+
message: "Starting NotebookLM financial reports ingest",
|
|
1735
|
+
data: { issuerId: manifest.issuer.issuerId, manifestPath: task.payload.manifestPath }
|
|
1736
|
+
});
|
|
1737
|
+
const ingest = await runNotebookLmFinancialReportsIngest({
|
|
1738
|
+
manifest,
|
|
1739
|
+
client,
|
|
1740
|
+
manifestHash,
|
|
1741
|
+
factsAsOf,
|
|
1742
|
+
generatedAt: context.financialReports?.now?.(),
|
|
1743
|
+
notebookTitle: task.payload.notebookTitle
|
|
1744
|
+
});
|
|
1745
|
+
const notebookPath = `companies/${ticker}/financial-reports/notebooklm/notebook.json`;
|
|
1746
|
+
const factsBundlePath = `companies/${ticker}/financial-reports/ingest/facts-bundle.json`;
|
|
1747
|
+
await writeWorkspaceJson(context, notebookPath, {
|
|
1748
|
+
schemaVersion: "financial-reports/notebooklm-run/v1",
|
|
1749
|
+
generatedAt: ingest.factsBundle.generatedAt,
|
|
1750
|
+
manifestPath: task.payload.manifestPath,
|
|
1751
|
+
notebooklm: ingest.notebooklm,
|
|
1752
|
+
sourceIds: ingest.sourceIds
|
|
1753
|
+
});
|
|
1754
|
+
await writeWorkspaceJson(context, factsBundlePath, ingest.factsBundle);
|
|
1755
|
+
const companyManifest = context.dryRun ? void 0 : await buildCompanyManifest2({
|
|
1756
|
+
workspaceRoot: context.workspaceRoot,
|
|
1757
|
+
ticker,
|
|
1758
|
+
market: marketToAppMarket(primaryMarket(manifest.issuer))
|
|
1759
|
+
});
|
|
1760
|
+
await context.appendEvent({
|
|
1761
|
+
level: "info",
|
|
1762
|
+
message: "NotebookLM financial reports ingest completed",
|
|
1763
|
+
data: {
|
|
1764
|
+
issuerId: manifest.issuer.issuerId,
|
|
1765
|
+
notebookPath,
|
|
1766
|
+
factsBundlePath
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
return {
|
|
1770
|
+
generatedFiles: [notebookPath, factsBundlePath, companyManifestPath2(ticker)],
|
|
1771
|
+
manifestPath: companyManifestPath2(ticker),
|
|
1772
|
+
manifest: companyManifest,
|
|
1773
|
+
blockingReasons: [],
|
|
1774
|
+
financialReports: financialTaskResult({
|
|
1775
|
+
manifest,
|
|
1776
|
+
manifestPath: task.payload.manifestPath,
|
|
1777
|
+
manifestHash,
|
|
1778
|
+
notebookPath,
|
|
1779
|
+
factsBundlePath
|
|
1780
|
+
})
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
async function runRefreshCompany(task, context) {
|
|
1784
|
+
const syncResult = await runSyncReports(
|
|
1785
|
+
{
|
|
1786
|
+
id: task.id,
|
|
1787
|
+
type: "financial_reports.sync_reports",
|
|
1788
|
+
payload: {
|
|
1789
|
+
issuer: task.payload.issuer,
|
|
1790
|
+
fiscalYears: task.payload.fiscalYears,
|
|
1791
|
+
reportTypes: task.payload.reportTypes
|
|
1792
|
+
}
|
|
1793
|
+
},
|
|
1794
|
+
context
|
|
1795
|
+
);
|
|
1796
|
+
if (!context.financialReports?.notebookLmClient) {
|
|
1797
|
+
return syncResult;
|
|
1798
|
+
}
|
|
1799
|
+
const manifestPath = syncResult.financialReports?.manifestPath;
|
|
1800
|
+
if (!manifestPath) {
|
|
1801
|
+
return syncResult;
|
|
1802
|
+
}
|
|
1803
|
+
const ingestResult = await runNotebookIngest(
|
|
1804
|
+
{
|
|
1805
|
+
id: task.id,
|
|
1806
|
+
type: "financial_reports.ingest_notebook",
|
|
1807
|
+
payload: {
|
|
1808
|
+
issuer: task.payload.issuer,
|
|
1809
|
+
manifestPath,
|
|
1810
|
+
factsAsOf: task.payload.factsAsOf,
|
|
1811
|
+
notebookTitle: task.payload.notebookTitle
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
context
|
|
1815
|
+
);
|
|
1816
|
+
return {
|
|
1817
|
+
...ingestResult,
|
|
1818
|
+
generatedFiles: [
|
|
1819
|
+
...syncResult.generatedFiles,
|
|
1820
|
+
...ingestResult.generatedFiles
|
|
1821
|
+
]
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
async function runFinancialReportsTask(task, context) {
|
|
1825
|
+
if (task.type === "financial_reports.sync_reports") {
|
|
1826
|
+
return runSyncReports(task, context);
|
|
1827
|
+
}
|
|
1828
|
+
if (task.type === "financial_reports.ingest_notebook" || task.type === "financial_reports.extract_facts") {
|
|
1829
|
+
return runNotebookIngest(task, context);
|
|
1830
|
+
}
|
|
1831
|
+
if (task.type === "financial_reports.refresh_company") {
|
|
1832
|
+
return runRefreshCompany(task, context);
|
|
1833
|
+
}
|
|
1834
|
+
throw new FinancialReportsRuntimeError(
|
|
1835
|
+
"VALIDATION_ERROR",
|
|
1836
|
+
"Unsupported financial reports task type",
|
|
1837
|
+
"financial_reports.dispatch"
|
|
1838
|
+
);
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// src/executors/investWiki.ts
|
|
1842
|
+
import { access as access2, mkdir as mkdir4, readFile as readFile5, stat as stat3, writeFile as writeFile3 } from "fs/promises";
|
|
1843
|
+
import path7 from "path";
|
|
1844
|
+
import {
|
|
1845
|
+
buildCompanyManifest as buildCompanyManifest3,
|
|
1846
|
+
companyManifestPath as companyManifestPath3,
|
|
1440
1847
|
investWikiConfigPath,
|
|
1441
1848
|
investWikiRoot as investWikiRoot2,
|
|
1442
|
-
resolveInsideWorkspace as
|
|
1849
|
+
resolveInsideWorkspace as resolveInsideWorkspace5
|
|
1443
1850
|
} from "@xdsjs/dossierx-workspace";
|
|
1444
1851
|
async function exists(absolutePath) {
|
|
1445
1852
|
try {
|
|
@@ -1464,7 +1871,7 @@ function isWorkspaceGuardError2(error) {
|
|
|
1464
1871
|
}
|
|
1465
1872
|
function workspacePath2(workspaceRoot, relativePath, step) {
|
|
1466
1873
|
try {
|
|
1467
|
-
return
|
|
1874
|
+
return resolveInsideWorkspace5(workspaceRoot, relativePath);
|
|
1468
1875
|
} catch (error) {
|
|
1469
1876
|
if (isWorkspaceGuardError2(error)) {
|
|
1470
1877
|
throw new InvestWikiRuntimeError(
|
|
@@ -1545,11 +1952,11 @@ function isMarket(value) {
|
|
|
1545
1952
|
async function readManifestMarket(workspaceRoot, ticker) {
|
|
1546
1953
|
const manifestPath = workspacePath2(
|
|
1547
1954
|
workspaceRoot,
|
|
1548
|
-
|
|
1955
|
+
companyManifestPath3(ticker),
|
|
1549
1956
|
"invest_wiki.sync"
|
|
1550
1957
|
);
|
|
1551
1958
|
try {
|
|
1552
|
-
const manifest = JSON.parse(await
|
|
1959
|
+
const manifest = JSON.parse(await readFile5(manifestPath, "utf8"));
|
|
1553
1960
|
if (!isMarket(manifest.market)) {
|
|
1554
1961
|
throw new InvestWikiRuntimeError(
|
|
1555
1962
|
"VALIDATION_ERROR",
|
|
@@ -1592,7 +1999,7 @@ async function runInitCompanyVault(task, context) {
|
|
|
1592
1999
|
`${vaultRelativePath}/.llm-wiki-invest/dossier-state.json`,
|
|
1593
2000
|
"invest_wiki.init_company_vault"
|
|
1594
2001
|
);
|
|
1595
|
-
await
|
|
2002
|
+
await mkdir4(vaultRoot, { recursive: true });
|
|
1596
2003
|
await context.appendEvent({
|
|
1597
2004
|
level: "info",
|
|
1598
2005
|
message: "Initializing invest-wiki vault",
|
|
@@ -1611,10 +2018,10 @@ async function runInitCompanyVault(task, context) {
|
|
|
1611
2018
|
const statusOutput = await runner.run(["dossier", "status"], { cwd: vaultRoot });
|
|
1612
2019
|
await appendOutputEvent2(context, "Invest wiki dossier status completed", statusOutput);
|
|
1613
2020
|
if (!await exists(markerPath)) {
|
|
1614
|
-
await
|
|
1615
|
-
await
|
|
2021
|
+
await mkdir4(path7.dirname(markerPath), { recursive: true });
|
|
2022
|
+
await writeFile3(markerPath, "# Created by dossierx-daemon\n");
|
|
1616
2023
|
}
|
|
1617
|
-
const manifest = await
|
|
2024
|
+
const manifest = await buildCompanyManifest3({
|
|
1618
2025
|
workspaceRoot: context.workspaceRoot,
|
|
1619
2026
|
ticker,
|
|
1620
2027
|
market: task.payload.market
|
|
@@ -1628,9 +2035,10 @@ async function runInitCompanyVault(task, context) {
|
|
|
1628
2035
|
generatedFiles: [
|
|
1629
2036
|
vaultRelativePath,
|
|
1630
2037
|
markerRelativePath,
|
|
1631
|
-
|
|
2038
|
+
companyManifestPath3(ticker)
|
|
1632
2039
|
],
|
|
1633
|
-
|
|
2040
|
+
blockingReasons: [],
|
|
2041
|
+
manifestPath: companyManifestPath3(ticker),
|
|
1634
2042
|
manifest
|
|
1635
2043
|
};
|
|
1636
2044
|
}
|
|
@@ -1654,7 +2062,8 @@ async function runStatus(task, context) {
|
|
|
1654
2062
|
);
|
|
1655
2063
|
return {
|
|
1656
2064
|
generatedFiles: [],
|
|
1657
|
-
|
|
2065
|
+
blockingReasons: [],
|
|
2066
|
+
manifestPath: companyManifestPath3(ticker)
|
|
1658
2067
|
};
|
|
1659
2068
|
}
|
|
1660
2069
|
async function runSync(task, context) {
|
|
@@ -1669,14 +2078,15 @@ async function runSync(task, context) {
|
|
|
1669
2078
|
const args = task.payload.dryRun ? ["sync", "--dry-run"] : ["sync"];
|
|
1670
2079
|
const output = await runner.run(args, { cwd: vaultRoot });
|
|
1671
2080
|
await appendOutputEvent2(context, "Invest wiki sync completed", output);
|
|
1672
|
-
const manifest = await
|
|
2081
|
+
const manifest = await buildCompanyManifest3({
|
|
1673
2082
|
workspaceRoot: context.workspaceRoot,
|
|
1674
2083
|
ticker,
|
|
1675
2084
|
market
|
|
1676
2085
|
});
|
|
1677
2086
|
return {
|
|
1678
2087
|
generatedFiles: [],
|
|
1679
|
-
|
|
2088
|
+
blockingReasons: [],
|
|
2089
|
+
manifestPath: companyManifestPath3(ticker),
|
|
1680
2090
|
manifest
|
|
1681
2091
|
};
|
|
1682
2092
|
}
|
|
@@ -1698,13 +2108,13 @@ async function runInvestWikiTask(task, context) {
|
|
|
1698
2108
|
}
|
|
1699
2109
|
|
|
1700
2110
|
// src/executors/mockWriteCompanyReport.ts
|
|
1701
|
-
import { mkdir as
|
|
1702
|
-
import
|
|
2111
|
+
import { mkdir as mkdir5, writeFile as writeFile4 } from "fs/promises";
|
|
2112
|
+
import path8 from "path";
|
|
1703
2113
|
import {
|
|
1704
|
-
buildCompanyManifest as
|
|
1705
|
-
companyManifestPath as
|
|
2114
|
+
buildCompanyManifest as buildCompanyManifest4,
|
|
2115
|
+
companyManifestPath as companyManifestPath4,
|
|
1706
2116
|
companyRoot,
|
|
1707
|
-
resolveInsideWorkspace as
|
|
2117
|
+
resolveInsideWorkspace as resolveInsideWorkspace6,
|
|
1708
2118
|
rightBusinessPath,
|
|
1709
2119
|
rightPeoplePath,
|
|
1710
2120
|
rightPricePath
|
|
@@ -1738,10 +2148,10 @@ var rightPrice = (ticker) => `# Right Price - ${ticker}
|
|
|
1738
2148
|
This is a placeholder right-price analysis for ${ticker}.
|
|
1739
2149
|
`;
|
|
1740
2150
|
async function writeWorkspaceFile(context, relativePath, content) {
|
|
1741
|
-
const absolutePath =
|
|
1742
|
-
await
|
|
2151
|
+
const absolutePath = resolveInsideWorkspace6(context.workspaceRoot, relativePath);
|
|
2152
|
+
await mkdir5(path8.dirname(absolutePath), { recursive: true });
|
|
1743
2153
|
if (!context.dryRun) {
|
|
1744
|
-
await
|
|
2154
|
+
await writeFile4(absolutePath, content);
|
|
1745
2155
|
}
|
|
1746
2156
|
}
|
|
1747
2157
|
async function runMockWriteCompanyReport(task, context) {
|
|
@@ -1756,7 +2166,7 @@ async function runMockWriteCompanyReport(task, context) {
|
|
|
1756
2166
|
message: "Creating company directory",
|
|
1757
2167
|
data: { ticker }
|
|
1758
2168
|
});
|
|
1759
|
-
await
|
|
2169
|
+
await mkdir5(resolveInsideWorkspace6(context.workspaceRoot, companyRoot(ticker)), {
|
|
1760
2170
|
recursive: true
|
|
1761
2171
|
});
|
|
1762
2172
|
await context.appendEvent({
|
|
@@ -1780,11 +2190,11 @@ async function runMockWriteCompanyReport(task, context) {
|
|
|
1780
2190
|
await context.appendEvent({
|
|
1781
2191
|
level: "info",
|
|
1782
2192
|
message: "Generating manifest.json",
|
|
1783
|
-
data: { path:
|
|
2193
|
+
data: { path: companyManifestPath4(ticker) }
|
|
1784
2194
|
});
|
|
1785
2195
|
let manifest;
|
|
1786
2196
|
if (!context.dryRun) {
|
|
1787
|
-
manifest = await
|
|
2197
|
+
manifest = await buildCompanyManifest4({
|
|
1788
2198
|
workspaceRoot: context.workspaceRoot,
|
|
1789
2199
|
ticker,
|
|
1790
2200
|
market: task.payload.market
|
|
@@ -1796,8 +2206,9 @@ async function runMockWriteCompanyReport(task, context) {
|
|
|
1796
2206
|
data: { ticker }
|
|
1797
2207
|
});
|
|
1798
2208
|
return {
|
|
1799
|
-
generatedFiles: [...generatedFiles,
|
|
1800
|
-
|
|
2209
|
+
generatedFiles: [...generatedFiles, companyManifestPath4(ticker)],
|
|
2210
|
+
blockingReasons: [],
|
|
2211
|
+
manifestPath: companyManifestPath4(ticker),
|
|
1801
2212
|
manifest
|
|
1802
2213
|
};
|
|
1803
2214
|
}
|
|
@@ -1810,6 +2221,9 @@ function getExecutor(task) {
|
|
|
1810
2221
|
if (task.type.startsWith("invest_wiki.")) {
|
|
1811
2222
|
return runInvestWikiTask;
|
|
1812
2223
|
}
|
|
2224
|
+
if (task.type.startsWith("financial_reports.")) {
|
|
2225
|
+
return runFinancialReportsTask;
|
|
2226
|
+
}
|
|
1813
2227
|
if (task.type === "mock.write_company_report") {
|
|
1814
2228
|
return runMockWriteCompanyReport;
|
|
1815
2229
|
}
|
|
@@ -1873,11 +2287,12 @@ async function runTask(ctx, task, agent) {
|
|
|
1873
2287
|
}
|
|
1874
2288
|
await appendEvent(ctx, task.id, "Task claimed");
|
|
1875
2289
|
try {
|
|
1876
|
-
|
|
2290
|
+
let result = await executor(task, {
|
|
1877
2291
|
workspaceRoot: ctx.workspaceRoot,
|
|
1878
2292
|
dryRun: ctx.dryRun,
|
|
1879
2293
|
codex: codexRunnerForTask(ctx, agent),
|
|
1880
2294
|
investWiki: ctx.investWiki,
|
|
2295
|
+
financialReports: ctx.financialReports,
|
|
1881
2296
|
appendEvent: async (event) => {
|
|
1882
2297
|
await appendLocalEvent(ctx, task.id, TaskEventInputSchema.parse(event));
|
|
1883
2298
|
},
|
|
@@ -1885,6 +2300,31 @@ async function runTask(ctx, task, agent) {
|
|
|
1885
2300
|
await appendLocalRawOutput(ctx, task.id, stream, chunk);
|
|
1886
2301
|
}
|
|
1887
2302
|
});
|
|
2303
|
+
if (ctx.gitMirror && result.manifest) {
|
|
2304
|
+
const gitSnapshot = await ctx.gitMirror.publishCompanySnapshot({
|
|
2305
|
+
workspaceRoot: ctx.workspaceRoot,
|
|
2306
|
+
company: {
|
|
2307
|
+
ticker: result.manifest.ticker,
|
|
2308
|
+
market: result.manifest.market
|
|
2309
|
+
},
|
|
2310
|
+
taskId: task.id
|
|
2311
|
+
});
|
|
2312
|
+
await appendLocalEvent(ctx, task.id, {
|
|
2313
|
+
level: "info",
|
|
2314
|
+
message: gitSnapshot.status === "published" ? "Git mirror snapshot published" : "Git mirror snapshot unchanged",
|
|
2315
|
+
data: {
|
|
2316
|
+
commitSha: gitSnapshot.commitSha,
|
|
2317
|
+
treeHash: gitSnapshot.treeHash,
|
|
2318
|
+
branch: gitSnapshot.branch,
|
|
2319
|
+
pushed: gitSnapshot.pushed
|
|
2320
|
+
}
|
|
2321
|
+
});
|
|
2322
|
+
result = {
|
|
2323
|
+
...result,
|
|
2324
|
+
commitSha: gitSnapshot.commitSha ?? result.commitSha,
|
|
2325
|
+
gitSnapshot
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
1888
2328
|
await appendEvent(ctx, task.id, "Task completed");
|
|
1889
2329
|
await ctx.api.completeTask(task.id, { result });
|
|
1890
2330
|
} catch (error) {
|
|
@@ -1920,6 +2360,38 @@ async function runTask(ctx, task, agent) {
|
|
|
1920
2360
|
});
|
|
1921
2361
|
return;
|
|
1922
2362
|
}
|
|
2363
|
+
if (error instanceof FinancialReportsRuntimeError) {
|
|
2364
|
+
await appendLocalEvent(ctx, task.id, {
|
|
2365
|
+
level: "error",
|
|
2366
|
+
message: error.message,
|
|
2367
|
+
data: { code: error.code, step: error.step }
|
|
2368
|
+
});
|
|
2369
|
+
await ctx.api.failTask(task.id, {
|
|
2370
|
+
error: {
|
|
2371
|
+
code: error.code,
|
|
2372
|
+
message: error.message,
|
|
2373
|
+
step: error.step,
|
|
2374
|
+
recoverable: false
|
|
2375
|
+
}
|
|
2376
|
+
});
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
if (error instanceof GitMirrorError) {
|
|
2380
|
+
await appendLocalEvent(ctx, task.id, {
|
|
2381
|
+
level: "error",
|
|
2382
|
+
message: error.message,
|
|
2383
|
+
data: { code: error.code, step: error.step }
|
|
2384
|
+
});
|
|
2385
|
+
await ctx.api.failTask(task.id, {
|
|
2386
|
+
error: {
|
|
2387
|
+
code: error.code,
|
|
2388
|
+
message: error.message,
|
|
2389
|
+
step: error.step,
|
|
2390
|
+
recoverable: false
|
|
2391
|
+
}
|
|
2392
|
+
});
|
|
2393
|
+
return;
|
|
2394
|
+
}
|
|
1923
2395
|
await appendLocalEvent(ctx, task.id, {
|
|
1924
2396
|
level: "error",
|
|
1925
2397
|
message: error instanceof Error ? error.message : "Task failed",
|
|
@@ -2025,6 +2497,9 @@ async function detectCapabilities() {
|
|
|
2025
2497
|
git,
|
|
2026
2498
|
node: true,
|
|
2027
2499
|
python: python3 || python,
|
|
2500
|
+
financialReports: true,
|
|
2501
|
+
financialReportsNotebookLm: false,
|
|
2502
|
+
financialReportsSecHtmlRenderer: false,
|
|
2028
2503
|
investWikiRuntime,
|
|
2029
2504
|
codex,
|
|
2030
2505
|
claude
|
|
@@ -2101,9 +2576,9 @@ async function subscribeToTaskAvailable(options, onEvent, onInvalidEvent) {
|
|
|
2101
2576
|
}
|
|
2102
2577
|
|
|
2103
2578
|
// src/service.ts
|
|
2104
|
-
import { mkdir as
|
|
2579
|
+
import { mkdir as mkdir6, unlink, writeFile as writeFile5 } from "fs/promises";
|
|
2105
2580
|
import os2 from "os";
|
|
2106
|
-
import
|
|
2581
|
+
import path9 from "path";
|
|
2107
2582
|
var LAUNCH_AGENT_LABEL = "com.xdsjs.dossierx-daemon";
|
|
2108
2583
|
function xmlEscape(value) {
|
|
2109
2584
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -2112,10 +2587,10 @@ function shellQuote(value) {
|
|
|
2112
2587
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
2113
2588
|
}
|
|
2114
2589
|
function launchAgentsDir() {
|
|
2115
|
-
return
|
|
2590
|
+
return path9.join(os2.homedir(), "Library", "LaunchAgents");
|
|
2116
2591
|
}
|
|
2117
2592
|
function launchAgentPlistPath(label = LAUNCH_AGENT_LABEL, dir = launchAgentsDir()) {
|
|
2118
|
-
return
|
|
2593
|
+
return path9.join(dir, `${label}.plist`);
|
|
2119
2594
|
}
|
|
2120
2595
|
function resolveDaemonProgramArguments(input) {
|
|
2121
2596
|
if (input.daemonCommand?.trim()) {
|
|
@@ -2126,14 +2601,14 @@ function resolveDaemonProgramArguments(input) {
|
|
|
2126
2601
|
if (!entry) {
|
|
2127
2602
|
throw new Error("Unable to resolve daemon entrypoint for LaunchAgent");
|
|
2128
2603
|
}
|
|
2129
|
-
if (
|
|
2604
|
+
if (path9.extname(entry) === ".ts") {
|
|
2130
2605
|
throw new Error(
|
|
2131
2606
|
"LaunchAgent cannot run a TypeScript daemon entrypoint directly; pass --daemon-command when installing from source"
|
|
2132
2607
|
);
|
|
2133
2608
|
}
|
|
2134
2609
|
return [
|
|
2135
2610
|
input.execPath ?? process.execPath,
|
|
2136
|
-
|
|
2611
|
+
path9.resolve(entry),
|
|
2137
2612
|
"--log-level",
|
|
2138
2613
|
input.logLevel ?? "info"
|
|
2139
2614
|
];
|
|
@@ -2180,6 +2655,10 @@ function runtimeOptionsFrom(input) {
|
|
|
2180
2655
|
codexCommand: input.codexCommand,
|
|
2181
2656
|
codexModel: input.codexModel,
|
|
2182
2657
|
codexSandbox: input.codexSandbox,
|
|
2658
|
+
gitMirrorRoot: input.gitMirrorRoot,
|
|
2659
|
+
gitMirrorRemote: input.gitMirrorRemote,
|
|
2660
|
+
gitMirrorBranch: input.gitMirrorBranch,
|
|
2661
|
+
gitCommand: input.gitCommand,
|
|
2183
2662
|
localApiPort: input.localApiPort
|
|
2184
2663
|
};
|
|
2185
2664
|
}
|
|
@@ -2188,7 +2667,7 @@ async function installLaunchAgent(options = {}) {
|
|
|
2188
2667
|
throw new Error("DossierX LaunchAgent service is only supported on macOS");
|
|
2189
2668
|
}
|
|
2190
2669
|
const label = options.label ?? LAUNCH_AGENT_LABEL;
|
|
2191
|
-
const configDir =
|
|
2670
|
+
const configDir = path9.resolve(
|
|
2192
2671
|
expandHomePath(options.configDir ?? getDaemonConfigDir())
|
|
2193
2672
|
);
|
|
2194
2673
|
const config = await readDaemonLocalConfig(configDir);
|
|
@@ -2208,8 +2687,8 @@ async function installLaunchAgent(options = {}) {
|
|
|
2208
2687
|
);
|
|
2209
2688
|
}
|
|
2210
2689
|
const plistPath = launchAgentPlistPath(label, options.launchAgentsDir);
|
|
2211
|
-
const stdoutPath =
|
|
2212
|
-
const stderrPath =
|
|
2690
|
+
const stdoutPath = path9.join(configDir, "daemon.out.log");
|
|
2691
|
+
const stderrPath = path9.join(configDir, "daemon.err.log");
|
|
2213
2692
|
const uid = typeof process.getuid === "function" ? process.getuid() : "<uid>";
|
|
2214
2693
|
const programArguments = resolveDaemonProgramArguments({
|
|
2215
2694
|
daemonCommand: options.daemonCommand,
|
|
@@ -2217,9 +2696,9 @@ async function installLaunchAgent(options = {}) {
|
|
|
2217
2696
|
argv: options.argv,
|
|
2218
2697
|
execPath: options.execPath
|
|
2219
2698
|
});
|
|
2220
|
-
await
|
|
2221
|
-
await
|
|
2222
|
-
await
|
|
2699
|
+
await mkdir6(path9.dirname(plistPath), { recursive: true });
|
|
2700
|
+
await mkdir6(configDir, { recursive: true, mode: 448 });
|
|
2701
|
+
await writeFile5(
|
|
2223
2702
|
plistPath,
|
|
2224
2703
|
buildLaunchAgentPlist({
|
|
2225
2704
|
label,
|
|
@@ -2260,7 +2739,7 @@ async function uninstallLaunchAgent(options = {}) {
|
|
|
2260
2739
|
|
|
2261
2740
|
// src/cli.ts
|
|
2262
2741
|
async function ensureWorkspaceDirectory(workspace) {
|
|
2263
|
-
await
|
|
2742
|
+
await mkdir7(workspace, { recursive: true }).catch(async (error) => {
|
|
2264
2743
|
const stats2 = await stat4(workspace).catch(() => null);
|
|
2265
2744
|
if (!stats2?.isDirectory()) {
|
|
2266
2745
|
throw new Error("Workspace path is not a directory");
|
|
@@ -2309,11 +2788,11 @@ function buildProgram() {
|
|
|
2309
2788
|
).option("--codex-command <command>", "installed Codex CLI command").option("--codex-model <model>", "Codex model override for exec mode").option(
|
|
2310
2789
|
"--codex-sandbox <mode>",
|
|
2311
2790
|
"Codex sandbox mode: workspace-write or danger-full-access"
|
|
2312
|
-
).option("--local-api-port <port>", "local workspace preview API port").option("--no-local-api", "disable local workspace preview API").action(async (options) => {
|
|
2791
|
+
).option("--git-mirror-root <path>", "local Git mirror repository path").option("--git-mirror-remote <url>", "remote Git mirror repository URL").option("--git-mirror-branch <branch>", "Git mirror branch name").option("--git-command <command>", "installed Git command").option("--local-api-port <port>", "local workspace preview API port").option("--no-local-api", "disable local workspace preview API").action(async (options) => {
|
|
2313
2792
|
await runDaemon(options);
|
|
2314
2793
|
});
|
|
2315
2794
|
const service = program.command("service").description("Manage the macOS LaunchAgent for dossierx-daemon");
|
|
2316
|
-
service.command("install").description("Write a macOS LaunchAgent plist for persistent daemon runs").option("--label <label>", "LaunchAgent label").option("--daemon-command <command>", "custom command used by launchd").option("--log-level <level>", "daemon log level", "info").option("--invest-wiki-mode <mode>", "invest wiki runner mode").option("--invest-wiki-local-repo <path>", "local llm-wiki-invest repo path").option("--invest-wiki-command <command>", "installed llm-wiki-invest command").option("--codex-command <command>", "installed Codex CLI command").option("--codex-model <model>", "Codex model override for exec mode").option("--codex-sandbox <mode>", "Codex sandbox mode").option("--local-api-port <port>", "local workspace preview API port").action(async (options) => {
|
|
2795
|
+
service.command("install").description("Write a macOS LaunchAgent plist for persistent daemon runs").option("--label <label>", "LaunchAgent label").option("--daemon-command <command>", "custom command used by launchd").option("--log-level <level>", "daemon log level", "info").option("--invest-wiki-mode <mode>", "invest wiki runner mode").option("--invest-wiki-local-repo <path>", "local llm-wiki-invest repo path").option("--invest-wiki-command <command>", "installed llm-wiki-invest command").option("--codex-command <command>", "installed Codex CLI command").option("--codex-model <model>", "Codex model override for exec mode").option("--codex-sandbox <mode>", "Codex sandbox mode").option("--git-mirror-root <path>", "local Git mirror repository path").option("--git-mirror-remote <url>", "remote Git mirror repository URL").option("--git-mirror-branch <branch>", "Git mirror branch name").option("--git-command <command>", "installed Git command").option("--local-api-port <port>", "local workspace preview API port").action(async (options) => {
|
|
2317
2796
|
const result = await installLaunchAgent({
|
|
2318
2797
|
...options,
|
|
2319
2798
|
localApiPort: parsePort(options.localApiPort)
|
|
@@ -2347,6 +2826,10 @@ async function runDaemon(options) {
|
|
|
2347
2826
|
codexCommand: options.codexCommand ?? localConfig?.codexCommand,
|
|
2348
2827
|
codexModel: options.codexModel ?? localConfig?.codexModel,
|
|
2349
2828
|
codexSandbox: options.codexSandbox ?? localConfig?.codexSandbox,
|
|
2829
|
+
gitMirrorRoot: options.gitMirrorRoot ?? localConfig?.gitMirrorRoot,
|
|
2830
|
+
gitMirrorRemote: options.gitMirrorRemote ?? localConfig?.gitMirrorRemote,
|
|
2831
|
+
gitMirrorBranch: options.gitMirrorBranch ?? localConfig?.gitMirrorBranch,
|
|
2832
|
+
gitCommand: options.gitCommand ?? localConfig?.gitCommand,
|
|
2350
2833
|
localApiPort: parsePort(options.localApiPort) ?? localConfig?.localApiPort ?? parsePort(process.env.DOSSIERX_LOCAL_API_PORT) ?? DOSSIERX_DEFAULT_LOCAL_API_PORT
|
|
2351
2834
|
};
|
|
2352
2835
|
if (!serverUrl || !supabaseUrl || !supabaseAnonKey || !machineKey) {
|
|
@@ -2354,11 +2837,12 @@ async function runDaemon(options) {
|
|
|
2354
2837
|
"Missing daemon connection config. Run the generated daemon command from DossierX first."
|
|
2355
2838
|
);
|
|
2356
2839
|
}
|
|
2357
|
-
const workspaceRoot =
|
|
2840
|
+
const workspaceRoot = path10.resolve(expandHomePath(workspacePath3));
|
|
2358
2841
|
await ensureWorkspaceDirectory(workspaceRoot);
|
|
2359
2842
|
const capabilities = await detectCapabilities();
|
|
2360
2843
|
const investWiki = createInvestWikiRunnerFromOptions(runtimeOptions);
|
|
2361
2844
|
const codex = createCodexRunnerFromOptions(runtimeOptions);
|
|
2845
|
+
const gitMirror = createGitMirrorFromOptions(runtimeOptions);
|
|
2362
2846
|
const taskArchive = createTaskArchive({ workspaceRoot });
|
|
2363
2847
|
const api = new ApiClient({
|
|
2364
2848
|
serverUrl,
|
|
@@ -2384,6 +2868,10 @@ async function runDaemon(options) {
|
|
|
2384
2868
|
codexCommand: runtimeOptions.codexCommand,
|
|
2385
2869
|
codexModel: runtimeOptions.codexModel,
|
|
2386
2870
|
codexSandbox: runtimeOptions.codexSandbox,
|
|
2871
|
+
gitMirrorRoot: runtimeOptions.gitMirrorRoot,
|
|
2872
|
+
gitMirrorRemote: runtimeOptions.gitMirrorRemote,
|
|
2873
|
+
gitMirrorBranch: runtimeOptions.gitMirrorBranch,
|
|
2874
|
+
gitCommand: runtimeOptions.gitCommand,
|
|
2387
2875
|
localApiPort: runtimeOptions.localApiPort
|
|
2388
2876
|
});
|
|
2389
2877
|
const state = { running: false, pending: false };
|
|
@@ -2397,6 +2885,7 @@ async function runDaemon(options) {
|
|
|
2397
2885
|
codex,
|
|
2398
2886
|
codexOptions: runtimeOptions,
|
|
2399
2887
|
investWiki,
|
|
2888
|
+
gitMirror,
|
|
2400
2889
|
taskArchive
|
|
2401
2890
|
};
|
|
2402
2891
|
async function heartbeat(status) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xdsjs/dossierx-daemon",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -28,8 +28,10 @@
|
|
|
28
28
|
"execa": "^9.0.0",
|
|
29
29
|
"pino": "^10.0.0",
|
|
30
30
|
"zod": "^4.0.0",
|
|
31
|
-
"@xdsjs/
|
|
32
|
-
"@xdsjs/dossierx-
|
|
31
|
+
"@xdsjs/dossier-financial-reports": "^0.1.0",
|
|
32
|
+
"@xdsjs/dossierx-git-mirror": "^0.1.2",
|
|
33
|
+
"@xdsjs/dossierx-shared": "^0.1.3",
|
|
34
|
+
"@xdsjs/dossierx-workspace": "^0.1.1"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@types/node": "^24.0.0",
|