dsh-ai-terminal-pet 1.2.4 → 1.2.6

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
@@ -19,42 +19,50 @@ maintainer pins a release in lib/release-pins.json, see below).
19
19
 
20
20
  ## What it does
21
21
 
22
- | Harness event | Overlay state |
22
+ | Harness signal | Overlay state |
23
23
  | --- | --- |
24
- | turn/start, agent/inbox/claimed, agent/status running | working |
25
- | tool/call | tool_use |
26
- | approval/asked, approval/request | waiting_permission |
27
- | tool/result with error, command/done error, agent/error | error |
28
- | turn/end, command/done success | idle |
24
+ | `turn/start`, `agent/inbox/claimed`, `agent/status` running | working |
25
+ | `tool/call` | tool_use |
26
+ | `approval/request` (waterfall listener) | waiting_permission |
27
+ | `tool/result` with `error`, `agent/error` | error |
28
+ | `turn/end`, `agent/status` idle | idle |
29
29
  | Harness process exit | closed |
30
30
 
31
+ Events this table does not list are not part of the Harness session log: there is
32
+ no `command/done` or `working-directory/change` in `SessionEventMap`. The project
33
+ path, the parent session and the session start time do not come from events at
34
+ all - they are read from `session.header` (`cwd`, `parentSession`, `createdAt`),
35
+ which is the only place the host states them. That is also why a session shows
36
+ the right project even though the desktop Harness itself runs from its install
37
+ directory, and why a subagent conversation is nested under the session it was
38
+ forked from.
39
+
31
40
  The overlay then plays an alert tone, shows a tray balloon on those transitions
32
41
  and its one-click jump button targets the Harness window. Plugin reload (HMR)
33
42
  disposes the listeners but deliberately does not close sessions: only a real
34
43
  process exit means the conversation is over.
35
44
 
36
- The bundle's entry in the [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
37
- registry ([PR #13937](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/13937)) was
38
- merged on 2026-11-10, and that repository's README lists it. Being merged is not the same as
39
- being visible in a market, and initially it was not: the entry page answered 404 and the catalog
40
- file behind DSH's own Plugins -> Market did not contain this repository while the upstream site
41
- build was still queued. That build finished (run 42844452013, 16:39:27Z), the entry page now
42
- answers 200, and the catalog (`count` 10119 -> 10230) lists this repository - so it can be found
43
- in that market. The entry's `install` command, however, is generated from the pinned `tarball`
44
- and pointed at the 1.2.0 asset; a follow-up PR moves it to the current release, which is 1.2.4.
45
- Changing that file always means a pull request against that repository, never an edit here.
46
-
47
- The other route needs no application at all: third-party markets harvest GitHub
48
- repositories carrying the `dsh-plugin` topic (this repository has it) together with
49
- a root `package.json` that declares `name` and `dsh.bundle`. That is exactly why
50
- the repository root carries a shim manifest — `dsh plugin add github:owner/repo`
51
- reads the root manifest, and without one the install resolves to a 0.0.0
52
- dependency that mounts nothing.
53
-
54
- The package describes itself to those storefronts through `dsh.catalog`: a
55
- category, a one-line summary in both languages, and the capabilities it uses.
56
- Capabilities are self-declared and never checked — DSH does not sandbox plugins —
57
- so read them as a description, not a permission grant.
45
+ ## Where it is listed, and what each market indexes
46
+
47
+ Three independent paths exist today, and each one reads a different field:
48
+
49
+ | Market | What it indexes | What this package offers it |
50
+ | --- | --- | --- |
51
+ | npm registry | package name, version, `dist.integrity` | published as `dsh-ai-terminal-pet`; a market that installs by name gets a verified tarball |
52
+ | dshmarket (the market tab most Harness Desktop users run) | `https://awesome-dsh-plugin.com/plugins.json`, which is generated from the [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) repository | entry merged via PR [#13937](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin/pull/13937); the `install` command it renders comes from that entry's pinned `tarball`, so bumping it is always a pull request *there*, never an edit here |
53
+ | GitHub-harvesting markets | repositories carrying the `dsh-plugin` topic plus a root `package.json` declaring `name` and `dsh.bundle` | this repository has the topic and the root shim manifest, which is what makes `dsh plugin add github:owner/repo` resolve instead of mounting a 0.0.0 dependency |
54
+
55
+ "Listed" and "visible" are different states, and only the second one helps a
56
+ user: the awesome entry existed before its `plugins.json` entry did, because the
57
+ site builds the catalog on a schedule. Check the market's own public index, not
58
+ the merged pull request. The package README cannot promise what another
59
+ repository's build will render; it can only promise that the fields those builds
60
+ read are present and correct.
61
+
62
+ The package describes itself to storefronts through `dsh.catalog`: a category, a
63
+ one-line summary in both languages, and the capabilities it uses. Capabilities
64
+ are self-declared and never checked — DSH does not sandbox plugins — so read them
65
+ as a description, not a permission grant.
58
66
  The bundle is Windows only and declares `dsh.bundle.platforms: ["win32"]`, so
59
67
  other platforms show it as unsupported instead of installing it.
60
68
 
@@ -64,17 +72,26 @@ that field - the bundle mounts through `dsh.bundle` either way - but storefronts
64
72
  that sort entries by profile do: without it a registry falls back to a
65
73
  headless-or-web guess and a desktop client hides the entry.
66
74
 
67
- Manually, from a terminal, into a profile you own — use the release tarball or
68
- pin the release tag. Both are verified end to end:
75
+ Manually, from a terminal, into a profile you own — by package name, by release
76
+ tarball, or by pinning the release tag. All three are verified end to end:
69
77
 
70
78
  ```sh
71
- # release tarball: no clone, no Go toolchain
72
- dsh plugin --profile <profile> add https://github.com/jjjjjjjjnnjnn/ai-terminal-pet/releases/download/dsh-plugin-v1.2.4/dsh-ai-terminal-pet-1.2.4.tgz
79
+ # by package name: published on npm, no clone, no Go toolchain
80
+ dsh plugin --profile <profile> add dsh-ai-terminal-pet
81
+
82
+ # release tarball: same bytes as the npm release, straight from the GitHub Release
83
+ dsh plugin --profile <profile> add https://github.com/jjjjjjjjnnjnn/ai-terminal-pet/releases/download/dsh-plugin-v1.2.6/dsh-ai-terminal-pet-1.2.6.tgz
73
84
 
74
85
  # git source, pinned to the release tag
75
- dsh plugin --profile <profile> add "git+https://github.com/jjjjjjjjnnjnn/ai-terminal-pet.git#dsh-plugin-v1.2.4&path:/packages/dsh-ai-terminal-pet"
86
+ dsh plugin --profile <profile> add "git+https://github.com/jjjjjjjjnnjnn/ai-terminal-pet.git#dsh-plugin-v1.2.6&path:/packages/dsh-ai-terminal-pet"
76
87
  ```
77
88
 
89
+ Those three differ only in transport. The npm path is the shortest and the one a
90
+ market uses when it installs by name; the tarball path works when a market pins a
91
+ release asset instead of a registry entry; the git path is the fallback for a
92
+ machine that cannot reach npm at all. Every one of them lands the same
93
+ prebuilt overlay in `vendor/`.
94
+
78
95
  Do not hand pnpm a bare repository URL. pnpm resolves a git dependency by
79
96
  running the package's lifecycle scripts, and it blocks those for unpinned
80
97
  git sources until you copy the `allowBuilds` key it prints into your profile's
@@ -85,9 +102,12 @@ SHA-256 of that file, the SHA-256 of the Go sources it was built from, and the
85
102
  Go version that produced it, so a stale or hand-edited vendor directory fails a
86
103
  check instead of shipping quietly.
87
104
 
88
- The GitHub paths above need no npm. Installing by package name
89
- (`add dsh-ai-terminal-pet`) starts working once the package is published to the
90
- npm registry; until then use one of the two commands above.
105
+ The GitHub and npm paths above need nothing from this repository at install time.
106
+ `dsh plugin add dsh-ai-terminal-pet` resolves through the npm registry; the
107
+ tarball and git forms resolve through GitHub. A marketplace that indexes npm
108
+ sees the package under its own name and can verify the published integrity
109
+ digest; a marketplace that indexes repositories sees the `dsh-plugin` topic and
110
+ the root shim manifest.
91
111
 
92
112
  `add` is the whole install: the profile records the dependency, and the bundle
93
113
  declares itself through `dsh.bundle.patch`, so no separate `enable` step exists.
package/lib/index.js CHANGED
@@ -125,20 +125,51 @@ function officialProtocol() {
125
125
  /** Per-conversation state. Kept at module scope so plugin HMR does not lose it. */
126
126
  const sessions = new Map()
127
127
 
128
- function metaFor(sessionId) {
128
+ /**
129
+ * The session header, whichever shape the caller was handed.
130
+ *
131
+ * `session/event` hands over the session object itself; other events hand over
132
+ * a wrapper with `.agent`. The header is the only place the Harness states a
133
+ * session's cwd, lineage and creation time, so everything below reads it from
134
+ * here instead of guessing.
135
+ */
136
+ function headerOf(candidate) {
137
+ return candidate?.session?.header ?? candidate?.header ?? candidate?.agent?.session?.header ?? null
138
+ }
139
+
140
+ /** What the header says about one candidate, with safe fallbacks. */
141
+ function sessionInfo(candidate) {
142
+ const header = headerOf(candidate)
143
+ const cwd = header && typeof header.cwd === 'string' ? header.cwd : ''
144
+ const parent = header && header.parentSession != null ? String(header.parentSession) : ''
145
+ const createdAt = header && Number.isFinite(header.createdAt) ? header.createdAt : 0
146
+ return { id: sessionIdOf(candidate), cwd, parent, createdAt }
147
+ }
148
+
149
+ function metaFor(sessionId, info) {
129
150
  let item = sessions.get(sessionId)
130
151
  if (!item) {
131
152
  item = {
132
153
  state: 'idle',
133
- project: safeCwd(),
154
+ // The session's own cwd, never the host process's: a desktop Harness runs
155
+ // from its install directory, so process.cwd() would label every session
156
+ // with the same wrong path.
157
+ project: info?.cwd || safeCwd(),
134
158
  title: TITLE,
135
159
  tool: '',
136
160
  reason: '',
137
- startedAt: new Date().toISOString(),
161
+ parentSessionId: info?.parent || '',
162
+ startedAt: new Date(info?.createdAt || Date.now()).toISOString(),
138
163
  stateAt: 0,
139
164
  sentAt: 0,
140
165
  }
141
166
  sessions.set(sessionId, item)
167
+ } else if (info) {
168
+ // The header is authoritative for a session that has not stated a project
169
+ // yet. An explicit project from an event wins for that report, which is
170
+ // what keeps a future cwd-change extension working.
171
+ if (!item.project && info.cwd) item.project = info.cwd
172
+ if (info.parent) item.parentSessionId = info.parent
142
173
  }
143
174
  return item
144
175
  }
@@ -166,25 +197,47 @@ function sessionIdOf(...candidates) {
166
197
  return 'dsh-main'
167
198
  }
168
199
 
200
+ /**
201
+ * What makes one report different from the previous one.
202
+ *
203
+ * The state alone is not enough: a patch can move the project, the reason, the
204
+ * tool or the lineage without touching the state, and the panel shows all of
205
+ * them. De-duplicating on state alone silently dropped those updates. Identical
206
+ * repeats are still dropped, which is what keeps a tool that fires fifty events
207
+ * per second from flooding the pipe.
208
+ */
209
+ function signatureOf(item) {
210
+ return [
211
+ item.state,
212
+ item.reason || '',
213
+ item.tool || '',
214
+ item.project || '',
215
+ item.title || '',
216
+ item.parentSessionId || '',
217
+ ].join('\u0000')
218
+ }
219
+
169
220
  /**
170
221
  * Publish one status. The protocol module writes the durable snapshot
171
222
  * synchronously before its first await, so calling this from process.on('exit')
172
223
  * still leaves a correct closed-state snapshot even when the pipe is gone.
173
224
  */
174
- function report(sessionId, patch, force = false) {
225
+ function report(sessionId, patch, force = false, info = null) {
175
226
  const p = officialProtocol()
176
227
  if (!p || typeof p.sendStatus !== 'function') return
177
- const item = metaFor(sessionId)
178
- const previous = item.state
228
+ const item = metaFor(sessionId, info)
229
+ const previousState = item.state
230
+ const previousSignature = signatureOf(item)
179
231
  Object.assign(item, patch)
180
232
  if (!p.STATES.has(item.state)) item.state = 'idle'
181
233
 
182
234
  const now = Date.now()
183
- const changed = item.state !== previous
235
+ const changed = signatureOf(item) !== previousSignature
236
+ const stateChanged = item.state !== previousState
184
237
  const stale = now - item.sentAt >= KEEP_ALIVE_MS
185
238
  if (!force && !changed && !stale) return
186
239
  item.sentAt = now
187
- if (changed) item.stateAt = now
240
+ if (stateChanged) item.stateAt = now
188
241
 
189
242
  const stablePid = typeof p.stableProcessId === 'function'
190
243
  ? p.stableProcessId(SOURCE + ':' + sessionId)
@@ -194,7 +247,7 @@ function report(sessionId, patch, force = false) {
194
247
  schemaVersion: p.SCHEMA_VERSION,
195
248
  source: SOURCE,
196
249
  sessionId,
197
- parentSessionId: '',
250
+ parentSessionId: item.parentSessionId || '',
198
251
  // Synthetic per-conversation pid (never used for OS liveness) plus the
199
252
  // real Harness host pid as the ancestor-chain anchor.
200
253
  pid: stablePid,
@@ -219,6 +272,17 @@ function report(sessionId, patch, force = false) {
219
272
  }
220
273
  }
221
274
 
275
+ /**
276
+ * Report for whatever object an event handler was given, after folding in what
277
+ * its session header says. Every handler goes through here so the project path
278
+ * and the parent link are never taken from the host process or invented per
279
+ * event type.
280
+ */
281
+ function reportFor(candidate, patch, force = false) {
282
+ const info = sessionInfo(candidate)
283
+ return report(info.id, patch, force, info)
284
+ }
285
+
222
286
  /** Copy one file into place through a temp sibling, never replacing a file. */
223
287
  function copyVerified(source, destination, mode) {
224
288
  let temp = ''
@@ -436,7 +500,24 @@ function bootstrap(ctx) {
436
500
  return bootstrapPromise
437
501
  }
438
502
 
439
- /** Map the durable session events documented in Harness' SessionEventMap. */
503
+ /**
504
+ * Map one durable session event onto a state patch.
505
+ *
506
+ * The authoritative list lives in @deepseek-ai/dsh-session's SessionEventMap
507
+ * (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`,
508
+ * `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`,
509
+ * `todo/write`, `request/header`, `request/context`, `session/end-seed`), which
510
+ * this switch covers for every event that says something about activity.
511
+ *
512
+ * `working-directory/change` and `session/title` are NOT in that map: the first
513
+ * does not exist anywhere in the host, the second is a client-side event. Both
514
+ * branches are kept as cheap forward-compatibility - if a future version emits
515
+ * them, they map correctly - but the project path and the lineage come from
516
+ * session.header (see reportFor), which is the only place the host states them.
517
+ * `approval/asked` / `approval/decided` likewise come from the approval service
518
+ * rather than the session log; the approval/request waterfall listener in
519
+ * apply() is the path that actually fires.
520
+ */
440
521
  function eventPatch(type, data) {
441
522
  switch (type) {
442
523
  case 'approval/asked':
@@ -496,26 +577,25 @@ export function apply(ctx, config) {
496
577
  }
497
578
 
498
579
  on('session/event', (session, event) => {
499
- const sessionId = sessionIdOf(session)
500
580
  const patch = eventPatch(event?.type, event?.data)
501
- if (patch) report(sessionId, patch)
581
+ if (patch) reportFor(session, patch)
502
582
  })
503
583
 
504
584
  on('agent/inbox/claimed', (payload) => {
505
- report(sessionIdOf(payload?.agent), { state: 'working', reason: '', tool: '' })
585
+ reportFor(payload?.agent, { state: 'working', reason: '', tool: '' })
506
586
  })
507
587
 
508
588
  on('agent/status', (payload) => {
509
589
  const status = String(payload?.status ?? '').toLowerCase()
510
590
  if (/run|work|busy|think|stream/.test(status)) {
511
- report(sessionIdOf(payload?.agent), { state: 'working' })
591
+ reportFor(payload?.agent, { state: 'working' })
512
592
  } else if (/idle|done|wait|stop/.test(status)) {
513
- report(sessionIdOf(payload?.agent), { state: 'idle' })
593
+ reportFor(payload?.agent, { state: 'idle' })
514
594
  }
515
595
  })
516
596
 
517
597
  on('agent/error', (payload) => {
518
- report(sessionIdOf(payload?.agent), {
598
+ reportFor(payload?.agent, {
519
599
  state: 'error',
520
600
  reason: short(payload?.error?.message ?? payload?.error, 120),
521
601
  })
@@ -524,7 +604,7 @@ export function apply(ctx, config) {
524
604
  // approval/request is a waterfall listener: it must always call next(),
525
605
  // otherwise other plugins' approval handling is silently dropped.
526
606
  on('approval/request', (request, next) => {
527
- report(sessionIdOf(request?.agent), {
607
+ reportFor(request?.agent, {
528
608
  state: 'waiting_permission',
529
609
  reason: 'permission required',
530
610
  tool: short(request?.toolName, 120),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ai-terminal-pet",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "DeepSeek Harness Cordis bundle that mirrors session state into the AI Terminal Pet desktop overlay (Windows).",
5
5
  "license": "MIT",
6
6
  "type": "module",
Binary file
@@ -3,13 +3,13 @@
3
3
  "generatedBy": "scripts/vendor.mjs",
4
4
  "goVersion": "go1.27.0",
5
5
  "source": {
6
- "files": 25,
7
- "sha256": "cf647af1aa1f45d1ed6f0df7b97e7e1439272f87064009e1989f1bfbd63db29a"
6
+ "files": 24,
7
+ "sha256": "6e2a4511fa4a77c15160a3cb861d77eaff30982d9a6dcafb0b11cb025a5dd454"
8
8
  },
9
9
  "overlay": {
10
10
  "file": "vendor/AI-Terminal-Pet.exe",
11
11
  "bytes": 4371968,
12
- "sha256": "bbb5163c296cd460d7c9a4c85e10342e125c0bd8017a065f913aa01567d41f0a"
12
+ "sha256": "cd9011ddf98ebb051d2ccd568913bba89525e6d067e9306bb22a85a8951504ef"
13
13
  },
14
14
  "protocol": {
15
15
  "file": "vendor/protocol.js",
@@ -0,0 +1 @@
1
+ { "type": "commonjs" }