Skip to content

Releases: sakuraqqq/dsh-auto-paste

v0.3.2

Choose a tag to compare

@sakuraqqq sakuraqqq released this 25 Oct 17:08
1539d01

Release 0.3.2

(无 changelog)

v0.3.1

Choose a tag to compare

@sakuraqqq sakuraqqq released this 25 Oct 17:05
83bebac

Release 0.3.1

Changelog:
04a2060 fix: route paste by session cwd; declare 0.2.1-rc.2/rc.3
0c5ec71 Merge pull request #21 from sakuraqqq/docs/readme-hero
47a686f docs: rewrite README hero, add demo gif and star CTA

v0.3.0

Choose a tag to compare

@sakuraqqq sakuraqqq released this 17 Oct 06:48
0527542

dsh-auto-paste v0.3.0

Two review findings fixed, and the plugin now ships zero runtime dependencies — which is what DSH STORE's admission policy requires.

Why a minor bump

The dependency shape changed (no dependencies at all; artifacts are now single-file bundles) and two behaviour fixes landed. Under 0.x semantics that is a minor.

What changed

R1 — the settings row was read-only on the dsh 0.1.5 line

0.2.4 fixed the settings round-trip but, in doing so, ANDed .volatile into the overall read capability. .volatile() does not exist on dsh 0.1.5 (schemastery 3.18.2), so canConfigure() returned false there and the row silently degraded to read-only — while package.json still declared ^0.1.5-rc.1.

The two lines close the loop differently, and the check now says so:

if (typeof settings.get === 'function') return true                      // 0.1.5: get + update alone
return typeof settings.describe === 'function' && schemaVolatileCapable  // 0.1.7+: both required

R2 — the serialization timeout trusted the callee

0.2.4 bounded the await with an AbortController. But abort() only notifies: a callee that ignores the signal leaves the promise pending forever, and by then the caller has already called preventDefault — so the user gets no chip, no text and no message while the file is already on disk. The bound is now owned by this side:

await Promise.race([controller?.serializeReference?.(…, bounds.signal), expired])

abort stays for cooperative cancellation, but it is no longer the only defence.

Both guards were rewritten to assert the property, not the spelling. The D2 guard previously accepted any of Promise.race / a setTimeout+abort / the timeout constant — which is exactly why an abort-only implementation passed it. It now demands Promise.race and forbids a bare await serializeReference(…). The canConfigure guard now asserts that .volatile may only ever guard the describe() path.

Runtime dependencies: now zero

zod is removed from dependencies and compiled into dist/typert.host.js by scripts/bundle.mjs (esbuild); it stays in devDependencies for the build.

Why inline rather than pin: DSH STORE's automation policy carries allowRuntimeDependencies: false, and all 27 already-approved catalog entries declare no dependencies at all. Pinning an exact version does not help — the objection is to the dependency edge, not to the range.

dist/index.js        17,337 B   (was 30,827 — shrinks: no zod, tree-shaken)
dist/typert.host.js 547,016 B   (was  5,369 — zod inlined)

Platform packages stay external (@deepseek-ai/*) because dsh supplies them through peerDependencies. THIRD_PARTY_NOTICES.md carries zod's full MIT text, and the bundle emits a banner with the copyright line — esbuild's legalComments cannot preserve zod's own notice, because its sources use plain // comments rather than /*! … */ legal comments.

Compatibility

dsh.compatibility.dshReleases now records 0.2.1-alpha.5: compatible, measured in a disposable profile on the real runtime — install, boot and uninstall all evidenced.

The dsh peer range is unchanged (^0.1.5-rc.1 || ^0.2.0-rc.2): dsh validates peers with includePrerelease, so ^0.2.0-rc.2 already covers the 0.2.1-alpha.* line. (This release corrects an earlier assumption of mine that it would not.)

Verification

tests      117 total · 111 pass · 6 fail

The 6 are spawnSync-based and fail only in a restricted agent sandbox that forbids child pipes; they fail identically on the previous release's commit.

lint       0 errors
prettier   All matched files use Prettier code style
metrics    6 files · 108 functions · 0 over limit
privacy    PASS — no identity, history or package finding (11 files ship)

Install

dsh plugin --profile <your-profile> add dsh-auto-paste@0.3.0

Full diff: v0.2.4...v0.3.0

v0.2.4

Choose a tag to compare

@sakuraqqq sakuraqqq released this 14 Oct 14:26
5fab0ff

dsh-auto-paste v0.2.4

Ten defects surfaced by an independent review, fixed red-first — each one was reproduced by a failing example before any code changed.

Client half

# What was wrong What it is now
D1 The outer .catch wrapped the whole save chain. A throw from the toast or the one-shot hint — after the chip had already been inserted — fell into "we lost your paste" and re-inserted the raw text: the chip plus the entire chunk in the composer at once, which is the exact shape this plugin exists to prevent. A flag marks the point where the paste is already on disk. Past it, a UI failure is reported as a UI failure instead of being "recovered" wrongly.
D2 serializeReference had no timeout, while the two other RPCs in the same file both do. The caller has already called preventDefault by then, so a serializer that never settles left no chip, no text and no message — the paste looked lost even though the file was written. Bounded, like its two neighbours.
D3 composerInsertion answered null five different ways in total silence, so a chip that quietly stopped working left nothing to debug with. One one-shot warning naming the reason. The null contract is unchanged, so behaviour is identical — only the diagnosis is new.

Host half

# What was wrong What it is now
HD4 minCharsSource was permanently 'deployment'. The plugin read the user layer back through settings.get() only — and the 0.1.7+ settings service has no get at all. So a saved preference was invisible to the plugin: the row's "reset to default" button could never render, and its copy kept saying the value came from the deployment — including immediately after a successful save. Both read paths are probed: get where it exists, and describe().user on the lines that project settings out of each plugin's own Config. canConfigure now requires a readable layer, not merely a writable one.
HD5 The wire contract had degraded to z.string(), which let the 'user' | 'deployment' union evaporate at exactly the boundary. Narrowed to an enum, so a typo in the client's comparison can no longer compile past the wire.
HD6 The save_paste tool handed its boot-time maxBytes snapshot to the writer, while the service re-reads the config on every call — and the comment claimed only the service path mattered. The tool re-reads too, so both halves enforce the same ceiling.
HD7 savePaste documents that a direct caller can hand it anything and it will refuse — but only text was guarded. sessionId is what decides which workspace a paste lands in. Guarded on the same terms.
HD8 The same-second collision retry loop was unbounded, so a directory full of squatters turned a paste into a hang rather than an error. Bounded, with a real error past the cap.
HD9 isRegisteredWorkspace compared raw spellings, so ws/./x was refused as "not registered". Paths are normalised first — a pure string operation, no filesystem hit, and symlinks are deliberately left alone.

One more line changed in tools/privacy-gate.mjs: a comment explaining which patterns that scanner deliberately ignores restated those patterns verbatim. DSH STORE runs its own regular expressions over source text without stripping comments, so on the next rescan that line would have matched a permissions signal. The reasoning is kept; the literal strings are not.

Guards

The reviewer's ten red examples now live in tests/release.test.js as a red-first block — they were first run red against the previous release (3 failing, then 7 more), and the suite is now 117 checks.

HD4b is a behavioural probe rather than a text assertion: it imports the built host and hands readSettings a service shaped the way 0.1.7+ actually is — no get, but a working describe — then asserts the user layer comes back. It skips when the build is absent instead of passing silently.

Three existing assertions pinned the very shapes that had to change, and were updated in place rather than bypassed. The new read path additionally got its own "probe before you call" assertion, so the discipline that protects the old path covers the new one too.

Verification

tests        117 total · 111 pass · 6 fail

The 6 are spawnSync-based and fail only inside a restricted agent sandbox that forbids child pipes; a pristine-HEAD worktree produces the identical 6, so they are environmental rather than regressions. On a normal terminal they pass.

lint        0 errors
prettier    All matched files use Prettier code style
metrics     5 files · 107 functions · 0 over limit
privacy     PASS — no identity, history or package finding

Install

dsh plugin --profile <your-profile> add dsh-auto-paste@0.2.4

Full diff: v0.2.3...v0.2.4

v0.2.3

Choose a tag to compare

@sakuraqqq sakuraqqq released this 09 Oct 00:41
bb27a15

dsh-auto-paste v0.2.3

Declares DSH compatibility explicitly, so the plugin store can list this plugin again.

What changed

  • dsh.compatibility added to the manifest. DSH STORE unlisted the entry because its catalog recorded unknown for every DSH release — including the latest three it checks — and its policy requires an exact compatible record at a fixed commit (AI-Scarlett/DSH-Store#934). The cause was simply that the manifest never carried a dsh.compatibility block, so there was nothing to read.

    "compatibility": {
      "dsh": "^0.1.5-rc.1 || ^0.2.0-rc.2",
      "dshReleases": {
        "0.1.5-rc.2": "compatible",
        "0.1.7-rc.2": "compatible",
        "0.2.0-rc.2": "compatible"
      },
      "profiles": ["web"]
    }

    Only versions this plugin has actually run on are declared; everything else stays unclaimed, so the catalog keeps defaulting it to unknown instead of reading a claim that cannot be backed:

    DSH release Status Basis
    0.1.5-rc.2 compatible the line this repository develops and tests against (devDependencies)
    0.1.7-rc.2 compatible the daily-driver web profile, and the build 0.2.2 was verified on
    0.2.0-rc.2 compatible the desktop runtime in use, whose runtime.json reports desktopVersion: 0.2.0-rc.2
  • No runtime behavior changed. The paste path, the reference chip, the one-shot hint and the settings row are untouched by this release.

Also worth recording

The catalog entry also carries credentials: ["api-key"], which this plugin does not use. That value is not produced by the store's own rule set: replaying permissionSignals() (src/automation-source-policy.mjs upstream) over the scannable files of v0.1.4, v0.1.5 and HEAD yields zero hits on all three credential patterns, while files: "write" (derived from mkdir( / readFile() is correct. The permission block carries no provenance to check against, unlike assurance.discovery, so the origin cannot be traced from the record. Written up in the same issue; nothing to change on our side for it.

Install

dsh plugin --profile <your-profile> add dsh-auto-paste@next

Full diff: v0.2.2...v0.2.3

v0.2.2

Choose a tag to compare

@sakuraqqq sakuraqqq released this 07 Oct 16:44
f6adb4f

dsh-auto-paste v0.2.2

Fixes a one-shot hint that leaked into every other conversation.

Fixes

  • The "no sidebar installed" hint followed the user into other sessions. The hint is raised the first time a large paste lands as an atomic chip, and it renders in conversation.input.overlay — a slot that every conversation mounts. Its one-shot flag, sidebarState.hintWanted, was page-wide and owned by nobody, so the annotation raised in session A also painted above the composer of whichever session the user switched to next (reported from a phone, 2026-10-05; the shape dates back to 0.2.0, when the hint moved off the pill and onto the composer overlay).

    The offer now names the session it belongs to (hintSessionId), a slot only claims an offer addressed to its own props.sessionId (ownsSidebarHint), and the slot that renders it consumes it (consumeSidebarHint). A conversation that never raised the hint can no longer find one standing.

    • The rendered annotation stays on screen after its own offer is consumed: a local shownFor latch — not the page-wide flag — keeps it visible, and its session key is what keeps it off every other conversation.
    • A slot with no session identity (dsh 0.1.5 hands the overlay no sessionId prop) keeps the old behavior rather than hiding the hint forever; the consumption above is what bounds it there.

Engineering

  • Test suite: 107 checks. The six new guards were written red first (106 tests, 6 fail) and only then made green. They pin the address on the snapshot, the change detector that must watch it, the offerSidebarHint(sessionId) call site, the ownership refusal, the consumption, and the local latch that outlives it.
  • The guards were then audited for negative power, not just for passing. An adversarial review measured three of them to be weaker than they read: the paste-path check was satisfied by the function's own definition line (vacuous), "from an effect" accepted any effect in the window, and the negative check rejected only one spelling of the regression. All three are re-anchored, and a runtime truth table now evaluates the ownership verdict itself — a source-text assertion can only see that some text is present, never whether the rule is right. Four real mutations (unconditional ownership, a call site that drops the session, the render guard reverted in the other order, the consume moved into the render body) are each caught by the guard that names them, after which the suite returns to 107/107.
  • Gate chain green: typecheck · eslint 0 · prettier 0 · privacy PASS · metrics 0 over-limit · smoke · artifacts rebuild byte-identically (src/client.js and lib/client.js share one SHA-256).
  • No change on the paste path itself: the model still receives the bare reference, and the chip is untouched.

Known limitations (found by the same review, not fixed here)

  • The save toast has the same shape. toastList is page-wide while ToastHost renders in the same per-conversation overlay, so a toast raised in session A floats above the composer of session B if you switch within its 2.6 s lifetime. It is transient and self-dismissing — unlike the one-shot hint — but it is the same class of bug, and this release does not fix it.
  • dsh 0.1.5 has a narrow race. That line hands the overlay no session identity at all, so a slot cannot tell whose offer it is, and the offer is consumed by whichever overlay renders first. The offer is raised only after savePaste and the chip insertion resolve, so switching conversations inside that window can still paint the hint in the wrong one. dsh 0.1.7 — the line 0.2.x targets — is unaffected: its slot props carry the session identity, which is exactly what ownsSidebarHint compares.

Install

dsh plugin --profile <your-profile> add dsh-auto-paste@next

Full diff: v0.2.1...v0.2.2

v0.2.1

Choose a tag to compare

@sakuraqqq sakuraqqq released this 02 Oct 21:52
f22e166

dsh-auto-paste v0.2.1

Fixes a reference chip that could reach the message as dead plain text, and keeps the paste size visible after a session switch.

Fixes

  • A chip pasted right after typed text went out as plain text. A paste dropped immediately after a character (the natural 他说<paste>, 是<paste>) produced the draft text 他说@"pastes/x.txt" — glued, with no whitespace in front of the token. Every reference scan in dsh anchors on (^|\s):

    • the composer's TEXT_REF_RE / FOLDER_REF_RE (dsh-client-ui-conversation), and
    • the renderer for messages that were already sent, projectUserText (dsh-client-ui-primitives).

    Neither can see a glued mention, so the reference rendered as dead text and was not clickable — the 2026-10-02 report. The insertion now puts a real space character into the draft first (read-only DOM probe of the character before the caret; "cannot tell" answers yes, insert the space), then re-captures the span and inserts the chip. A paste into an empty draft is untouched — it already sits at a valid boundary.

    The space cannot live inside the chip: at send time the chip's span is replaced by serializeReference(ref), which returns the bare mention — a space carried there would vanish exactly when it is needed.

    This plugin's plain-text fallback had documented the same (^|\s) constraint since 0.1.x; the chip path simply did not carry it over. Control pair from one session: 测试插件@"pastes/…txt" (glued) → plain text; 📄 20261003-053449397.txt (alone) → file card. After the fix, 是 📄 20261003-053548488.txt.

  • The paste size now survives a session switch. dsh persists a session's composer draft as a plain string (localStorage → dsh.conversation.session-<id> → {"draft":"…"}) and re-seeds it as text on remount, so any atomic chip comes back as text. The chip's draft projection therefore now carries the size (… @"pastes/x.txt" (1161 字符)), which makes the degraded text as informative as the pre-0.2.0 form — and it still satisfies the whitespace boundary above. The model is unaffected: the send path replaces that span with the bare mention, so the request text is byte-identical to 0.2.0.

Known upstream limitation (not fixed here)

  • A chip still de-grades to a text reference when a session is switched away and back: dsh's draft mirror stores text only, and its chip-aware restore (restoreDraft(draft, occurrences)) never receives the occurrences from that mirror. This affects every atomic node, including dsh's own. A fix belongs upstream (carry occurrences in the mirror, or re-scan on remount).

Engineering

  • Test suite: 100 checks. The two new/updated guards were written red first (100 tests, 2 fail) and only then made green:
    • the chip payload must build its draft projection from the one token builder, with the size and without the boundary space — while ref stays the bare mention (openReference derives the path from it);
    • the insertion must check needsBoundarySpace(), repair it with insertText(' ', …), and re-capture the span afterwards (the draft revision moved).
  • Live-verified in an isolated profile: the draft projection (exactly what the send path concatenates) reads …测试 @"pastes/20261003-053618884.txt" (1161 字符) — boundary and size in one read.
  • Gate chain green: eslint 0 · prettier 0 · privacy PASS · metrics 0 over-limit · smoke.

Install

dsh plugin --profile <your-profile> add dsh-auto-paste@next

Full diff: v0.2.0...v0.2.1

v0.2.0

Choose a tag to compare

@sakuraqqq sakuraqqq released this 30 Sep 23:31
6949542

dsh-auto-paste v0.2.0

A large paste now lands in the composer as dsh's own atomic reference chip — a filename with its size, removed with one key, clicked to open the file — and the floating capture bar is gone. The model still receives the same bare mention it always did.

New

  • The composer gets an atomic reference chip instead of a line of text.
    Pasting above the threshold inserts dsh's reference-chip node (the same node dsh's attachments use) showing 📄 20260815-103000.txt · 1234 字符:

    • one key removes it — the node is a Lexical decorator with contenteditable="false" and isKeyboardSelectable() === false, so Backspace/Delete take the whole chip, and there is no partial-deletion state to get stuck in;
    • clicking opens the file in the right Sidebar — dsh routes the click itself (official read-only preview, or dsh-better-sidebar's editor when that plugin claims the address);
    • the model text is unchanged: the chip serializes to the same @"pastes/20260815-103000.txt" mention as before, so dsh-file-reference's "this grammar adds no request tokens" property still holds and the model still reads the file rather than receiving 60 KB of text.

    The size rides in the chip's display field only, never inside the mention: openReference derives the path from the mention (ref.slice(2, -1) for the quoted form), so a size inside it would be read as part of the path and the click would open nothing.

  • Insertion goes through dsh's public input facade, not a private editor hook: ctx.get('conversation').input.for(actx).insertReference(reference, span) with the revision-guarded span from the slot's inputActions.captureInsertion(). Verified against dsh 0.1.7-rc.2 and against the desktop 0.2.x bundle, which ships the identical implementation.

  • Every capability is probed before use. The load-bearing probe is the reference source's serializer: a chip whose source has no registered serializer makes the message unsendable (no serializer for reference source), which is far worse than a plain-text reference. Missing facade, stale session, refused edit or a thrown error all fall back to the text form.

Changed

  • The capture bar (pill) is removed, together with everything that existed only to serve it: the capture snapshot store, the composer-scan removal path (beforeinput hand-off included), the view action, its CSS, and the composer input listener.

    • The plugin no longer calls dsh-better-sidebar's file API at all. Presence detection stays (ctx.inject(['betterSidebar'])), because it drives the one-shot hint and the Settings → General status row.
  • The one-shot hint moved onto the chip. Its copy is now 没装 dsh-better-sidebar(官方侧栏能看,装了能直接编辑), and it is raised when a paste first lands as a chip (still shown once, still dismissible, still remembered).

  • ⚠️ Known upstream limitation, documented in the README: dsh-better-sidebar's editor saves a file using the workspace-relative spelling it was given, while its write route requires an absolute path — so saving from a chip-opened editor answers 400 "…" is not an absolute path. That is upstream omdsh-dev/DSH-better-sidebar#646 and it affects any session-scoped file address, including dsh's own @file picks. Until it is fixed, open the file from better-sidebar's own file tree (that route resolves to an absolute path) to save. The previous pill offered a second route that passed an absolute path; with the pill gone, that workaround is no longer part of this plugin.

Engineering

  • dist/client.js is emitted by tsc from src/client.js (the tsconfig.json allowJs + outDir: dist path); sync-client writes lib/ only. Both copies are committed, as CI requires.
  • Test suite: 99 checks. Five new ones lock the shapes a naive writer breaks, and all five were mutation-verified — each goes red when the host is changed to normalize CRLF, to prepend a BOM, or to write latin1:
    • CRLF survives verbatim (every CR and LF is counted on disk);
    • a single 60 KB line with no line break anywhere survives intact;
    • leading / consecutive / trailing blank lines survive;
    • the writer adds no BOM — and a U+FEFF the user pasted is preserved as content;
    • the bytes on disk are exactly Buffer.from(text, 'utf8'), with no U+FFFD — i.e. the file is never a code-page (GBK/CP936) spelling.

Install

dsh plugin --profile <your-profile> add dsh-auto-paste

Full diff: v0.1.6...v0.2.0

v0.1.5

Choose a tag to compare

@sakuraqqq sakuraqqq released this 29 Sep 17:59

dsh-auto-paste v0.1.5

One package now runs on both dsh lines — 0.1.5-rc.x and 0.1.7-rc.x. Four separate 0.1.7 incompatibilities are fixed; the 0.1.5 behaviour is unchanged, and each fix is a capability probe rather than a version check.

Fixes

  • 0.1.7: the plugin could take the official workspace UI down with it. 0.1.7's typert loader rejects a strict codec that has no create() factory. Typert definitions are committed and withdrawn per owner, so a contributor that fails to register leaves the whole batch incomplete — the official workspace remotes (workspace list and directory picker) stopped resolving, which surfaces as "my sessions disappeared": the sessions were intact, the UI just could not enumerate workspaces. All six codecs are now built by one helper that always supplies create.
  • 0.1.7: a large paste was silently a no-op. The paste listener read the session from sessions.list.getSnapshot().current. 0.1.7 moved the selection out of ClientSessions and deleted that field, so the read answered undefined, the handler returned before preventDefault(), and the browser pasted the raw text — no file, no toast, and nothing in the console. The session now resolves through one helper that prefers the 0.1.5 snapshot and falls back to the identity every scope: session slot component is handed (SessionStandardProps.sessionId; this plugin's bar lives in conversation.input.overlay, which is exactly such a slot).
  • 0.1.7: the settings row broke the whole config path. 0.1.7's SettingsForms keeps the service but drops get and register. The unguarded this.settings()?.get(ns) threw this.settings(...)?.get is not a function, which failed getConfig and left the threshold on the built-in fallback. Reads now go through one guarded helper and register sits behind a typeof probe.
  • 0.1.7: minChars could not be edited. 0.1.7 projects a settings form out of each plugin's own Config and exposes only fields marked volatile — a plugin without one gets no generated form and its write is refused. This plugin declared no Config at all. It now declares one with minChars volatile, and the host reads the field live (a volatile field arrives as a box: config.x.get()), so a saved value applies without a restart.
    • Compatibility note: .volatile() exists only on the 0.1.7 line — dsh 0.1.5 ships schemastery 3.18.2, whose whole package contains no such method — so the field is wrapped only behind a capability probe. Calling it unconditionally would throw while the module loads and take the plugin down on 0.1.5.

New

  • Tells you once when the sidebar integration is missing. With no dsh-better-sidebar installed, the capture bar explains what the integration adds (in-place editing, not merely viewing — dsh's built-in file panel already browses pastes/, it just cannot change anything), and a read-only 「侧栏集成」 row in Settings → General keeps saying so until the sidebar appears. Shown once and remembered.

Engineering

  • The session identity comes from slot props instead of a list field a version removed; canConfigure is re-specified to require a working write path (update, plus either get on 0.1.5 or a volatile field on 0.1.7) rather than a read-back channel alone.
  • dist/client.js is produced by tsc from src/client.js; sync-client only writes lib/. Both are committed, as CI requires.
  • Test suite: 88 checks. Every assertion added in this release was contrast-red verified — run against git show HEAD:<file> — keeping only the ones that fail before the fix and pass after it.
  • README and the cordis.patch.yml comment now state where an override is stored on each line, instead of claiming a settings document that 0.1.7 no longer uses.

Install

dsh plugin --profile <your-profile> add dsh-auto-paste

Full diff: v0.1.4...v0.1.5

v0.1.4

Choose a tag to compare

@sakuraqqq sakuraqqq released this 12 Sep 16:43

Release 0.1.4

Changelog:
5f4afda release: v0.1.4
5892d62 docs: sync the release record for 0.1.4 and document the sidebar integration
437be21 fix(host,client): hand the sidebar an absolute path so an opened paste can be saved
e3ded0d fix(client): the ✕ button hands the deletion to the editor instead of using execCommand
875f3cd fix(host,client,docs): review batch C — freeze the chars unit in docs, keep the absolute path host-side
5a0dd2f fix(release,host,client): review batch A/B — tag ordering, wire cap, honest fallback, session-bound bar
1466213 chore(privacy): move internal docs into .私档 and actually untrack them
7e4561c docs: record the E-2 lessons (Lexical delete pipeline, betterSidebar contract, seat and slot-id pitfalls)
d99e754 feat(client): capture bar with sidebar view and reference removal (E-2)
c677af7 feat(settings): make the paste threshold a General preference row
be6cff8 chore: ignore .script-archive local tool output
19ed88f feat(config): make the host the single authority for minChars
37403e3 fix(client): anchor save toast to the composer card, not the frame overlay
e45394e feat(routing): refuse unknown/ambiguous session->workspace instead of falling back
880a168 feat: paste labels + configurable maxBytes (batch 1)
e9351d4 fix(gitignore): repair ANSI-mangled TASK pattern, use ASCII glob
c9d320e chore: ignore local task brief
0ecf232 docs: record v0.1.3 release + add batch-1 task brief
32295b1 ci: add npm trusted publishing (OIDC) release workflow
56b41d5 chore(privacy): sanitize local paths and contact info from tracked docs