@lmzhen/dsh-skill-usage 0.3.16 → 0.3.18
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/lib/index.js +36 -6
- package/lib/types/index.d.ts +23 -1
- package/package.json +5 -5
package/lib/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Service } from "@deepseek-ai/cordis";
|
|
|
2
2
|
import z from "@deepseek-ai/schemastery";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { appendEvolutionEvent, bumpPatch, bumpUse, bumpView, eventsFile, evolutionIoAdapter, getRecord, loadUsage, markAgentCreated, mutateUsage,
|
|
5
|
+
import { appendEvolutionEvent, bumpPatch, bumpUse, bumpView, eventsFile, evolutionIoAdapter, getRecord, loadUsage, markAgentCreated, mutateUsage, resolveSkillsRoot } from "@lmzhen/dsh-evolution-core";
|
|
6
6
|
//#region lib/types/index.js
|
|
7
7
|
/**
|
|
8
8
|
* Skill usage telemetry service for the evolution family.
|
|
@@ -61,13 +61,14 @@ var SkillUsageRegistry = class extends Service {
|
|
|
61
61
|
chain = Promise.resolve();
|
|
62
62
|
constructor(ctx, config = {}) {
|
|
63
63
|
super(ctx, "skillUsage");
|
|
64
|
-
this.root = config
|
|
64
|
+
this.root = resolveSkillsRoot(config);
|
|
65
65
|
this.eventsHome = config.eventsHome || process.env.DSH_HOME || join(homedir(), ".dsh");
|
|
66
66
|
this.io = evolutionIoAdapter(() => ctx.evolutionIo.provider());
|
|
67
67
|
ctx.on("session/event", (_session, event) => {
|
|
68
68
|
if (event.type !== "tool/call") return;
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
const data = event.data;
|
|
70
|
+
if (!(typeof data?.name === "string" ? READ_TOOL_KIND[data.name] : void 0)) return;
|
|
71
|
+
const name = skillNameFromToolCall(data?.arguments);
|
|
71
72
|
if (!name) return;
|
|
72
73
|
this.observeRead(name).catch(() => {});
|
|
73
74
|
});
|
|
@@ -92,7 +93,15 @@ var SkillUsageRegistry = class extends Service {
|
|
|
92
93
|
if (viewsBefore === 0) await this.appendUsageWindowEvent(map);
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
|
-
/**
|
|
96
|
+
/**
|
|
97
|
+
* Append the observation-window anchor event; best-effort, never fails the
|
|
98
|
+
* observation.
|
|
99
|
+
*
|
|
100
|
+
* Lock-order contract (E-66): this acquires the evolution-events lock while
|
|
101
|
+
* the caller holds the usage lock (usage → events one-way nesting — see
|
|
102
|
+
* `mutate`). Never take the usage lock from inside a block that holds the
|
|
103
|
+
* events lock; that reverse nesting is a deadlock.
|
|
104
|
+
*/
|
|
96
105
|
async appendUsageWindowEvent(map) {
|
|
97
106
|
try {
|
|
98
107
|
const at = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -104,13 +113,21 @@ var SkillUsageRegistry = class extends Service {
|
|
|
104
113
|
counts: usageTotals(map),
|
|
105
114
|
window: { opened: at }
|
|
106
115
|
});
|
|
107
|
-
} catch {
|
|
116
|
+
} catch (error) {
|
|
117
|
+
this.ctx.logger.warn(`dsh-skill-usage: failed to append observation-window anchor: ${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
+
}
|
|
108
119
|
}
|
|
109
120
|
/**
|
|
110
121
|
* Serialize read-modify-write cycles so concurrent record calls never lose
|
|
111
122
|
* updates, then run each cycle as ONE atomic transact (rc.50 P2-2): the map
|
|
112
123
|
* is read from disk inside the lock, so a second process that shares
|
|
113
124
|
* DSH_HOME cannot interleave its own RMW between our read and write.
|
|
125
|
+
*
|
|
126
|
+
* Lock-order contract (E-66): this holds the usage (`.usage.json`) lock. A
|
|
127
|
+
* usage-locked `task` MAY acquire the evolution-events lock one-way
|
|
128
|
+
* (usage → events, e.g. via `appendUsageWindowEvent`); it must NEVER acquire
|
|
129
|
+
* it in the reverse direction. Two writers taking the two locks in opposite
|
|
130
|
+
* order would deadlock, so the one-way order is a binding invariant.
|
|
114
131
|
*/
|
|
115
132
|
mutate(task) {
|
|
116
133
|
const run = this.chain.then(async () => {
|
|
@@ -153,6 +170,19 @@ var SkillUsageRegistry = class extends Service {
|
|
|
153
170
|
});
|
|
154
171
|
}
|
|
155
172
|
/**
|
|
173
|
+
* 0.3.18 (E-70): create-path telemetry in ONE atomic RMW. The former
|
|
174
|
+
* prepare→markAgentCreated pair ran two full transacts (doubled lock
|
|
175
|
+
* traffic) and left a window where `created_by=null` was on disk between
|
|
176
|
+
* them. `agentCreated` picks the authorship marking at the same commit
|
|
177
|
+
* point where the record is created.
|
|
178
|
+
*/
|
|
179
|
+
async ensureRecordCreated(name, agentCreated) {
|
|
180
|
+
await this.mutate((map) => {
|
|
181
|
+
if (agentCreated) markAgentCreated(map, name);
|
|
182
|
+
else getRecord(map, name);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
156
186
|
* Mark a skill's usage record as archived (delete / curator archive paths).
|
|
157
187
|
* Unlike `record('patch')` this never bumps the patch counter — archiving is
|
|
158
188
|
* a state transition, not a content mutation.
|
package/lib/types/index.d.ts
CHANGED
|
@@ -36,13 +36,27 @@ export declare class SkillUsageRegistry extends Service {
|
|
|
36
36
|
* that anchor exists (curator gates churn on `usageObserved()`).
|
|
37
37
|
*/
|
|
38
38
|
private observeRead;
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Append the observation-window anchor event; best-effort, never fails the
|
|
41
|
+
* observation.
|
|
42
|
+
*
|
|
43
|
+
* Lock-order contract (E-66): this acquires the evolution-events lock while
|
|
44
|
+
* the caller holds the usage lock (usage → events one-way nesting — see
|
|
45
|
+
* `mutate`). Never take the usage lock from inside a block that holds the
|
|
46
|
+
* events lock; that reverse nesting is a deadlock.
|
|
47
|
+
*/
|
|
40
48
|
private appendUsageWindowEvent;
|
|
41
49
|
/**
|
|
42
50
|
* Serialize read-modify-write cycles so concurrent record calls never lose
|
|
43
51
|
* updates, then run each cycle as ONE atomic transact (rc.50 P2-2): the map
|
|
44
52
|
* is read from disk inside the lock, so a second process that shares
|
|
45
53
|
* DSH_HOME cannot interleave its own RMW between our read and write.
|
|
54
|
+
*
|
|
55
|
+
* Lock-order contract (E-66): this holds the usage (`.usage.json`) lock. A
|
|
56
|
+
* usage-locked `task` MAY acquire the evolution-events lock one-way
|
|
57
|
+
* (usage → events, e.g. via `appendUsageWindowEvent`); it must NEVER acquire
|
|
58
|
+
* it in the reverse direction. Two writers taking the two locks in opposite
|
|
59
|
+
* order would deadlock, so the one-way order is a binding invariant.
|
|
46
60
|
*/
|
|
47
61
|
private mutate;
|
|
48
62
|
record(name: string, kind: 'use' | 'view' | 'patch', at?: Date): Promise<void>;
|
|
@@ -56,6 +70,14 @@ export declare class SkillUsageRegistry extends Service {
|
|
|
56
70
|
* mutation maturity is not inflated by mere creation.
|
|
57
71
|
*/
|
|
58
72
|
ensureRecord(name: string): Promise<void>;
|
|
73
|
+
/**
|
|
74
|
+
* 0.3.18 (E-70): create-path telemetry in ONE atomic RMW. The former
|
|
75
|
+
* prepare→markAgentCreated pair ran two full transacts (doubled lock
|
|
76
|
+
* traffic) and left a window where `created_by=null` was on disk between
|
|
77
|
+
* them. `agentCreated` picks the authorship marking at the same commit
|
|
78
|
+
* point where the record is created.
|
|
79
|
+
*/
|
|
80
|
+
ensureRecordCreated(name: string, agentCreated: boolean): Promise<void>;
|
|
59
81
|
/**
|
|
60
82
|
* Mark a skill's usage record as archived (delete / curator archive paths).
|
|
61
83
|
* Unlike `record('patch')` this never bumps the patch counter — archiving is
|
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.3.
|
|
4
|
+
"version": "0.3.18",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|
|
@@ -33,18 +33,18 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
36
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
36
|
+
"@lmzhen/dsh-evolution-core": "^0.3.18"
|
|
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.3.
|
|
42
|
+
"@lmzhen/dsh-evolution-io": "^0.3.18"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.2",
|
|
46
46
|
"@deepseek-ai/dsh-session": "^0.1.1-rc.2",
|
|
47
|
-
"@lmzhen/dsh-evolution-core": "^0.3.
|
|
48
|
-
"@lmzhen/dsh-evolution-io": "^0.3.
|
|
47
|
+
"@lmzhen/dsh-evolution-core": "^0.3.18",
|
|
48
|
+
"@lmzhen/dsh-evolution-io": "^0.3.18"
|
|
49
49
|
}
|
|
50
50
|
}
|