deepseek-coordination 0.14.2 → 0.14.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/lib/client.js +2938 -940
- package/lib/index.mjs +262 -0
- package/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { appendFileSync, closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { readdir } from "node:fs/promises";
|
|
3
4
|
import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
4
5
|
import Schema$1 from "@deepseek-ai/schemastery";
|
|
5
6
|
import { Service } from "@deepseek-ai/cordis";
|
|
@@ -15803,6 +15804,11 @@ const ZH = {
|
|
|
15803
15804
|
rebuildDone: "重建完成:{files} 个文件,{nodes} 个节点,用时 {ms} 毫秒",
|
|
15804
15805
|
rebuildPartial: "已重建(部分)",
|
|
15805
15806
|
actionFailed: "操作失败({code})",
|
|
15807
|
+
indexWorkspace: "索引工作区",
|
|
15808
|
+
indexHint: "工作区还没有索引。索引后可浏览文件、大纲与依赖关系。",
|
|
15809
|
+
indexBusy: "正在索引工作区,请稍候",
|
|
15810
|
+
indexDone: "索引完成:{indexed} 个文件,{nodes} 个节点,用时 {ms} 毫秒",
|
|
15811
|
+
indexPartial: "已索引部分文件(达到上限)",
|
|
15806
15812
|
tabs: {
|
|
15807
15813
|
files: "文件",
|
|
15808
15814
|
outline: "大纲",
|
|
@@ -15899,6 +15905,7 @@ const ZH = {
|
|
|
15899
15905
|
fit: "适应画布",
|
|
15900
15906
|
resetLayout: "重置节点位置",
|
|
15901
15907
|
rebuilding: "正在重建…",
|
|
15908
|
+
indexWorkspace: "扫描工作区文件并建立索引(只读)",
|
|
15902
15909
|
agentStatus: "代理状态",
|
|
15903
15910
|
tabAgents: "查看本会话代理",
|
|
15904
15911
|
agentExpand: "展开子代理",
|
|
@@ -16777,6 +16784,11 @@ const COPY_BY_LOCALE = {
|
|
|
16777
16784
|
rebuildDone: "Rebuilt {files} files and {nodes} nodes in {ms} ms",
|
|
16778
16785
|
rebuildPartial: "Rebuilt partially",
|
|
16779
16786
|
actionFailed: "Action failed ({code})",
|
|
16787
|
+
indexWorkspace: "Index workspace",
|
|
16788
|
+
indexHint: "This workspace has no index yet. Index it to browse files, outlines and dependencies.",
|
|
16789
|
+
indexBusy: "Indexing the workspace; please wait.",
|
|
16790
|
+
indexDone: "Indexed {indexed} files and {nodes} nodes in {ms} ms",
|
|
16791
|
+
indexPartial: "Indexed partially (limit reached)",
|
|
16780
16792
|
tabs: {
|
|
16781
16793
|
files: "Files",
|
|
16782
16794
|
outline: "Outline",
|
|
@@ -16873,6 +16885,7 @@ const COPY_BY_LOCALE = {
|
|
|
16873
16885
|
fit: "Fit to canvas",
|
|
16874
16886
|
resetLayout: "Reset node positions",
|
|
16875
16887
|
rebuilding: "Rebuilding…",
|
|
16888
|
+
indexWorkspace: "Scan workspace files and build the index (read-only)",
|
|
16876
16889
|
agentStatus: "Agent status",
|
|
16877
16890
|
tabAgents: "View this session’s agents",
|
|
16878
16891
|
agentExpand: "Expand sub-agents",
|
|
@@ -68563,6 +68576,18 @@ var require_underline = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
|
68563
68576
|
* No Office byte is ever written to disk, no external process is started and
|
|
68564
68577
|
* no converted body is stored or cached.
|
|
68565
68578
|
*
|
|
68579
|
+
* # Bounded workspace indexing (`POST /graph/index`)
|
|
68580
|
+
*
|
|
68581
|
+
* A fresh profile has an empty graph: the file tools index only what they are
|
|
68582
|
+
* asked to parse. {@link indexWorkspaceFiles} is the scan half of the explicit
|
|
68583
|
+
* indexing entry point and is still pure orchestration — the host injects a
|
|
68584
|
+
* read-only {@link WorkspaceIndexIo} that binds it to `node:fs`, `ParseService`
|
|
68585
|
+
* and `GraphStore`/`ChainEngine`. The scan never descends into
|
|
68586
|
+
* `node_modules`/`.git`/`dist`/`.tmp`, never attempts a binary extension, is
|
|
68587
|
+
* capped at {@link MAX_INDEX_FILES} files / {@link INDEX_TIME_LIMIT_MS} ms and
|
|
68588
|
+
* reports `truncated` when a bound stopped it. {@link WorkspaceIndexGate}
|
|
68589
|
+
* serializes concurrent runs (the route answers 409 `BUSY`).
|
|
68590
|
+
*
|
|
68566
68591
|
* # Two-level cache (dependencies / imports / intra)
|
|
68567
68592
|
*
|
|
68568
68593
|
* Per-file facts (`imports`, `exports`, `deps`) are resolved through:
|
|
@@ -71390,6 +71415,173 @@ const Color = {
|
|
|
71390
71415
|
Gray: 1,
|
|
71391
71416
|
Black: 2
|
|
71392
71417
|
};
|
|
71418
|
+
/** Directories the scan never descends into (matched case-insensitively). */
|
|
71419
|
+
const INDEX_SKIP_DIRECTORIES = [
|
|
71420
|
+
"node_modules",
|
|
71421
|
+
".git",
|
|
71422
|
+
"dist",
|
|
71423
|
+
".tmp"
|
|
71424
|
+
];
|
|
71425
|
+
/**
|
|
71426
|
+
* Extensions the scan accepts, mirroring the registered text parsers.
|
|
71427
|
+
*
|
|
71428
|
+
* Deliberately an allowlist: shared `.json`/`.yaml`/`.xml` extensions have no
|
|
71429
|
+
* registered parser (the text layer refuses to guess), and binary documents
|
|
71430
|
+
* (`.docx`, `.pdf`, images, archives) stay out by construction.
|
|
71431
|
+
*/
|
|
71432
|
+
const INDEX_FILE_EXTENSIONS = [
|
|
71433
|
+
".ts",
|
|
71434
|
+
".tsx",
|
|
71435
|
+
".mts",
|
|
71436
|
+
".cts",
|
|
71437
|
+
".js",
|
|
71438
|
+
".jsx",
|
|
71439
|
+
".mjs",
|
|
71440
|
+
".cjs",
|
|
71441
|
+
".java",
|
|
71442
|
+
".py",
|
|
71443
|
+
".pyi",
|
|
71444
|
+
".pyw",
|
|
71445
|
+
".rs",
|
|
71446
|
+
".php",
|
|
71447
|
+
".c",
|
|
71448
|
+
".h",
|
|
71449
|
+
".cc",
|
|
71450
|
+
".cpp",
|
|
71451
|
+
".hpp",
|
|
71452
|
+
".cxx",
|
|
71453
|
+
".hh",
|
|
71454
|
+
".md",
|
|
71455
|
+
".markdown",
|
|
71456
|
+
".mdx",
|
|
71457
|
+
".tex",
|
|
71458
|
+
".ltx",
|
|
71459
|
+
".latex",
|
|
71460
|
+
".book",
|
|
71461
|
+
".css",
|
|
71462
|
+
".html",
|
|
71463
|
+
".htm",
|
|
71464
|
+
".csv",
|
|
71465
|
+
".tsv"
|
|
71466
|
+
];
|
|
71467
|
+
/** True when a workspace-relative path has an extension the text layer parses. */
|
|
71468
|
+
function isIndexableWorkspaceFile(path) {
|
|
71469
|
+
const extension = extensionOf(path);
|
|
71470
|
+
return extension.length > 0 && INDEX_FILE_EXTENSIONS.includes(extension);
|
|
71471
|
+
}
|
|
71472
|
+
function isSkippedWorkspaceDirectory(name) {
|
|
71473
|
+
return INDEX_SKIP_DIRECTORIES.includes(name.toLowerCase());
|
|
71474
|
+
}
|
|
71475
|
+
function joinWorkspacePath(directory, name) {
|
|
71476
|
+
return directory.length === 0 ? name : `${directory}/${name}`;
|
|
71477
|
+
}
|
|
71478
|
+
/**
|
|
71479
|
+
* Scan the workspace and index every accepted text file.
|
|
71480
|
+
*
|
|
71481
|
+
* Breadth-first and deterministic: each directory's entries are sorted by
|
|
71482
|
+
* code-point name before they are visited, so a capped run always indexes the
|
|
71483
|
+
* same prefix of the workspace. `indexed` counts successful parses, `skipped`
|
|
71484
|
+
* counts files that were seen but not indexed (extension or parse failure);
|
|
71485
|
+
* directories are never counted. A directory that cannot be listed is skipped
|
|
71486
|
+
* without aborting the scan.
|
|
71487
|
+
*/
|
|
71488
|
+
async function indexWorkspaceFiles(io, options = {}) {
|
|
71489
|
+
const maxFiles = Math.max(0, Math.floor(options.maxFiles ?? 200));
|
|
71490
|
+
const timeLimitMs = Math.max(0, Math.floor(options.timeLimitMs ?? 1e4));
|
|
71491
|
+
const maxDepth = Math.max(1, Math.floor(options.maxDepth ?? 8));
|
|
71492
|
+
const now = options.now ?? Date.now;
|
|
71493
|
+
const startedAt = now();
|
|
71494
|
+
let indexed = 0;
|
|
71495
|
+
let skipped = 0;
|
|
71496
|
+
let nodes = 0;
|
|
71497
|
+
let attempted = 0;
|
|
71498
|
+
let truncated = false;
|
|
71499
|
+
const pending = [{
|
|
71500
|
+
path: "",
|
|
71501
|
+
depth: 0
|
|
71502
|
+
}];
|
|
71503
|
+
scan: while (pending.length > 0) {
|
|
71504
|
+
if (now() - startedAt >= timeLimitMs) {
|
|
71505
|
+
truncated = true;
|
|
71506
|
+
break;
|
|
71507
|
+
}
|
|
71508
|
+
const directory = pending.shift();
|
|
71509
|
+
let entries;
|
|
71510
|
+
try {
|
|
71511
|
+
entries = await io.readDirectory(directory.path);
|
|
71512
|
+
} catch {
|
|
71513
|
+
continue;
|
|
71514
|
+
}
|
|
71515
|
+
const ordered = [...entries].sort((left, right) => compareText(left.name, right.name));
|
|
71516
|
+
for (const entry of ordered) {
|
|
71517
|
+
if (now() - startedAt >= timeLimitMs) {
|
|
71518
|
+
truncated = true;
|
|
71519
|
+
break scan;
|
|
71520
|
+
}
|
|
71521
|
+
if (entry.directory) {
|
|
71522
|
+
if (directory.depth + 1 >= maxDepth) continue;
|
|
71523
|
+
if (isSkippedWorkspaceDirectory(entry.name)) continue;
|
|
71524
|
+
pending.push({
|
|
71525
|
+
path: joinWorkspacePath(directory.path, entry.name),
|
|
71526
|
+
depth: directory.depth + 1
|
|
71527
|
+
});
|
|
71528
|
+
continue;
|
|
71529
|
+
}
|
|
71530
|
+
const path = joinWorkspacePath(directory.path, entry.name);
|
|
71531
|
+
if (!isIndexableWorkspaceFile(path)) {
|
|
71532
|
+
skipped += 1;
|
|
71533
|
+
continue;
|
|
71534
|
+
}
|
|
71535
|
+
if (attempted >= maxFiles) {
|
|
71536
|
+
truncated = true;
|
|
71537
|
+
break scan;
|
|
71538
|
+
}
|
|
71539
|
+
attempted += 1;
|
|
71540
|
+
try {
|
|
71541
|
+
const count = await io.indexFile(path);
|
|
71542
|
+
indexed += 1;
|
|
71543
|
+
nodes += Number.isFinite(count) && count > 0 ? Math.floor(count) : 0;
|
|
71544
|
+
} catch {
|
|
71545
|
+
skipped += 1;
|
|
71546
|
+
}
|
|
71547
|
+
}
|
|
71548
|
+
}
|
|
71549
|
+
return {
|
|
71550
|
+
indexed,
|
|
71551
|
+
skipped,
|
|
71552
|
+
nodes,
|
|
71553
|
+
durationMs: Math.max(0, now() - startedAt),
|
|
71554
|
+
truncated
|
|
71555
|
+
};
|
|
71556
|
+
}
|
|
71557
|
+
/**
|
|
71558
|
+
* Serialize workspace indexes in one host process.
|
|
71559
|
+
*
|
|
71560
|
+
* The flag flips synchronously before the task's first `await`, so a second
|
|
71561
|
+
* request in the same tick answers BUSY instead of queuing behind the scan.
|
|
71562
|
+
* The `finally` reset means a thrown task never leaves the gate stuck.
|
|
71563
|
+
*/
|
|
71564
|
+
var WorkspaceIndexGate = class {
|
|
71565
|
+
active = false;
|
|
71566
|
+
isBusy() {
|
|
71567
|
+
return this.active;
|
|
71568
|
+
}
|
|
71569
|
+
async run(task) {
|
|
71570
|
+
if (this.active) return {
|
|
71571
|
+
ok: false,
|
|
71572
|
+
busy: true
|
|
71573
|
+
};
|
|
71574
|
+
this.active = true;
|
|
71575
|
+
try {
|
|
71576
|
+
return {
|
|
71577
|
+
ok: true,
|
|
71578
|
+
value: await task()
|
|
71579
|
+
};
|
|
71580
|
+
} finally {
|
|
71581
|
+
this.active = false;
|
|
71582
|
+
}
|
|
71583
|
+
}
|
|
71584
|
+
};
|
|
71393
71585
|
//#endregion
|
|
71394
71586
|
//#region src/index.ts
|
|
71395
71587
|
const name = "deepseek-coordination";
|
|
@@ -71674,6 +71866,58 @@ async function rebuildGraph(service) {
|
|
|
71674
71866
|
};
|
|
71675
71867
|
}
|
|
71676
71868
|
/**
|
|
71869
|
+
* Serializes `POST /graph/index`: the flag flips before the first await, so a
|
|
71870
|
+
* concurrent request answers 409 `BUSY` instead of queuing behind the scan.
|
|
71871
|
+
*/
|
|
71872
|
+
const indexGate = new WorkspaceIndexGate();
|
|
71873
|
+
/**
|
|
71874
|
+
* Explicit indexing entry point for a profile whose graph is still empty.
|
|
71875
|
+
*
|
|
71876
|
+
* The file tools only index the paths they are asked to parse, so a fresh
|
|
71877
|
+
* workspace legitimately has no graph and the panels show their empty state.
|
|
71878
|
+
* This scan walks the workspace instead: the bounds (depth ≤ 8, accepted
|
|
71879
|
+
* extensions, skipped directories, 200 files / 10 s) live beside the pure
|
|
71880
|
+
* walker in `host/graph-view.ts`; this adapter only binds it to the same
|
|
71881
|
+
* services {@link rebuildGraph} uses.
|
|
71882
|
+
*
|
|
71883
|
+
* Read-only: directories are listed, accepted files are read through
|
|
71884
|
+
* `ParseService`'s budgeted read path, and only the coordination database's own
|
|
71885
|
+
* graph rows are rewritten. No build, command or execution runs, no workspace
|
|
71886
|
+
* file is written, and symlinks are not followed.
|
|
71887
|
+
*/
|
|
71888
|
+
async function indexWorkspace(service) {
|
|
71889
|
+
const root = resolveToolWorkspaceRoot(service);
|
|
71890
|
+
const parser = new ParseService({ workspaceRoot: root });
|
|
71891
|
+
const store = new GraphStore(service.db, { workspaceRoot: root });
|
|
71892
|
+
const registry = new NodeRegistry({ persist: { saveNode: (record) => store.saveNode(record) } });
|
|
71893
|
+
const chain = new ChainEngine({ persist: {
|
|
71894
|
+
saveNode: (node) => store.saveChainNode(node),
|
|
71895
|
+
saveEdge: (edge) => store.saveEdge(edge),
|
|
71896
|
+
deleteEdge: (edge) => store.deleteEdge(edge)
|
|
71897
|
+
} });
|
|
71898
|
+
const result = await indexWorkspaceFiles({
|
|
71899
|
+
async readDirectory(path) {
|
|
71900
|
+
const absolute = path.length === 0 ? root : join(root, path);
|
|
71901
|
+
return (await readdir(absolute, { withFileTypes: true })).filter((entry) => !entry.isSymbolicLink() && (entry.isDirectory() || entry.isFile())).map((entry) => ({
|
|
71902
|
+
name: entry.name,
|
|
71903
|
+
directory: entry.isDirectory()
|
|
71904
|
+
}));
|
|
71905
|
+
},
|
|
71906
|
+
async indexFile(path) {
|
|
71907
|
+
const parsed = parser.parse({ path });
|
|
71908
|
+
return store.transaction(() => {
|
|
71909
|
+
store.beginParse(path);
|
|
71910
|
+
const registered = registry.register(parsed);
|
|
71911
|
+
chain.index(parsed);
|
|
71912
|
+
store.saveSnapshot(parsed, { nodeCount: registered.length });
|
|
71913
|
+
return registered.length;
|
|
71914
|
+
});
|
|
71915
|
+
}
|
|
71916
|
+
});
|
|
71917
|
+
resetGraphCache();
|
|
71918
|
+
return result;
|
|
71919
|
+
}
|
|
71920
|
+
/**
|
|
71677
71921
|
* Nest a flat dotted override map, then merge it over the deployment config.
|
|
71678
71922
|
*
|
|
71679
71923
|
* Stored overrides win over the profile patch: the operator changed those paths
|
|
@@ -72220,6 +72464,24 @@ function apply(context, configInput) {
|
|
|
72220
72464
|
}
|
|
72221
72465
|
return;
|
|
72222
72466
|
}
|
|
72467
|
+
if (path === "/graph/index" && method === "POST") {
|
|
72468
|
+
const outcome = await indexGate.run(() => indexWorkspace(service));
|
|
72469
|
+
if (!outcome.ok) {
|
|
72470
|
+
json(response, {
|
|
72471
|
+
ok: false,
|
|
72472
|
+
error: {
|
|
72473
|
+
code: "BUSY",
|
|
72474
|
+
message: "a workspace index is already running"
|
|
72475
|
+
}
|
|
72476
|
+
}, 409);
|
|
72477
|
+
return;
|
|
72478
|
+
}
|
|
72479
|
+
json(response, {
|
|
72480
|
+
ok: true,
|
|
72481
|
+
value: outcome.value
|
|
72482
|
+
});
|
|
72483
|
+
return;
|
|
72484
|
+
}
|
|
72223
72485
|
if (path === "/graph/rebuild" && method === "POST") {
|
|
72224
72486
|
if (rebuildInFlight) {
|
|
72225
72487
|
json(response, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepseek-coordination",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.3",
|
|
4
4
|
"description": "A dual-face Cordis plugin for DeepSeek Harness: the host half owns a coordination SQLite database, seven model-facing tools and an HTTP surface; the web half mounts five composer dropdowns, a settings catalog and the service-level takeover dialog.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|