dsh-cc-loader 0.3.0 → 0.3.2
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/cordis.patch.yml +5 -0
- package/package.json +7 -1
- package/src/index.js +5 -0
- package/src/message-source.js +46 -0
- package/src/patterns.js +20 -2
- package/src/session-start-compat.js +72 -0
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# dsh-cc-loader is a library, not a plugin: it contributes no row, only the
|
|
2
|
+
# shared parse layer the other dsh-cc packages import. This empty patch exists
|
|
3
|
+
# so the package stays installable through `dsh plugin` / the plugin manager,
|
|
4
|
+
# which refuse any package that declares no `dsh.bundle`.
|
|
5
|
+
[]
|
package/package.json
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-cc-loader",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Shared parse layer for the dsh-cc ecosystem: parses Claude Code .claude/ (project + global) into a standalone .dsh intermediate representation (IR), classifying every component DIRECT/ADAPTED/UNSUPPORTED/BLOCKED and filtering unsupported ones out.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js"
|
|
9
9
|
},
|
|
10
|
+
"dsh": {
|
|
11
|
+
"bundle": {
|
|
12
|
+
"patch": "./cordis.patch.yml"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
10
15
|
"files": [
|
|
11
16
|
"src",
|
|
17
|
+
"cordis.patch.yml",
|
|
12
18
|
"README.md"
|
|
13
19
|
],
|
|
14
20
|
"keywords": [
|
package/src/index.js
CHANGED
|
@@ -42,3 +42,8 @@ export {
|
|
|
42
42
|
// Session event-log readers. `Session.events` was removed in DSH 0.1.2-alpha.4
|
|
43
43
|
// and is gone in 0.1.5; every plugin read of the log goes through these.
|
|
44
44
|
export { sessionEvents, sessionEventAt, sessionLastEvent } from './session-compat.js'
|
|
45
|
+
// "A session has started" moved from `agent/session-start` (0.1.5) to
|
|
46
|
+
// `agent/created` + `source` (0.2.0). This boundary is the only place that knows.
|
|
47
|
+
export { onSessionStart } from './session-start-compat.js'
|
|
48
|
+
// What `source` an injected message must carry for the host's v4 admission gate.
|
|
49
|
+
export { injectedSource } from './message-source.js'
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// message-source.js — the single place that knows what `source` a message this
|
|
2
|
+
// ecosystem injects must carry for the host to admit it.
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS EXISTS
|
|
5
|
+
// ---------------
|
|
6
|
+
// DSH's session format v4 has an admission gate that validates the `source` of
|
|
7
|
+
// EVERY durable message slot before the event is adopted. Its whole rule
|
|
8
|
+
// (`lib/types/message-sources.js`) is:
|
|
9
|
+
//
|
|
10
|
+
// function source(message) {
|
|
11
|
+
// const value = message["source"]
|
|
12
|
+
// if (!isSessionFormatJsonObject(value)
|
|
13
|
+
// || typeof value["kind"] !== "string"
|
|
14
|
+
// || value["kind"].length === 0
|
|
15
|
+
// || value["kind"] === "plugin")
|
|
16
|
+
// throw new SessionFormatError("format v4 message requires a producer-owned source kind")
|
|
17
|
+
// }
|
|
18
|
+
//
|
|
19
|
+
// The line of policy above it settles what that means: "Native source admission
|
|
20
|
+
// PRESERVES UNKNOWN ATTRIBUTION and REFUSES RETIRED PLUGIN WRAPPERS." So any
|
|
21
|
+
// non-empty `kind` other than `plugin` is admitted and kept verbatim — you do
|
|
22
|
+
// not have to register your name with the host. The single refused form is the
|
|
23
|
+
// V3 plugin wrapper `{ kind: 'plugin', plugin: '<name>' }`, and refusing it
|
|
24
|
+
// fails the whole TURN, not just that message.
|
|
25
|
+
//
|
|
26
|
+
// `kind` doubles as the context-row label, so it should name the producer.
|
|
27
|
+
//
|
|
28
|
+
// WHAT WENT WRONG
|
|
29
|
+
// ---------------
|
|
30
|
+
// cc-hooks and cc-agents shipped the V3 wrapper, so the first time either one
|
|
31
|
+
// injected anything — SessionStart context, hook-injected context, the catalog
|
|
32
|
+
// reminder, a prompt/agent hook result — the turn died with
|
|
33
|
+
// "format v4 message requires a producer-owned source kind". The constant was
|
|
34
|
+
// duplicated across three files, which is why the shape can drift again; this
|
|
35
|
+
// module is the one place it is written down.
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Build the `source` for a message this ecosystem injects.
|
|
39
|
+
*
|
|
40
|
+
* @param {string} kind - the producing plugin; also the context-row label.
|
|
41
|
+
* Must be non-empty and must not be `'plugin'`.
|
|
42
|
+
* @returns {{ kind: string }} an admitted source.
|
|
43
|
+
*/
|
|
44
|
+
export function injectedSource(kind) {
|
|
45
|
+
return { kind }
|
|
46
|
+
}
|
package/src/patterns.js
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
// `./` or bare = relative to cwd.
|
|
11
11
|
// - Domain (WebFetch): `*` matches text between dots (a leading `*.` spans
|
|
12
12
|
// any subdomain depth; a bare `*` matches everything).
|
|
13
|
+
//
|
|
14
|
+
// `[...]` character classes belong to the PATH dialect alone: CC's command and
|
|
15
|
+
// domain patterns have no class syntax, so `[` is a literal character there.
|
|
13
16
|
|
|
14
17
|
/** Escape a literal string for use inside a RegExp. */
|
|
15
18
|
export function escapeRegExp(s) {
|
|
@@ -35,9 +38,10 @@ export function globToRegexBody(pattern, { segment = '/' } = {}) {
|
|
|
35
38
|
} else if (ch === '?') {
|
|
36
39
|
out += seg === null ? '.' : `[^${seg}]`
|
|
37
40
|
i++
|
|
38
|
-
} else if (ch === '[') {
|
|
41
|
+
} else if (ch === '[' && seg === '/') {
|
|
39
42
|
const close = pattern.indexOf(']', i + 1)
|
|
40
|
-
|
|
43
|
+
const cls = close > 0 ? pattern.slice(i, close + 1) : ''
|
|
44
|
+
if (cls !== '' && isUsableCharClass(cls)) { out += cls; i = close + 1 }
|
|
41
45
|
else { out += '\\['; i++ }
|
|
42
46
|
} else {
|
|
43
47
|
out += escapeRegExp(ch)
|
|
@@ -47,6 +51,20 @@ export function globToRegexBody(pattern, { segment = '/' } = {}) {
|
|
|
47
51
|
return out
|
|
48
52
|
}
|
|
49
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Whether a glob `[...]` slice is simultaneously a VALID regex character class.
|
|
56
|
+
*
|
|
57
|
+
* Gitignore classes and JS regex classes mostly agree, but a reversed range
|
|
58
|
+
* such as `[11-04-54]` (read as `1-0`) is tolerated by gitignore and rejected
|
|
59
|
+
* by `new RegExp`. Class compilation happens on every tool call, so letting one
|
|
60
|
+
* such class throw would take down the whole permission gate; an unusable class
|
|
61
|
+
* degrades to a literal `[` instead.
|
|
62
|
+
* @param {string} cls - the slice from `[` through `]` inclusive.
|
|
63
|
+
*/
|
|
64
|
+
function isUsableCharClass(cls) {
|
|
65
|
+
try { new RegExp(cls); return true } catch { return false }
|
|
66
|
+
}
|
|
67
|
+
|
|
50
68
|
/**
|
|
51
69
|
* Compile a Bash/PowerShell command pattern.
|
|
52
70
|
*
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// session-start-compat.js — the single compatibility boundary for the
|
|
2
|
+
// "a session has started" extension point.
|
|
3
|
+
//
|
|
4
|
+
// WHY THIS EXISTS
|
|
5
|
+
// ---------------
|
|
6
|
+
// DSH 0.2.0 moved this extension point and enriched its payload:
|
|
7
|
+
//
|
|
8
|
+
// 0.1.5-rc.2 agent/session-start { agent, source }
|
|
9
|
+
// 0.2.0-rc.2 agent/created { agent, source, signal? }
|
|
10
|
+
//
|
|
11
|
+
// `agent/session-start` is simply GONE in 0.2.0 — a listener on that name is
|
|
12
|
+
// never called, with no error at any point, so a plugin loads, passes the
|
|
13
|
+
// install gate, and silently loses the feature. `agent/created` does exist on
|
|
14
|
+
// 0.1.5, but there the payload is `{ agent }` alone: no `source`. The two names
|
|
15
|
+
// are therefore NOT aliases, and a plain rename trades one silent break for
|
|
16
|
+
// another (the SessionStart hook payload would lose its `source`).
|
|
17
|
+
//
|
|
18
|
+
// The discriminator is `source`: 0.2.0's `agent/created` always carries one (the
|
|
19
|
+
// registry announces as `announce(agent, source, signal)`), 0.1.5's never does,
|
|
20
|
+
// and 0.1.5's `agent/session-start` always does. Listening on both and acting
|
|
21
|
+
// only on a payload that carries `source` fires the handler exactly once on
|
|
22
|
+
// either generation, with no bookkeeping: the host calls its publication entry
|
|
23
|
+
// once per agent (`publish(source)`), and 0.2.0's registry rejects a second
|
|
24
|
+
// announce for the same entry.
|
|
25
|
+
//
|
|
26
|
+
// The official `@deepseek-ai/dsh-hooks-claude-code@0.2.0-rc.2` bridge makes the
|
|
27
|
+
// same move, dropping its `agent/session-start` listener for `agent/created`.
|
|
28
|
+
//
|
|
29
|
+
// SECOND HAZARD: 0.2.0's `agent/created` is a SERIAL event — its listeners are
|
|
30
|
+
// awaited, and a listener rejection vetoes session publication. 0.1.5's was
|
|
31
|
+
// synchronous and fire-and-forget. Session-start work here is slow (filesystem
|
|
32
|
+
// discovery, spawned hook processes), so this boundary deliberately does NOT
|
|
33
|
+
// return the handler's promise and never lets a throw escape: a plugin must not
|
|
34
|
+
// be able to stall or veto the host's session publication.
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Subscribe to "a session has started", on either host generation.
|
|
38
|
+
*
|
|
39
|
+
* The handler runs at most once per agent with `{ agent, source, signal }`.
|
|
40
|
+
* `source` is one of `'startup' | 'resume' | 'clear' | 'compact'` and is
|
|
41
|
+
* present on both generations this boundary supports.
|
|
42
|
+
*
|
|
43
|
+
* The handler runs detached and its result is ignored — neither a throw nor a
|
|
44
|
+
* rejection reaches the host (see the header). A failure is logged instead.
|
|
45
|
+
*
|
|
46
|
+
* @param {{ on: Function, logger?: any }} ctx - the plugin context.
|
|
47
|
+
* @param {(payload: { agent: any, source: string, signal?: AbortSignal }) => unknown} handler
|
|
48
|
+
*/
|
|
49
|
+
export function onSessionStart(ctx, handler) {
|
|
50
|
+
const invoke = (payload) => {
|
|
51
|
+
try {
|
|
52
|
+
const result = handler(payload)
|
|
53
|
+
if (result !== null && typeof result === 'object' && typeof result.then === 'function') {
|
|
54
|
+
result.then(undefined, (error) => warn(ctx, error))
|
|
55
|
+
}
|
|
56
|
+
} catch (error) {
|
|
57
|
+
warn(ctx, error)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
ctx.on('agent/session-start', invoke)
|
|
61
|
+
ctx.on('agent/created', (payload) => {
|
|
62
|
+
// 0.1.5's `agent/created` is registration-only and carries no `source`;
|
|
63
|
+
// the session-start event that does carry one follows it immediately.
|
|
64
|
+
// Acting here as well would run the handler twice.
|
|
65
|
+
if (payload === null || typeof payload !== 'object' || payload.source === undefined) return
|
|
66
|
+
invoke(payload)
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function warn(ctx, error) {
|
|
71
|
+
ctx.logger?.warn?.(`dsh-cc-loader: session-start listener failed: ${String(error)}`)
|
|
72
|
+
}
|