@lmzhen/dsh-skill-usage 0.1.0 → 0.2.0-rc.1
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 +8 -0
- package/lib/index.js +91 -2
- package/lib/types/index.d.ts +18 -0
- package/package.json +7 -5
package/README.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Skill usage telemetry service
|
|
4
4
|
|
|
5
|
+
## Telemetry sources
|
|
6
|
+
|
|
7
|
+
`record(name, kind)` is the write API (`use` / `view` / `patch`). Reads are
|
|
8
|
+
observed automatically: the service listens for `session/event` `tool/call`
|
|
9
|
+
records of the read tools (`skill`, `skill_load`) and bumps `view` on
|
|
10
|
+
EXISTING records only — an arbitrary read never mints a usage record
|
|
11
|
+
(records are authored by skill creation, patching, or curator seeding).
|
|
12
|
+
|
|
5
13
|
## Model Experience
|
|
6
14
|
|
|
7
15
|
### Indirect model surface
|
package/lib/index.js
CHANGED
|
@@ -1,21 +1,110 @@
|
|
|
1
1
|
import { Service } from "@deepseek-ai/cordis";
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
|
-
import {
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { appendEvolutionEvent, bumpPatch, bumpUse, bumpView, eventsFile, evolutionIoAdapter, getRecord, loadUsage, markAgentCreated, mutateUsage, skillsRoot } from "@lmzhen/dsh-evolution-core";
|
|
4
6
|
//#region lib/types/index.js
|
|
5
7
|
/**
|
|
6
8
|
* Skill usage telemetry service for the evolution family.
|
|
7
9
|
* @module @lmzhen/dsh-skill-usage
|
|
8
10
|
*/
|
|
11
|
+
/**
|
|
12
|
+
* Read tool names -> usage kind. The single declarative classification table
|
|
13
|
+
* for read-side observation (A2): any tool listed here records a `view` when
|
|
14
|
+
* its call arguments name a skill.
|
|
15
|
+
*/
|
|
16
|
+
const READ_TOOL_KIND = {
|
|
17
|
+
skill: "view",
|
|
18
|
+
skill_load: "view"
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Skill name from a tool/call arguments payload (A2, mirrors
|
|
22
|
+
* evolution-review's collectReadSkillNames): JSON strings are re-parsed and
|
|
23
|
+
* `name` wins over `skill`. Empty when unparseable or anonymous.
|
|
24
|
+
*/
|
|
25
|
+
function skillNameFromToolCall(raw) {
|
|
26
|
+
let parsed = {};
|
|
27
|
+
if (typeof raw === "string") try {
|
|
28
|
+
parsed = JSON.parse(raw);
|
|
29
|
+
} catch {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
else if (raw && typeof raw === "object") parsed = raw;
|
|
33
|
+
return typeof parsed.name === "string" ? parsed.name : typeof parsed.skill === "string" ? parsed.skill : "";
|
|
34
|
+
}
|
|
35
|
+
/** Cumulative library-wide usage totals (C observation-window event counts). */
|
|
36
|
+
function usageTotals(map) {
|
|
37
|
+
let views = 0;
|
|
38
|
+
let use = 0;
|
|
39
|
+
let patches = 0;
|
|
40
|
+
for (const record of map.values()) {
|
|
41
|
+
views += record.view_count;
|
|
42
|
+
use += record.use_count;
|
|
43
|
+
patches += record.patch_count;
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
skills: map.size,
|
|
47
|
+
views,
|
|
48
|
+
use,
|
|
49
|
+
patches
|
|
50
|
+
};
|
|
51
|
+
}
|
|
9
52
|
var SkillUsageRegistry = class extends Service {
|
|
10
53
|
static inject = ["evolutionIo"];
|
|
11
|
-
static Config = z.object({
|
|
54
|
+
static Config = z.object({
|
|
55
|
+
root: z.string().default(""),
|
|
56
|
+
eventsHome: z.string().default("")
|
|
57
|
+
});
|
|
12
58
|
root;
|
|
59
|
+
eventsHome;
|
|
13
60
|
io;
|
|
14
61
|
chain = Promise.resolve();
|
|
15
62
|
constructor(ctx, config = {}) {
|
|
16
63
|
super(ctx, "skillUsage");
|
|
17
64
|
this.root = config.root || skillsRoot();
|
|
65
|
+
this.eventsHome = config.eventsHome || process.env.DSH_HOME || join(homedir(), ".dsh");
|
|
18
66
|
this.io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
|
|
67
|
+
ctx.on("session/event", (_session, event) => {
|
|
68
|
+
if (event.type !== "tool/call") return;
|
|
69
|
+
if (!READ_TOOL_KIND[event.data.name]) return;
|
|
70
|
+
const name = skillNameFromToolCall(event.data.arguments);
|
|
71
|
+
if (!name) return;
|
|
72
|
+
this.observeRead(name).catch(() => {});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Read-side telemetry (A2): bump the view counter for an EXISTING record
|
|
77
|
+
* only. Creation-free by design — otherwise an arbitrary read mints a
|
|
78
|
+
* record, and "patched many times, reads zero" (the write-ghost signal)
|
|
79
|
+
* stays computable only while records exist from authorship paths.
|
|
80
|
+
*
|
|
81
|
+
* Observation window (C): the FIRST observed read library-wide opens the
|
|
82
|
+
* window — one `type:'usage'` event is appended to the evolution timeline
|
|
83
|
+
* (best-effort; the usage sidecar stays the truth and never fails the read
|
|
84
|
+
* path). Post-A2 deployments treat `view_count` as trustworthy only after
|
|
85
|
+
* that anchor exists (curator gates churn on `usageObserved()`).
|
|
86
|
+
*/
|
|
87
|
+
observeRead(name) {
|
|
88
|
+
return this.mutate(async (map) => {
|
|
89
|
+
if (!map.has(name)) return;
|
|
90
|
+
const viewsBefore = usageTotals(map).views;
|
|
91
|
+
bumpView(map, name, /* @__PURE__ */ new Date());
|
|
92
|
+
if (viewsBefore === 0) await this.appendUsageWindowEvent(map);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/** Append the observation-window anchor event; best-effort, never fails the observation. */
|
|
96
|
+
async appendUsageWindowEvent(map) {
|
|
97
|
+
try {
|
|
98
|
+
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
99
|
+
await appendEvolutionEvent(this.io, eventsFile(this.eventsHome), {
|
|
100
|
+
type: "usage",
|
|
101
|
+
kind: "skill",
|
|
102
|
+
source: "observation",
|
|
103
|
+
note: "observation window opened",
|
|
104
|
+
counts: usageTotals(map),
|
|
105
|
+
window: { opened: at }
|
|
106
|
+
});
|
|
107
|
+
} catch {}
|
|
19
108
|
}
|
|
20
109
|
/**
|
|
21
110
|
* Serialize read-modify-write cycles so concurrent record calls never lose
|
package/lib/types/index.d.ts
CHANGED
|
@@ -12,14 +12,32 @@ declare module '@deepseek-ai/cordis' {
|
|
|
12
12
|
}
|
|
13
13
|
export interface Config {
|
|
14
14
|
root?: string;
|
|
15
|
+
/** Home for the evolution event timeline (`<eventsHome>/evolution/events.json`); defaults to DSH_HOME or ~/.dsh. */
|
|
16
|
+
eventsHome?: string;
|
|
15
17
|
}
|
|
16
18
|
export declare class SkillUsageRegistry extends Service {
|
|
17
19
|
static inject: string[];
|
|
18
20
|
static Config: Schema<Config>;
|
|
19
21
|
readonly root: string;
|
|
22
|
+
private readonly eventsHome;
|
|
20
23
|
private readonly io;
|
|
21
24
|
private chain;
|
|
22
25
|
constructor(ctx: Context, config?: Config);
|
|
26
|
+
/**
|
|
27
|
+
* Read-side telemetry (A2): bump the view counter for an EXISTING record
|
|
28
|
+
* only. Creation-free by design — otherwise an arbitrary read mints a
|
|
29
|
+
* record, and "patched many times, reads zero" (the write-ghost signal)
|
|
30
|
+
* stays computable only while records exist from authorship paths.
|
|
31
|
+
*
|
|
32
|
+
* Observation window (C): the FIRST observed read library-wide opens the
|
|
33
|
+
* window — one `type:'usage'` event is appended to the evolution timeline
|
|
34
|
+
* (best-effort; the usage sidecar stays the truth and never fails the read
|
|
35
|
+
* path). Post-A2 deployments treat `view_count` as trustworthy only after
|
|
36
|
+
* that anchor exists (curator gates churn on `usageObserved()`).
|
|
37
|
+
*/
|
|
38
|
+
private observeRead;
|
|
39
|
+
/** Append the observation-window anchor event; best-effort, never fails the observation. */
|
|
40
|
+
private appendUsageWindowEvent;
|
|
23
41
|
/**
|
|
24
42
|
* Serialize read-modify-write cycles so concurrent record calls never lose
|
|
25
43
|
* updates, then run each cycle as ONE atomic transact (rc.50 P2-2): the map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmzhen/dsh-skill-usage",
|
|
3
3
|
"description": "Skill usage telemetry service (community build)",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0-rc.1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,16 +33,18 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
36
|
-
"@lmzhen/dsh-evolution-core": "^0.
|
|
36
|
+
"@lmzhen/dsh-evolution-core": "^0.2.0-rc.1"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
40
40
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
41
|
-
"@
|
|
41
|
+
"@deepseek-ai/dsh-session": "^0.1.1-rc.2",
|
|
42
|
+
"@lmzhen/dsh-evolution-io": "^0.2.0-rc.1"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
45
|
-
"@
|
|
46
|
-
"@lmzhen/dsh-evolution-
|
|
46
|
+
"@deepseek-ai/dsh-session": "^0.1.1-rc.2",
|
|
47
|
+
"@lmzhen/dsh-evolution-core": "^0.2.0-rc.1",
|
|
48
|
+
"@lmzhen/dsh-evolution-io": "^0.2.0-rc.1"
|
|
47
49
|
}
|
|
48
50
|
}
|