mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge remote-tracking branch 'origin/master' into worktree/web-multimodal-image-input
# Conflicts: # apps/cli/src/web.ts # apps/web/tests/smoke-fixture.e2e.ts # docs/architecture.i18n.yaml # packages/client/connection/src/client/fixture.ts # packages/client/runtime/src/client/sessions/conversation.ts # packages/client/runtime/src/client/sessions/session.ts # packages/client/ui-conversation/package.json # packages/client/ui-conversation/src/client/contract/slots.ts # packages/client/ui-conversation/src/client/index.ts # packages/client/ui-conversation/src/client/service.ts # packages/client/ui-conversation/src/client/skeleton/ConversationRoot.tsx # packages/client/ui-conversation/tests/apply-inject.spec.tsx # packages/client/ui-conversation/tests/service-orchestration.spec.ts # packages/host/runtime/src/api-proxy.ts # packages/host/runtime/src/boot.ts # packages/host/webserver/tests/webserver.spec.ts # pnpm-lock.yaml
This commit is contained in:
@@ -10,7 +10,7 @@ The TUI surface:
|
||||
- tells the agent where its own source lives: after boot it adds a prompt section naming this harness checkout, resolved from the launcher's real path so it holds under a PATH symlink and an arbitrary cwd, so the self-referential `cordis` toolset can read and modify it;
|
||||
- applies the personal overlay from `~/.dsh` (see [app-boot's Personal config](../../packages/ui/app-boot/README.md#personal-config)): `.env` fills environment gaps (ambient > project `.env` > personal `.env`), `config.yaml` patches the booted tree.
|
||||
|
||||
The Web surface treats its invoking directory as the default project and loads applicable `AGENTS.md`/`CLAUDE.md` instructions into each agent-loop request prefix with a 65,536-byte render budget.
|
||||
The Web surface treats its invoking directory as the default project, loads applicable `AGENTS.md`/`CLAUDE.md` instructions into each agent-loop request prefix with a 65,536-byte render budget, and opts into first-message model titles. The headless surface retains deterministic fallback titles without making the auxiliary title-model request.
|
||||
|
||||
## Install (developer machine)
|
||||
|
||||
@@ -20,4 +20,4 @@ Symlink the source-running launcher onto your PATH; it resolves the checkout thr
|
||||
ln -sf "$(pwd)/bin/dsh" ~/.local/bin/dsh
|
||||
```
|
||||
|
||||
`pnpm run demo:tui` runs the same entry from the repo root. The built form (`lib/bin.js`, via `pnpm run build`) needs `node --expose-internals` for the shipped config's HMR entry, exactly like the demo bins.
|
||||
`pnpm run demo:tui` runs the same entry from the repo root. The built form (`lib/bin.js`, via `pnpm run build`) boots the same config under plain Node.
|
||||
|
||||
@@ -14,6 +14,16 @@
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@deepseek-ai/dsh-app-boot": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-connection": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-hmr": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-i18n": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-conversation": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-layout": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-question": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-sidebar": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-theme": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-trajectory": "workspace:^",
|
||||
"@deepseek-ai/dsh-frontend": "workspace:^",
|
||||
"@deepseek-ai/dsh-host-apiproxy": "workspace:^",
|
||||
"@deepseek-ai/dsh-host-runtime": "workspace:^",
|
||||
|
||||
@@ -14,6 +14,34 @@ const LOOPBACK_HOST = '127.0.0.1'
|
||||
const ALL_INTERFACES_HOST = '0.0.0.0'
|
||||
const REQUEST_ENVELOPE_HEADROOM_BYTES = 1024 * 1024
|
||||
|
||||
// --- Client composition (composition decisions live in the composing app) ---
|
||||
// The composition layer owns one decision: which plugin packages mount (the
|
||||
// roster). Dependency edges and the boot prefetch tier live in each package's
|
||||
// dshClient declaration.
|
||||
|
||||
/**
|
||||
* Dev-only plugin: the client HMR driver. Whether it composes in is a
|
||||
* deployment decision — the dev graph includes its row, the prod graph does
|
||||
* not mount it at all.
|
||||
*/
|
||||
const CLIENT_HMR_ID = '@deepseek-ai/dsh-client-hmr'
|
||||
|
||||
/** Bundle stat-poll interval for --dev (held here so the startup log states the real value). */
|
||||
const CLIENT_BUNDLE_POLL_MS = 500
|
||||
|
||||
/** The client plugin roster (flat; per-row boot behavior comes from manifests). */
|
||||
const CLIENT_PACKAGES = [
|
||||
'@deepseek-ai/dsh-client-connection',
|
||||
'@deepseek-ai/dsh-client-runtime',
|
||||
'@deepseek-ai/dsh-client-ui-theme',
|
||||
'@deepseek-ai/dsh-client-i18n',
|
||||
'@deepseek-ai/dsh-client-ui-layout',
|
||||
'@deepseek-ai/dsh-client-ui-sidebar',
|
||||
'@deepseek-ai/dsh-client-ui-conversation',
|
||||
'@deepseek-ai/dsh-client-ui-question',
|
||||
'@deepseek-ai/dsh-client-ui-trajectory',
|
||||
] as const
|
||||
|
||||
export async function runWeb(argv: string[]): Promise<void> {
|
||||
const { values } = parseArgs({
|
||||
args: argv,
|
||||
@@ -21,6 +49,7 @@ export async function runWeb(argv: string[]): Promise<void> {
|
||||
host: { type: 'string', default: LOOPBACK_HOST },
|
||||
port: { type: 'string', default: '3080' },
|
||||
'max-request-body-bytes': { type: 'string' },
|
||||
dev: { type: 'boolean', default: false },
|
||||
},
|
||||
allowPositionals: false,
|
||||
})
|
||||
@@ -40,8 +69,11 @@ export async function runWeb(argv: string[]): Promise<void> {
|
||||
? undefined
|
||||
: Number(values['max-request-body-bytes'])
|
||||
if (configuredMaxRequestBodyBytes !== undefined
|
||||
&& (!Number.isInteger(configuredMaxRequestBodyBytes) || configuredMaxRequestBodyBytes < 1)) {
|
||||
process.stderr.write(`dsh web: invalid --max-request-body-bytes ${values['max-request-body-bytes']}\n`)
|
||||
&& (!Number.isInteger(configuredMaxRequestBodyBytes)
|
||||
|| configuredMaxRequestBodyBytes < 1)) {
|
||||
process.stderr.write(
|
||||
`dsh web: invalid --max-request-body-bytes ${values['max-request-body-bytes']}\n`,
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -50,22 +82,46 @@ export async function runWeb(argv: string[]): Promise<void> {
|
||||
boot: {
|
||||
persistenceRoot: './.sessions',
|
||||
workspaceContext: { maxBytes: 65_536 },
|
||||
sessionTitleLlm: true,
|
||||
},
|
||||
})
|
||||
const attachments = host.ctx.get('attachments')
|
||||
if (attachments === undefined) throw new Error('dsh web: attachment service unavailable')
|
||||
const maxRequestBodyBytes = configuredMaxRequestBodyBytes
|
||||
?? Math.ceil(attachments.imageLimits.maxMessageImageBytes * 4 / 3) + REQUEST_ENVELOPE_HEADROOM_BYTES
|
||||
?? Math.ceil(attachments.imageLimits.maxMessageImageBytes * 4 / 3)
|
||||
+ REQUEST_ENVELOPE_HEADROOM_BYTES
|
||||
|
||||
// Web UI plugin chain: in-memory Loader tree over the eight UI packages,
|
||||
// then the registry that feeds __DSH_BOOT__ and /plugins/<id>/client.js.
|
||||
const mounted = await mountWebPlugins(host.ctx)
|
||||
// Client plugin chain: in-memory Loader tree over the composed roster, then
|
||||
// the registry that feeds the __DSH_BOOT__ entry graph and
|
||||
// /plugins/<id>/client.js. All row content comes from dshClient discovery
|
||||
// over the mounted roster (dev adds the HMR driver row and turns on the
|
||||
// bundle watch that drives rebuilt frames).
|
||||
const roster = [...CLIENT_PACKAGES, ...values.dev ? [CLIENT_HMR_ID] : []]
|
||||
const mounted = await mountWebPlugins(host.ctx, roster, import.meta.url)
|
||||
const webPlugins = createHostWebPluginRegistry({
|
||||
ctx: host.ctx,
|
||||
loader: mounted.loader,
|
||||
resolvePkgJson: mounted.resolvePkgJson,
|
||||
onError: (err: Error) => { process.stderr.write(`dsh web: plugin rescan: ${String(err)}\n`) },
|
||||
...values.dev ? { watch: { intervalMs: CLIENT_BUNDLE_POLL_MS } } : {},
|
||||
})
|
||||
if (values.dev) {
|
||||
// Dev visibility (the registry is a library and never prints): list what
|
||||
// the bundle watch covers, then log every observed rebuild. This is a
|
||||
// second onRebuilt subscription — the SSE relay inside the webserver is
|
||||
// unaffected (multicast).
|
||||
const revs = new Map(webPlugins.graph().entries.map(row => [row.id, row.rev]))
|
||||
const bundlePaths = [...revs.keys()]
|
||||
.map(id => webPlugins.clientPath(id))
|
||||
.filter((path): path is string => path !== undefined)
|
||||
console.log(
|
||||
`dsh web: watching ${String(bundlePaths.length)} plugin bundles (${String(CLIENT_BUNDLE_POLL_MS)}ms poll):\n ${bundlePaths.join('\n ')}`,
|
||||
)
|
||||
webPlugins.onRebuilt((id, rev) => {
|
||||
console.log(`dsh web: plugin rebuilt: ${id} rev ${revs.get(id) ?? '?'} -> ${rev}`)
|
||||
revs.set(id, rev)
|
||||
})
|
||||
}
|
||||
// Published so the webserver invariant companion can audit manifest/bundle
|
||||
// consistency; nothing else reads this key.
|
||||
host.ctx.reflect.provide('webPlugins', webPlugins)
|
||||
@@ -97,7 +153,14 @@ export async function runWeb(argv: string[]): Promise<void> {
|
||||
let server: Awaited<ReturnType<typeof startWebServer>>
|
||||
try {
|
||||
server = await startWebServer(
|
||||
{ host: hostAddress, port, distIndex, apiHandler: host.handler, maxRequestBodyBytes, webPlugins },
|
||||
{
|
||||
host: hostAddress,
|
||||
port,
|
||||
distIndex,
|
||||
apiHandler: host.handler,
|
||||
maxRequestBodyBytes,
|
||||
webPlugins,
|
||||
},
|
||||
(err: Error) => {
|
||||
process.stderr.write(`dsh web: ${String(err)}\n`)
|
||||
void shutdown(1)
|
||||
|
||||
@@ -8,12 +8,56 @@
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{ "path": "../../vendor/cordis" },
|
||||
{ "path": "../../packages/host/apiproxy" },
|
||||
{ "path": "../../packages/host/runtime" },
|
||||
{ "path": "../../packages/host/webserver" },
|
||||
{ "path": "../../packages/core/session" },
|
||||
{ "path": "../../packages/ui/app-boot" },
|
||||
{ "path": "../../packages/util/paths" }
|
||||
{
|
||||
"path": "../../vendor/cordis"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/host/apiproxy"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/host/runtime"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/host/webserver"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/core/session"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/ui/app-boot"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/util/paths"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/connection"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/hmr"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/runtime"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/ui-theme"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/i18n"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/ui-layout"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/ui-sidebar"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/ui-conversation"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/ui-trajectory"
|
||||
},
|
||||
{
|
||||
"path": "../../packages/client/ui-question"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user