Merge origin/master: web permission sandbox, default pi-ai providers

This commit is contained in:
Turtle
2026-07-29 14:29:32 +08:00
parent 42e3cceb64
commit e7c0a5b794
147 changed files with 6770 additions and 195 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ Each run starts a fresh session by default (its event log lands under `./.sessio
dsh --resume <prior-session-id>
```
`/resume` opens a searchable keyboard selector with titles, activity, last-turn results, model route, durable goal phase, and live/persisted state. The installed `dsh` host flushes and disposes the current app, then replaces the process with `dsh --resume <id>`. The TUI still prints that command on exit and shows it when a custom host cannot hand off. `dsh --resume <id>` provides the id on the boot context, which `cordis.yml` reads (`resumeSessionId: !!js "typeof resumeSessionId === 'string' ? resumeSessionId : undefined"`); with no flag the agent starts a new session. A missing or unreadable id starts no agent and emits `agent-loop/config-start-failed`: the TUI prints the failure and exits nonzero. The selector has no cross-process session lock, so deployments with concurrent hosts must coordinate session ownership separately.
`/resume` opens a searchable keyboard selector with titles, activity, last-turn results, model route, durable goal phase, and live/persisted state. The installed `dsh` host flushes and disposes the current app, then replaces the process with `dsh --resume <id>`; a host that cannot hand off in place says so and leaves the session running. Resume needs no key in this file: `dsh` provides the session identity and the exit line on the boot context, so `--resume <id>` and the printed resume command survive any personal-overlay patch of the `tui-agent` entry. With no flag the agent starts a new session. A missing or unreadable id starts no agent and emits `agent-loop/config-start-failed`: the TUI prints the failure and exits nonzero. The selector has no cross-process session lock, so deployments with concurrent hosts must coordinate session ownership separately.
## Code Mode
-2
View File
@@ -10,9 +10,7 @@
config:
provider: deepseek
model: deepseek-v4-pro
resumeSessionId: !!js "typeof resumeSessionId === 'string' ? resumeSessionId : undefined"
persistenceRoot: './.sessions'
resumeCommand: 'dsh --resume {session}'
workspaceContext:
maxBytes: 65536
tools:
+3
View File
@@ -59,6 +59,8 @@ flowchart LR
cfg --> plugin_tui_fs_policy
plugin_tui_tool_fs["tool-fs<br/>@deepseek-ai/dsh-tool-fs"]
cfg --> plugin_tui_tool_fs
plugin_tui_source_guard["source-guard<br/>@deepseek-ai/dsh-source-guard"]
cfg --> plugin_tui_source_guard
plugin_tui_tool_fs_search["tool-fs-search<br/>@deepseek-ai/dsh-tool-fs-search"]
cfg --> plugin_tui_tool_fs_search
plugin_tui_timeout_policy["timeout-policy<br/>@deepseek-ai/dsh-timeout-policy"]
@@ -93,6 +95,7 @@ flowchart LR
| `fs-local` | `@deepseek-ai/dsh-fs-local` |
| `fs-policy` | `@deepseek-ai/dsh-fs-policy` |
| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |
| `source-guard` | `@deepseek-ai/dsh-source-guard` |
| `tool-fs-search` | `@deepseek-ai/dsh-tool-fs-search` |
| `timeout-policy` | `@deepseek-ai/dsh-timeout-policy` |
| `spill-local` | `@deepseek-ai/dsh-spill-local` |
+14 -9
View File
@@ -47,15 +47,11 @@
config:
provider: deepseek
model: deepseek-v4-pro
# `dsh --resume <id>` provides the session id on the boot context (the ids
# live under ./.sessions); with no flag the identifier is undefined and a
# fresh session starts each run. The typeof guard tolerates a launcher that
# never provides the slot, reading undefined rather than throwing.
resumeSessionId: !!js "typeof resumeSessionId === 'string' ? resumeSessionId : undefined"
persistenceRoot: './.sessions'
# Printed on exit and listed by `/resume`; `{session}` fills the live id.
# `dsh --resume <id>` resumes that session, so run it from this cwd.
resumeCommand: 'dsh --resume {session}'
# Session identity and the resume command printed on exit are launcher-owned:
# `dsh` provides both on the boot context, so `--resume <id>` and the exit
# hint need no key here. `persistenceRoot` is omitted: the dsh launcher
# supplies its shared Harness-home store through a boot slot, and a bare
# example boot falls back to the bundle's project-local `./.sessions`.
workspaceContext:
maxBytes: 65536
ui:
@@ -169,6 +165,15 @@
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
# Refuses write/edit inside the dsh checkout this launcher runs from, on that
# checkout's own branch, until the session loads dsh-customize — the skill whose
# workflow (task worktree, then integrate under the staging lock) the refusal
# points at. Inert everywhere else: another repository, a task worktree nested
# under the protected one, a sibling checkout on a different branch, and any
# workspace outside a dsh source install all pass through untouched.
- id: source-guard
name: '@deepseek-ai/dsh-source-guard'
# Bash-backed discovery tools (glob/grep): fixed ripgrep commands through the
# local bash executor above — not ctx.fs. Capped results save the complete
# formatted list through the spill backend below (ctx.spillStore, optional).
+24 -3
View File
@@ -20,6 +20,13 @@ const SKILL_BLOCK_OPEN = '<skill name="scripted-skill">'
const SKILL_BODY_MARKER = 'SCRIPTED SKILL BODY MARKER'
const SKILL_RECEIVED_TEXT = 'Scripted skill body received.'
const TITLE_TEXT = 'scripted session title'
// The failing-bash scenario proves the terminal card reports a non-zero exit
// exactly once: the model-facing result carries the `[exit code: N]` marker, and
// the card turns it into its own `[exit N]` pill instead of showing both.
const BASH_FAILURE_PROBE = 'Run the failing scripted command.'
const BASH_FAILURE_COMMAND = 'printf "SCRIPTED_BASH_FAILED\\n"; exit 3'
const BASH_FAILURE_TEXT = 'Scripted bash failure observed.'
const BASH_FAILURE_CALL_ID = CallId('call-bash-failure')
function textChunks(text: string): StreamChunk[] {
return [
@@ -108,9 +115,23 @@ class ScriptedTuiAdapter extends LlmAdapter {
return
}
const hasToolResult = lastMessage?.content.some(block => block.type === 'tool-result') ?? false
if (hasToolResult) {
for (const chunk of textChunks(FINAL_TEXT)) yield chunk
const blocks = lastMessage?.content ?? []
if (blocks.some(block => block.type === 'tool-result')) {
const answered = blocks.some(block => block.type === 'tool-result' && block.toolCallId === BASH_FAILURE_CALL_ID)
for (const chunk of textChunks(answered ? BASH_FAILURE_TEXT : FINAL_TEXT)) yield chunk
return
}
if (lastText.includes(BASH_FAILURE_PROBE)) {
const bashArgs = JSON.stringify({ command: BASH_FAILURE_COMMAND, description: 'Run the failing scripted command' })
yield { type: 'block-start', index: 0, blockType: 'tool-call' }
yield { type: 'tool-call-delta', index: 0, id: BASH_FAILURE_CALL_ID, name: 'bash', argumentsDelta: bashArgs }
yield {
type: 'block-end',
index: 0,
block: { type: 'tool-call', id: BASH_FAILURE_CALL_ID, name: 'bash', arguments: bashArgs },
}
yield { type: 'usage', usage: { inputTokens: 20, outputTokens: 10 } }
yield { type: 'finish', reason: { kind: 'tool-calls' } }
return
}
@@ -33,8 +33,6 @@
# The smoke's log inspection reads plain `.jsonl`; keep the scripted
# fixture uncompressed like the other snapshot-facing configs.
persistenceCompression: none
resumeSessionId: !!js "typeof resumeSessionId === 'string' ? resumeSessionId : undefined"
resumeCommand: 'dsh --resume {session}'
workspaceContext:
maxBytes: 65536
welcome: 'scripted TUI ready.'
@@ -365,11 +365,11 @@ describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
expect(output).toContain('must be a top-level YAML array of loader patch entries')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('routes the --resume flag into the config resume intake, failing loud on a missing id', async () => {
// The flag path end to end: apps/cli parses `--resume missing-session` and
// provides the id on the boot context, the shipped config's `!!js` reads it
// as a bare identifier, and the resume fails loud — proving the printed
// `dsh --resume <id>` hint reaches the config resume intake with no env var.
it('routes the --resume flag into the launcher session-identity slot, failing loud on a missing id', async () => {
// The flag path end to end: apps/cli parses `--resume missing-session`,
// provides it as the launcher-owned identity on the boot context, and the
// resume fails loud — proving the printed hint reaches the app's resume
// intake with no config key and no environment variable.
const output = await smoke({
label: 'dsh resume flag failure',
tempDirPrefix: 'dsh-resume-flag-',
@@ -380,6 +380,70 @@ describe('dsh CLI keyless smoke (apps/cli through the same PTY)', () => {
expect(output).toContain('ui-tui: session "missing-session" failed to start:')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('prints the launcher-owned resume command on exit, naming the booted config', async () => {
// The exit line is built by apps/cli from this invocation, so it must carry
// `--config`: a hint that omitted it would resume into the default tree.
const output = await smoke({
label: 'dsh goodbye message',
tempDirPrefix: 'dsh-goodbye-',
binScript: dshBinScript,
configPath: scriptedConfigPath,
actions: [{ waitFor: 'scripted TUI ready.', send: '/exit\r' }],
})
expect(output).toMatch(/To resume this session: dsh --resume=main-session-[0-9a-f-]{36} --config/)
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('keeps resume working when the personal overlay replaces the whole tui-agent config', async () => {
// Loader patches replace a targeted `config` key wholesale, so a personal
// overlay that omits a resume key used to silently disable the exit hint.
// Launcher-owned identity and exit line make that unreachable.
const output = await smoke({
label: 'dsh overlay keeps resume',
tempDirPrefix: 'dsh-overlay-resume-',
binScript: dshBinScript,
configArgs: [],
prepare: seedWorkspace({
personal: {
'config.yaml': [
'- id: tui-agent',
" name: '@deepseek-ai/dsh-tui-demo'",
' config:',
' provider: deepseek',
' model: deepseek-v4-flash',
' workspaceContext: false',
' welcome: OVERLAY REPLACED THE CONFIG.',
'',
].join('\n'),
},
}),
actions: [{ waitFor: 'OVERLAY REPLACED THE CONFIG.', send: '/exit\r' }],
})
expect(output).toMatch(/To resume this session: dsh --resume=main-session-[0-9a-f-]{36}/)
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('reports a failing bash command exactly once, as the terminal card exit pill', async () => {
// The model-facing result ends in `[exit code: 3]`, which the terminal card
// consumes into its own `[exit 3]` pill. Rendering both would report the same
// exit twice, so the marker must not survive into the card body.
const output = await smoke({
label: 'tui-agent bash exit pill',
tempDirPrefix: 'dsh-bash-exit-pill-',
configPath: scriptedConfigPath,
actions: [
...SELECT_PRO_MODEL,
{
waitFor: 'Model selected: tui-scripted/tui-scripted-model-pro.',
send: 'Run the failing scripted command.\r',
},
{ waitFor: 'Scripted bash failure observed.', send: '/exit\r' },
],
})
// The command really ran: its stdout is in the card body.
expect(output).toContain('SCRIPTED_BASH_FAILED')
expect(output).toContain('[exit 3]')
expect(output).not.toContain('[exit code: 3]')
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
it('tells the model its source path and offers the bundled maintenance skills', async () => {
// The launcher resolves the checkout root three hops up from apps/cli/{src,lib};
// this test file sits an equal depth under the same root, so the same hop applies.