@openplan/dsh-fuse 0.1.0 → 0.1.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 CHANGED
@@ -15,12 +15,12 @@ O pacote declara `dsh.bundle`, que é o que faz `dsh plugin add` ativar a
15
15
  camada. Sem essa declaração o pnpm instalaria uma biblioteca inerte.
16
16
 
17
17
  ```bash
18
- # npm — a forma canônica de instalação (publicado, v0.1.0)
18
+ # npm — a forma canônica de instalação (publicado)
19
19
  dsh plugin --profile <perfil> add @openplan/dsh-fuse
20
20
 
21
21
  # tarball (sem depender de registry)
22
22
  pnpm pack # gera openplan-dsh-fuse-<versão>.tgz
23
- dsh plugin --profile <perfil> add ./openplan-dsh-fuse-0.1.0.tgz
23
+ dsh plugin --profile <perfil> add ./openplan-dsh-fuse-<versão>.tgz
24
24
 
25
25
  # direto do git (exige allowlist de build do pnpm >= 10 — veja a doc do harness)
26
26
  dsh plugin --profile <perfil> add github:<org>/<repo>#<sha>
package/dist/harness.js CHANGED
@@ -98,6 +98,45 @@ export function apply(ctx, config) {
98
98
  const logger = ctx.logger;
99
99
  const tokenMeter = () => ctx.get?.("tokenMeter") ?? undefined;
100
100
  const llm = () => ctx.get?.("llm") ?? undefined;
101
+ /**
102
+ * In-session cut notices: when the fuse blocks a step, the harness ends the
103
+ * turn as `{ kind: "blocked" }` with no visible explanation — the user is
104
+ * told nothing. The harness's own convention for "something just happened"
105
+ * is a plugin-producer `notice`-form user message (`agent.inject`), which
106
+ * the client conversation renders as a collapsed one-line row expandable to
107
+ * the full text. One notice per (rule, window) per process: a budget that
108
+ * stays tripped must not restate itself on every blocked step, and a new
109
+ * window that re-trips it should announce that again.
110
+ */
111
+ const cutNotices = new Set();
112
+ function notifyCut(agent, input) {
113
+ const window = windowStart(new Date(), "day");
114
+ const key = `${input.rule}\u0000${window}`;
115
+ if (cutNotices.has(key))
116
+ return;
117
+ cutNotices.add(key);
118
+ try {
119
+ agent.inject({
120
+ id: crypto.randomUUID(),
121
+ role: "user",
122
+ content: [{ type: "text", text: input.detail }],
123
+ source: {
124
+ kind: "plugin",
125
+ plugin: "@openplan/dsh-fuse",
126
+ form: "notice",
127
+ summary: input.summary,
128
+ },
129
+ });
130
+ }
131
+ catch (error) {
132
+ // A notice must never break the reject path — the cut already landed
133
+ // in the store; the UI alert is best-effort.
134
+ logger.warn("[dsh] cut notice not delivered", {
135
+ rule: input.rule,
136
+ error: String(error),
137
+ });
138
+ }
139
+ }
101
140
  /**
102
141
  * Documented load-time rule: *"A plugin should also reject schema-valid
103
142
  * config that names an unavailable resource or provider as soon as it can
@@ -532,6 +571,11 @@ export function apply(ctx, config) {
532
571
  resetAt: remoteBlock.resetAt,
533
572
  project,
534
573
  });
574
+ notifyCut(payload.agent, {
575
+ rule: remoteBlock.rule,
576
+ summary: "fuse: chamadas bloqueadas (orçamento do painel)",
577
+ detail: `O painel central cortou as chamadas deste escopo (regra: ${remoteBlock.rule}). O bloqueio vale até ${remoteBlock.resetAt} — o fuse local segue ativo e nenhum token é gasto enquanto isso.`,
578
+ });
535
579
  return { kind: "reject" };
536
580
  }
537
581
  const estimatedCostUsd = estimateStepCostUsd(payload.agent, payload.messages, model, provider);
@@ -565,6 +609,11 @@ export function apply(ctx, config) {
565
609
  rule: decision.rule,
566
610
  project,
567
611
  });
612
+ notifyCut(payload.agent, {
613
+ rule: decision.rule ?? "unknown",
614
+ summary: "fuse: chamada cortada — orçamento atingido",
615
+ detail: `O fuse bloqueou a chamada antes de gastar tokens (regra: ${decision.rule ?? "unknown"}). Ajuste o budget em cordis.patch.yml ou use a ferramenta dsh_budget_status para ver o status; o limite reseta no fim da janela.`,
616
+ });
568
617
  return { kind: "reject" };
569
618
  }
570
619
  return next();
package/package.json CHANGED
@@ -1,64 +1,64 @@
1
1
  {
2
- "name": "@openplan/dsh-fuse",
3
- "version": "0.1.0",
4
- "description": "Cost policy for the DeepSeek Harness: per-call metering plus a local fuse that enforces budget, model and reasoning-effort limits before any token is spent.",
5
- "license": "MIT",
6
- "type": "module",
7
- "private": false,
8
- "publishConfig": {
9
- "access": "public"
10
- },
11
- "files": [
12
- "dist",
13
- "cordis.patch.yml",
14
- "README.md"
15
- ],
16
- "engines": {
17
- "node": ">=20"
18
- },
19
- "main": "./dist/index.js",
20
- "types": "./dist/index.d.ts",
21
- "exports": {
22
- ".": {
23
- "types": "./dist/index.d.ts",
24
- "default": "./dist/index.js"
25
- }
26
- },
27
- "keywords": [
28
- "dsh",
29
- "dsh-plugin",
30
- "llm",
31
- "budget",
32
- "cost",
33
- "enforcement",
34
- "cordis"
35
- ],
36
- "scripts": {
37
- "build": "tsc -p tsconfig.build.json",
38
- "typecheck": "tsc --noEmit",
39
- "test": "vitest run",
40
- "prepublishOnly": "pnpm build"
41
- },
42
- "dsh": {
43
- "bundle": {
44
- "patch": "./cordis.patch.yml"
45
- }
46
- },
47
- "dependencies": {
48
- "@deepseek-ai/schemastery": "3.18.2",
49
- "@libsql/client": "^0.17.4"
50
- },
51
- "peerDependencies": {
52
- "@deepseek-ai/cordis": "4.0.1"
53
- },
54
- "devDependencies": {
55
- "@deepseek-ai/cordis": "4.0.1",
56
- "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
57
- "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
58
- "@deepseek-ai/dsh-session": "0.1.1-rc.2",
59
- "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
60
- "@types/node": "^22.0.0",
61
- "typescript": "^5.9.0",
62
- "vitest": "^3.1.0"
63
- }
2
+ "name": "@openplan/dsh-fuse",
3
+ "version": "0.1.1",
4
+ "description": "Cost policy for the DeepSeek Harness: per-call metering plus a local fuse that enforces budget, model and reasoning-effort limits before any token is spent.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "private": false,
8
+ "publishConfig": {
9
+ "access": "public"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "cordis.patch.yml",
14
+ "README.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "main": "./dist/index.js",
20
+ "types": "./dist/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./dist/index.d.ts",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "keywords": [
28
+ "dsh",
29
+ "dsh-plugin",
30
+ "llm",
31
+ "budget",
32
+ "cost",
33
+ "enforcement",
34
+ "cordis"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsc -p tsconfig.build.json",
38
+ "typecheck": "tsc --noEmit",
39
+ "test": "vitest run",
40
+ "prepublishOnly": "pnpm build"
41
+ },
42
+ "dsh": {
43
+ "bundle": {
44
+ "patch": "./cordis.patch.yml"
45
+ }
46
+ },
47
+ "dependencies": {
48
+ "@deepseek-ai/schemastery": "3.18.2",
49
+ "@libsql/client": "^0.17.4"
50
+ },
51
+ "peerDependencies": {
52
+ "@deepseek-ai/cordis": "4.0.1"
53
+ },
54
+ "devDependencies": {
55
+ "@deepseek-ai/cordis": "4.0.1",
56
+ "@deepseek-ai/dsh-agent": "0.1.1-rc.2",
57
+ "@deepseek-ai/dsh-llm": "0.1.1-rc.2",
58
+ "@deepseek-ai/dsh-session": "0.1.1-rc.2",
59
+ "@deepseek-ai/dsh-tools": "0.1.1-rc.2",
60
+ "@types/node": "^22.0.0",
61
+ "typescript": "^5.9.0",
62
+ "vitest": "^3.1.0"
63
+ }
64
64
  }