Commit Graph
18711 Commits
Author SHA1 Message Date
Yichen Jiang a190ef580c chore(release): carry dsh-authorization to the rc.7 family version 2026-08-20 17:58:38 +08:00
Yichen Jiang 29a82543ed chore(sync): re-record the packages README pairing after the cascade 2026-08-20 17:58:38 +08:00
Yichen Jiang 0b2bc3d651 fix(llm-pi-ai): stub both home spellings in the tilde-expansion test
os.homedir() reads HOME on POSIX and USERPROFILE on Windows; stubbing
only HOME left the Windows lane expanding ~ into the real profile
directory and failing the fileExists assertions.
2026-08-20 17:58:38 +08:00
Yichen Jiang 6428b844ef fix(ci): catch the branch up with the static and smoke gates
- dsh-authorization's manifest becomes a release member on current
  master's terms: version matches the root 0.1.0-rc.6 and
  publishConfig.access is public, which the constraints gate and the
  tarball pack's version verify both enforce after the master merge.
- Regenerate docs/module-graph (zh mirror included) for the
  authorization package and the llm-pi-ai -> authorization edge; the
  graph gate lives outside doc-sync and was never regenerated when the
  package was added.
- The built-bin smoke seeds the versioned credentials document; this
  branch's provider refuses the pre-release flat layout by design, and
  the master-side test still wrote the old shape.
2026-08-20 17:58:38 +08:00
Yichen Jiang 9eaaeaeb96 fix(credentials,authorization,llm-pi-ai): harden the auth seams per review
Review findings on #2509, all confirmed:

- Every writer of .credentials.yaml now waits out the record-mutation
  lock (DOCUMENT_LOCK_WAIT_MS): refs and records share one file and one
  lock, so a reference write or record delete contending with an OAuth
  refresh must not fail at the 2s file-work default.
- api-key records are admitted before they are rendered: an empty key,
  a non-POSIX env name, or an empty env value is refused at the write
  instead of persisting a document the next boot rejects wholesale.
- llm-pi-ai no longer lets the credential-key grammar reject legal
  route ids: reads answer "nothing stored" via isCredentialKeySegment
  (new dsh-credentials export), deletes have nothing to remove, and only
  a write refuses, as LlmError UNSTORABLE_PROVIDER_ID; flow registration
  skips a future catalog id outside the grammar instead of failing the
  mount.
- authorization/settled fans out with contained listener failures on
  the credentials seam's terms (INVARIANT still rethrows), so a broken
  watcher can never turn a finished attempt into a failure.
- notify() is fire-and-forget at the seam: a surface that cannot render
  a notice loses the notice, never the attempt.
- A declined prompt is an outcome: interactions reject with the new
  AuthorizationDeclinedError and the attempt settles cancelled instead
  of failed.
- NOT_COMMITTED now confirms a commit observed during the attempt
  (credentials/record-updated for the flow's key), so a re-auth cannot
  pass a stale record off as fresh; a flow that deletes its record is
  refused on the same code.

READMEs, the subsystem/event/config catalogs, and the Agent Note follow
the shipped behavior; memory.ts carries the dedup TODO.
2026-08-20 17:58:38 +08:00
Yichen Jiang 21ea0ed9e6 docs(authorization): record the credential-record and flow decisions
Why the record union is one step more abstract than pi-ai's credential
in exactly two places, why a record key names the owning plugin rather
than the provider, why the flow owns the write, and why the interaction
travels with the request instead of a registry. Cross-links the release
fix it supersedes, and states what is still missing: the wire contract
and the Models-page control that would let a human start a login.

The two web e2e goldens regain exactly the openai-codex option line
they lost when the provider was withheld.
2026-08-20 17:58:38 +08:00
Yichen Jiang 57c5f017ac feat(llm-pi-ai): sign in to a provider instead of withholding it
pi-ai's auth model reaches this adapter through three translations, all
of which live here: a CredentialStore over the harness credential
records, an AuthContext over the credential plane and the host
filesystem, and one authorization flow per installed provider that
ships a login. The seams they consume name nothing from pi-ai, so a
second adapter family can arrive with a different auth model and share
them.

Every collection is now built with the store and the context rather
than with nothing, which is what makes a signed-in provider stay signed
in across the collection rebuild a configuration change causes. With a
posture that works, the configurable-provider directory no longer
withholds OAuth-only routes and `openai-codex` is offered again; the
predicate that withheld it is gone.

The credential plane stays optional. Reads answer "nothing stored"
without a credentials service because such a composition genuinely
holds no credential, while writes refuse by name — a login whose grant
evaporated would report success and then fail every request. Flow
registration is scoped to the authorization seam, so a headless or ACP
composition mounts with no sign-in and everything else unchanged.

Two fixes found while wiring this up: pre-release credential fixtures
in the llm suites still used the flat document the record work
replaced, and a flow that ignores its cancellation signal would have
held its key for the life of the process — withdrawal now settles the
attempt either way.
2026-08-20 17:58:38 +08:00
Yichen Jiang 732a7361f5 feat(authorization): obtain a credential by asking the human
Some credentials cannot be configured, only obtained: getting one means
a conversation — open this page, paste that code, pick an account. The
new seam owns that conversation and the one-attempt-per-key lifecycle,
and never the protocol, so a second authorization protocol arrives as
another flow rather than as another seam.

A flow is registered under the CredentialKey it writes, which is also
how the seam knows which plugin answers for the format inside that
record. The flow owns the write: run() resolving means the record is
already committed through ctx.credentials, and the seam confirms it.
That keeps a library persisting through its own store adapter the
single writer instead of being copied back out and written twice.

The interaction travels with the request rather than a registry,
because whoever starts an authorization is the one who can talk to the
human about it. A request already withdrawn never claims the key and
never starts the flow — relying on each flow to check its signal before
the first await would let one that does not hang holding the key.
2026-08-20 17:58:38 +08:00
Yichen Jiang 86a9f8c862 feat(credentials): store durable credential records beside references
The seam answered one question — what is behind this environment-variable
name — and that shape cannot hold what an authorization grant is: a
multi-field, rotating value keyed by a provider id rather than by a POSIX
identifier. The Models page already works around the gap by inventing a
synthetic environment name (`MINIMAX_CN_API_KEY`) for a route the user added
by hand, because the store's key must look like one.

`CredentialKey` is `<scope>/<id>`, where the scope is the owning plugin's
registered name. The owner is in the key because a `grant` payload is written
in its owner's format: two plugins serving the same provider name would
otherwise read each other's payload, and a record left by an uninstalled
plugin could not be told from a live one. The `/` also keeps the grammar
disjoint from `CredentialRef`, so the key spaces cannot collide.

`CredentialRecord` is `api-key` (key and/or provider environment values) or
`grant` (an opaque, owner-owned payload). The asymmetry is deliberate: an api
key is the harness's own data, a grant is a package it carries for someone
else. `modifyRecord` is the only write path because a correct write depends
on the current value — a token refresh is read-decide-replace under one
cross-process lock, without which two processes rotating one refresh token
lose whichever wrote first.

`.credentials.yaml` becomes a versioned two-section document. The pre-release
flat layout is refused by name, with the entry count and the one edit needed,
rather than read as an empty store — which would surface as an authentication
failure on the first request instead of at load. A grant payload is admitted
in both directions, so a value the document could not read back exactly as
written is refused rather than stored lossily.
2026-08-20 17:58:38 +08:00
Yichen Jiang 26a8e6a555 feat(atomic-write): state the writer-lock wait limit per call
How long a contender waits is a property of the operation the lock holder
runs, not of the write protocol. The 2s default was sized for the
render-and-rename cycle every call site had; a credential mutation that
refreshes an expired token performs a network round trip while holding the
lock, and leaving the default in place would fail every other writer of that
file for the duration.

`withFileLock` takes an optional `waitMs`; the retry cadence stays fixed
because it governs how often a contender asks, which no caller varies. Every
existing call site keeps the default.
2026-08-20 17:58:38 +08:00
_Kerman a577a48acd Merge remote-tracking branch 'origin/master' into dshw/pr-deepseek-harness-deepseek-harness-2702 2026-08-20 17:52:56 +08:00
pku-xht d778e83b2b fix(subprocess): route fallback warning through logger 2026-08-20 17:38:01 +08:00
Chinesezjc f5faeae4d3 Merge pull request #2798 from deepseek-harness/ci/release-check-panel
ci: stop PR gray checks from lifecycle and release publish jobs
2026-08-20 17:36:12 +08:00
_Kerman cdb918c4b0 chore(storage-json): exempt the shared unit lifecycle from the duplication gate
The two standalone unit classes deliberately mirror the KvUnit drain/guard
lifecycle (close + assertOpen); mark the block with a reason-carrying
jscpd ignore so the duplication gate stays green.
2026-08-20 17:34:08 +08:00
pku-xht 4c164cd163 fix(subprocess): close native containment CI gaps 2026-08-20 17:29:22 +08:00
pku-xht 3a9e1a6e24 Merge pull request #2749 from deepseek-harness/codex/cache-hit-decimal-display-v2
feat(web): preserve near-full cache-hit precision
2026-08-20 17:27:06 +08:00
_Kerman f6080c3753 docs(session-projection-cache): sync catalogs, type-equiv, and event consumers after master merge
Regenerate config/cordis catalogs and doc graphs (the master merge changed
configs and consumers), add the DomainSpec layout field to the storage
type-equiv block, record session-projection-cache as a session/created
consumer, and drop the leftover experimental/team ghost directories from
the master rename. All 37 static gates pass.
2026-08-20 17:26:32 +08:00
pku-xht 36aead7320 Merge remote-tracking branch 'origin/master' into codex/cache-hit-decimal-display-v2 2026-08-20 17:18:29 +08:00
_Kerman 2c11e73c55 Merge remote-tracking branch 'origin/master' into xtr/projection-per-session-cache 2026-08-20 17:15:25 +08:00
_Kerman eb1f167fa4 refactor(session-projection-cache): restore the base method order
Keep the base class's relative method order (write before coldSnapshot) so
the diff against the base shows the cold-read methods as a pure insertion
instead of a reorder of existing methods.
2026-08-20 17:14:05 +08:00
Yichen Jiang 6d6262703b Merge pull request #2820 from deepseek-harness/feat/1687-multiline-question-answer
feat(web): answer ask_user_question over multiple lines
2026-08-20 17:12:39 +08:00
Yichen Jiang b2c9c3ce37 fix(web): derive the caret-delete range from what the draft lost
A caret Backspace or Delete replaces no selection, so `beforeinput` reports
the bare caret and the previous derivation produced a negative inserted
length and fell back to the ambiguous scan. The delete half of the defect
survived, and the component test missed it by pre-expanding the selection to
the span the engines never report.

The range for a caret delete now comes from the direction `inputType` names
and the number of characters the draft actually lost, measured rather than
assumed to be one, so a grapheme, word, or line deletion sizes correctly.
Only the insert and delete families are recorded; a history replay reports
wherever the caret sits and would name a wrong span while passing every check.

Component tests cover the caret Backspace, Delete, and word-delete gestures,
and an assembled browser scenario drives them as real key presses, which is
the only place an engine's reported range is observable.
2026-08-20 17:11:35 +08:00
Yichen Jiang 7260f4cf64 fix(web): cap the answer field at six text lines in both variants
The card-wide `border-box` reached `.fieldMirror`, so the growth cap counted
text plus padding. The inline variant carries none and landed on the declared
line count, while the optionless variant's 16px inset spent two thirds of a
line and delivered its last one as an 8px sliver. The e2e measured only the
inline shape, so nothing caught the drift.

The mirror now takes `box-sizing: content-box`, which states the cap in the
units it is written in, and the cap moves to six lines. The e2e asserts whole
text lines rather than a box height, and covers the optionless shape — asked
straight through the user-questions seam, since a layout metric needs no model
round — including that the reserved empty field is fully covered by its own
control.
2026-08-20 16:58:41 +08:00
Yichen Jiang 477615162a feat(ui-conversation): lexical chip node, projections, span map 2026-08-20 16:57:56 +08:00
Yichen Jiang 2279fd19b0 chore(ui-conversation): add lexical dependencies and jsdom spike 2026-08-20 16:46:57 +08:00
_Kerman b67761a8a5 fix(session-projection-cache): checkpoint at session creation
A session that never talks — a forked child seeded with its ancestor's
title, say — previously got its first cache row only at detach; a crash,
or a fork held live in the store, left the seed-derived values (the
title) unreadable on the cold list. Session creation is now a third
mandatory write point: the creation checkpoint folds the seed and
persists immediately. Write-policy docs (README + catalogs) updated.
2026-08-20 16:37:41 +08:00
pku-xht 884cd6be70 Merge codex/subprocess-win32-process-primitives into codex/subprocess-native-containment
# Conflicts:
#	packages/subprocess/win32-process/package.json
2026-08-20 16:27:21 +08:00
imccyu 3866791579 Merge pull request #2725 from deepseek-harness/worktree-bootserver
feat(webserver): structured index injection table and the client boot seams
2026-08-20 16:26:31 +08:00
pku-xht 894f6aeb8a fix(subprocess): contain escaped descendants with native owners 2026-08-20 16:26:19 +08:00
_Kerman 08e546eff1 feat(storage-json): one-time migration of a legacy whole-unit file to per-record
Opening a per-record unit splits a legacy `<root>/<name>.json` (the
pre-per-record single-file layout) into per-record documents; an
already-present new record wins, and the legacy file is deleted once
every record migrated. The migration also runs when the new tree is
absent — the fresh-upgrade shape — and foreign, shapeless, or malformed
legacy files are left alone. This preserves previously cached session
titles, list metadata, stats, and subagent identity across the medium
change.
2026-08-20 16:25:39 +08:00
_Kerman 84db39cec4 feat(session-projection-cache): seed cold reads from the cache and write back
A detached history read still traverses the complete log, but each unit's
fold is now seeded from its cached checkpoint: the registry's restore
slices off the already-folded prefix (events at or below the row's seq)
and applies only the tail. The first cold read writes the refreshed
checkpoint back (fail-soft), so the cache row is created on first read
and kept current afterwards. The recipe lives on the cache
(cachedCheckpoint, coldSnapshot, writeBack); the api-proxy carrier only
supplies the stored header and the full log.
2026-08-20 16:25:39 +08:00
imccyu be3a630da8 docs(web): retire tap-era prose across READMEs, subsystems, and notes 2026-08-20 16:13:05 +08:00
imccyu d582939783 docs(web): align injection-surface JSDoc, notes, and bilingual READMEs 2026-08-20 16:13:05 +08:00
imccyu 97f4d4608b test(gateway): pin the namespace atomic-visibility guarantee 2026-08-20 16:13:05 +08:00
imccyu 10f9f506e2 fix(web): stand down prefetch only when the transport owns bundle bytes 2026-08-20 16:13:05 +08:00
imccyu 3ca4997cd2 test: cover gateway rollback and theme fallback branches 2026-08-20 16:13:05 +08:00
imccyu d4fd03ae53 chore(docs): classify IndexInjection and regenerate the catalog surfaces 2026-08-20 16:13:04 +08:00
imccyu 156bd075a9 feat(webserver): structured index injection table and the client boot seams
Replace per-plugin tapIndex regex edits with pure-data IndexInjection rows
collected fresh per render over one webserver/index-inject event. One table,
two renderers: the served form renders rows into index.html; a static worker
form ships the same rows over its boot payload. tapIndex survives as the
raw-HTML escape hatch, applied after row rendering; client-modules and
ui-theme move to the event, and the manifest global renders as
globalThis["__DSH_BOOT__"].

The client boot chain gains the seams a pre-injected transport needs: the
module loader takes loadBundle from the transport global by default, HTTP
prefetch stands down when a transport owns bundle bytes, the web-app bundle
can decline frontend serving, the gateway client installs a namespace's
whole method group inside its fiber apply so a parked dependent never
observes the service without its methods, and the dynamic-code precheck
gates through new Function so hosts without a real node:vm keep the
define-time parse gate.
2026-08-20 16:13:04 +08:00
_Kerman 4347ee17f3 fix(test): use unique temp dirs for acp-demo composition persistence
The composition tests hardcoded shared /tmp/dsh-acp-demo-* persistence
roots. On the shared self-hosted CI VM, concurrent jobs running the same
master-level test file opened the same SQLite session-query.db and one
failed with "database is locked". Use mkdtemp-unique directories so each
run owns its SQLite index while still exercising explicit
persistenceRoot forwarding.
2026-08-20 16:04:40 +08:00
_Kerman 02a608271e Merge origin/master into xtr/projection-per-session-cache 2026-08-20 16:03:51 +08:00
pku-xht 7ef1c458f0 fix(win32-process): close PR1 validation gaps 2026-08-20 16:03:15 +08:00
Chinesezjc 56a8f7d325 fix(cic): cover release-publish in client-build gate and correct group wording
Address the latest review pass on PR #2798:

- client-build-environment.client.spec.ts: add release-publish.yml to
  dshBuildWorkflows so the 'workflow env must not set DSH_CLIENT_*' gate covers
  the new dsh publish path (it runs build:official and writes a dsh client
  build record). vendor-publish runs only build:lib:host, so it is not added.
- 2026-08-10-npm-release-sequences note (en/zh/i18n): the Release-publish group
  is carried by the publish job (job-level concurrency), not the whole
  workflow; corrected the wording.
2026-08-20 15:59:40 +08:00
_Kerman 5609922d27 Merge remote-tracking branch 'origin/master' into dshw/pr-deepseek-harness-deepseek-harness-2672 2026-08-20 15:58:12 +08:00
07akioni e483c9e30e Merge pull request #2776 from deepseek-harness/feat/table-optmize
feat(web): wrap markdown tables to the column with an overflow wide view
2026-08-20 15:46:40 +08:00
Chinesezjc 16affb84fb fix(cic): restore subscription-type gates and note table for split publish
Address the fresh review pass on PR #2798:

- ci-workflow.spec.ts: restore the subscription-type assertions the rewrite had
  dropped (issue-lifecycle pull_request types omit ready_for_review and include
  review_requested; pull_request_review types == ['submitted']) alongside the
  new step-level gate checks, so the 'tests pin the subscribed events' gate
  holds.
- 2026-08-10-npm-release-sequences note (en/zh/i18n): the three-sequence table's
  Workflow column now lists the pack and publish workflows for dsh/vendor, and
  line 114 no longer claims release.yml publishes (the dsh pack job packs the
  vendored family for verification; release-publish.yml repacks and publishes).

Follow-up for the pack-job copy drift is filed as #2816.
2026-08-20 15:41:24 +08:00
Yichen Jiang 795d36d68f fix(web): carry the composer edit range from the pre-edit selection
Ordinary typing supplied no edit range, so the machine recovered one by
scanning the two drafts for a common prefix and suffix. That reading is
ambiguous whenever the inserted text repeats what it lands against, and the
greedy scan always slides the edit as late as the characters allow. A
reference renders as '@' plus its label, so typing '@' immediately before one
reads as an insertion inside the reference; reconcile then applies its
intersect rule and drops the occurrence. Submission takes the occurrence-free
path and sends the human-facing label instead of the owner's model form, past
the serialization guard that only fires when an occurrence survives.

InputBar now records the textarea's selection during beforeinput and passes
the resulting range to setDraft, which the machine already prefers over its
own scan. A record that cannot describe the reported change yields no range
and the scan still runs.
2026-08-20 15:32:51 +08:00
Chinesezjc abe414a7e3 Merge remote-tracking branch 'origin/master' into ci/release-check-panel 2026-08-20 15:30:47 +08:00
Yichen Jiang 4562616c9b docs(apiproxy): record that a pending question dies with the host
The pending-question registry holds the awaiting tool call's own
resolve/reject, so it is host-process memory. events.mux replays every
still-pending question on reopen, which covers a browser reload or a
reconnect, but a host restart takes the awaiting turn with it and the
reopened Session offers no composer for that question.
2026-08-20 15:29:14 +08:00
Yichen Jiang 9616790b6c feat(web): answer ask_user_question over multiple lines
A question that carried options collected its free-text answer in a
single-line input: a long sentence scrolled sideways inside one line and
Shift+Enter was inert, so an answer with structure could not be typed.
The optionless question already used a textarea, but a fixed 64-140px box
that never followed the draft.

Both shapes now answer into one AnswerField: a `textarea rows=1` sharing a
grid cell with a hidden mirror that renders the draft plus a trailing
newline and so owns the height. Soft wraps are invisible to a '\n' count,
so the mirror is what grows the box; growth stops at eight lines and the
textarea scrolls from there, keeping the choices the answer belongs to in
view. Enter still continues and submits, Shift+Enter breaks the line, and
the IME guard is unchanged.
2026-08-20 15:29:13 +08:00
_Kerman 28b265c1e9 Merge remote-tracking branch 'origin/master' into dshw/pr-deepseek-harness-deepseek-harness-2702
# Conflicts:
#	examples/headless-agent/tests/snapshots/headless-profile/session.expected.jsonl
2026-08-20 15:26:37 +08:00