@sparkelf/dsh-plus 0.2.1-rc.2.4 → 0.2.2-alpha.2.2

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.
Files changed (37) hide show
  1. package/README.md +7 -5
  2. package/README.zh.md +7 -5
  3. package/lib/apply-08huHfLG.js +665 -0
  4. package/lib/apply-DmICvIGh.js +653 -0
  5. package/lib/apply.js +1 -1
  6. package/lib/bin.js +188 -45
  7. package/lib/types/apply.d.ts +1 -1
  8. package/lib/types/apply.js +56 -117
  9. package/lib/types/patch-set.d.ts +59 -0
  10. package/lib/types/patch-set.js +132 -0
  11. package/lib/types/standalone-cli.js +93 -2
  12. package/lib/types/standalone-profile.d.ts +8 -1
  13. package/lib/types/standalone-profile.js +126 -49
  14. package/package.json +602 -175
  15. package/patches/approval-allow-policy.patch +108 -0
  16. package/patches/atomic-write-zombie-lock-holder.patch +53 -0
  17. package/patches/bootstrap-exempt-names.patch +32 -0
  18. package/patches/browser-auth-mode.patch +127 -0
  19. package/patches/composer-popover-boundaries.patch +337 -0
  20. package/patches/diagram-preview.patch +562 -0
  21. package/patches/image-offload-hoist-replay.patch +533 -0
  22. package/patches/legacy-code-preset.patch +148 -0
  23. package/patches/llm-pi-ai-transport-truncation.patch +33 -0
  24. package/patches/mobile-journal-generation.patch +76 -0
  25. package/patches/office-preview-stable-load.patch +34 -0
  26. package/patches/officecli-deliverables.patch +42 -0
  27. package/patches/ptc-mcp-schema-types.patch +156 -0
  28. package/patches/responses-reasoning-status.patch +289 -0
  29. package/patches/session-envelope-ignorable.patch +63 -0
  30. package/patches/session-export-chinese.patch +85 -0
  31. package/patches/session-format-legacy-restart.patch +35 -0
  32. package/patches/session-log-trajectory-toolbar.patch +352 -0
  33. package/patches/state-dot-brand-blue.patch +185 -0
  34. package/patches/ui-settings-compact-phone.patch +76 -0
  35. package/patches/web-runtime-compat.patch +83 -0
  36. package/patches/workspace-storage-restore.patch +186 -0
  37. package/patches/wsl-native-open.patch +265 -0
@@ -0,0 +1,108 @@
1
+ diff --git a/packages/bundle/base/cordis.patch.yml b/packages/bundle/base/cordis.patch.yml
2
+ index 863d194bf5..2d1a7d4c40 100644
3
+ --- a/packages/bundle/base/cordis.patch.yml
4
+ +++ b/packages/bundle/base/cordis.patch.yml
5
+ @@ -259,7 +259,7 @@
6
+ - id: approval
7
+ name: '@deepseek-ai/dsh-user-approval'
8
+ config:
9
+ - policy: !!js "(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'never' : 'ask'"
10
+ + policy: !!js "(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'allow' : 'ask'"
11
+
12
+ - id: permission
13
+ name: '@deepseek-ai/dsh-permission-presets'
14
+ @@ -273,7 +273,7 @@
15
+ approval: ask
16
+ danger-full-access:
17
+ sandbox: danger-full-access
18
+ - approval: never
19
+ + approval: allow
20
+
21
+ - id: shell-env
22
+ name: '@deepseek-ai/dsh-shell-env'
23
+ diff --git a/packages/interaction/permission-presets/src/index.ts b/packages/interaction/permission-presets/src/index.ts
24
+ index b071d0297a..628f8f42ea 100644
25
+ --- a/packages/interaction/permission-presets/src/index.ts
26
+ +++ b/packages/interaction/permission-presets/src/index.ts
27
+ @@ -111,7 +111,7 @@ const permissionStateSchema: zod.ZodType<PermissionProjectionState> = zod.object
28
+ zod.literal('workspace-write'),
29
+ zod.literal('danger-full-access'),
30
+ ]).nullable(),
31
+ - approval: zod.union([zod.literal('ask'), zod.literal('never')]).nullable(),
32
+ + approval: zod.union([zod.literal('ask'), zod.literal('never'), zod.literal('allow')]).nullable(),
33
+ seeded: zod.boolean(),
34
+ }).strict()
35
+
36
+ diff --git a/packages/interaction/user-approval/src/index.ts b/packages/interaction/user-approval/src/index.ts
37
+ index df48aca514..82c3f39f19 100644
38
+ --- a/packages/interaction/user-approval/src/index.ts
39
+ +++ b/packages/interaction/user-approval/src/index.ts
40
+ @@ -64,20 +64,27 @@ const OUTCOMES: readonly ApprovalOutcome[] = ['allowed-once', 'rejected', 'cance
41
+ * - `'never'` — never prompt anyone: every ask resolves `'rejected'`
42
+ * deterministically. The strict headless stance (CI, unattended runs) and
43
+ * the policy whose outcome is knowable without asking.
44
+ + * - `'allow'` — never prompt anyone either, and resolve every ask
45
+ + * `'allowed-once'` instead. The full-access stance: an operation that
46
+ + * would have asked is admitted, which is what `danger-full-access` means
47
+ + * and what `'never'` cannot express.
48
+ */
49
+ -export type ApprovalPolicy = 'ask' | 'never'
50
+ +export type ApprovalPolicy = 'ask' | 'never' | 'allow'
51
+
52
+ /** Every {@link ApprovalPolicy}, for option advertisement and runtime validation of untrusted policy strings. */
53
+ -export const APPROVAL_POLICIES: readonly ApprovalPolicy[] = ['ask', 'never']
54
+ +export const APPROVAL_POLICIES: readonly ApprovalPolicy[] = ['ask', 'never', 'allow']
55
+
56
+ /** Model-facing statement for the deterministic `'never'` policy. */
57
+ const NEVER_SENTENCE = 'Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).'
58
+ /** Model-facing statement for an interactive policy that may still fail closed. */
59
+ const ASK_SENTENCE = 'Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.'
60
+ +/** Model-facing statement for the deterministic `'allow'` policy. */
61
+ +const ALLOW_SENTENCE = 'Approval policy: allow. Operations that require approval are approved automatically; no prompt is shown and sandbox escalation is permitted.'
62
+ /** Model-facing policy names in switch notices; a bare `never` reads as "approval is never granted". */
63
+ const POLICY_NOTICE_NAMES: Readonly<Record<ApprovalPolicy, string>> = {
64
+ ask: 'ask the user for approval',
65
+ never: 'never ask the user for approval',
66
+ + allow: 'allow operations without asking',
67
+ }
68
+
69
+ interface ApprovalState {
70
+ @@ -90,7 +97,7 @@ const approvalStateDefinition = {
71
+ key: 'approvalState',
72
+ stateVersion: 1,
73
+ stateSchema: zod.object({
74
+ - policy: zod.enum(['ask', 'never']).nullable(),
75
+ + policy: zod.enum(['ask', 'never', 'allow']).nullable(),
76
+ turnOpen: zod.boolean(),
77
+ }),
78
+ init: (): ApprovalState => ({ policy: null, turnOpen: false }),
79
+ @@ -168,7 +175,7 @@ export class ApprovalService extends Service {
80
+ static inject = ['sessionProjections']
81
+
82
+ static Config: z<Config> = z.object({
83
+ - policy: z.union(['ask', 'never'] as const).default('ask'),
84
+ + policy: z.union(['ask', 'never', 'allow'] as const).default('ask'),
85
+ })
86
+
87
+ private approvalProjectionHandle!: ProjectionHandle<ApprovalState>
88
+ @@ -195,7 +202,8 @@ export class ApprovalService extends Service {
89
+ // A bare assemble() (tests, diagnostics) has no session to state.
90
+ if (agent === undefined) return ''
91
+ const policy = effective(agent)
92
+ - return policy === 'never' ? NEVER_SENTENCE : ASK_SENTENCE
93
+ + if (policy === 'never') return NEVER_SENTENCE
94
+ + return policy === 'allow' ? ALLOW_SENTENCE : ASK_SENTENCE
95
+ },
96
+ })
97
+ })
98
+ @@ -299,6 +307,10 @@ export class ApprovalService extends Service {
99
+ // documented promise that 'never' rejects deterministically regardless
100
+ // of registration order — only the service's own request path can.
101
+ if (this.effectivePolicy(session) === 'never') return 'rejected'
102
+ + // 'allow' is the mirror of 'never' and is decided at the same point for the same
103
+ + // reason: the outcome must not depend on which answerers happen to be composed or on
104
+ + // listener registration order.
105
+ + if (this.effectivePolicy(session) === 'allow') return 'allowed-once'
106
+ // Enter the promise chain BEFORE dispatching: a listener that throws
107
+ // SYNCHRONOUSLY (before its first await) must land in the same rejection
108
+ // path as an async one — `Promise.resolve(call())` would let it escape
@@ -0,0 +1,53 @@
1
+ diff --git a/packages/util/atomic-write/src/index.ts b/packages/util/atomic-write/src/index.ts
2
+ index 60e2c67b8c..cfe7c7824f 100644
3
+ --- a/packages/util/atomic-write/src/index.ts
4
+ +++ b/packages/util/atomic-write/src/index.ts
5
+ @@ -12,6 +12,7 @@
6
+ */
7
+
8
+ import { createHash, randomBytes } from 'node:crypto'
9
+ +import { readFileSync } from 'node:fs'
10
+ import { lstat, mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises'
11
+ import { dirname } from 'node:path'
12
+ import { setTimeout as wait } from 'node:timers/promises'
13
+ @@ -119,13 +120,39 @@ function holderExited(record: string): boolean {
14
+ if (pid === process.pid) return false
15
+ try {
16
+ process.kill(pid, 0)
17
+ - return false
18
+ + // The signal probe accepts a zombie: its PID is still issued, so `kill` succeeds even
19
+ + // though the holder has exited and will never release the lock. `/proc` reports that
20
+ + // state directly, and a zombie proves the holder is gone.
21
+ + return zombieState(pid)
22
+ } catch (error) {
23
+ // EPERM means the process exists under another user.
24
+ return (error as NodeJS.ErrnoException).code === 'ESRCH'
25
+ }
26
+ }
27
+
28
+ +/**
29
+ + * Whether `pid` is a zombie: a holder that exited but was not reaped yet.
30
+ + *
31
+ + * Only Linux exposes this, and only through `/proc`: elsewhere the signal probe stays the
32
+ + * whole answer. A read failure proves nothing about the holder, so it reads as "still alive"
33
+ + * and leaves the lock alone, because taking a live holder's lock is the worse misjudgement.
34
+ + * @param pid - the positive int32 PID the lock record names.
35
+ + * @returns true only when `/proc/<pid>/stat` says the process is `Z`.
36
+ + */
37
+ +function zombieState(pid: number): boolean {
38
+ + let stat: string
39
+ + try {
40
+ + stat = readFileSync('/proc/' + String(pid) + '/stat', 'utf8')
41
+ + } catch {
42
+ + return false
43
+ + }
44
+ + // The command name sits in parentheses and may contain spaces or parentheses, so the
45
+ + // fields after it are located from the LAST ")" rather than by splitting the whole line.
46
+ + const close = stat.lastIndexOf(')')
47
+ + if (close < 0 || close + 2 >= stat.length) return false
48
+ + return stat.charAt(close + 2) === 'Z'
49
+ +}
50
+ +
51
+ /** The lock file's content, or undefined when it cannot be read. */
52
+ async function readLockRecord(lockPath: string): Promise<string | undefined> {
53
+ try {
@@ -0,0 +1,32 @@
1
+ diff --git a/packages/boot/app-boot/src/index.ts b/packages/boot/app-boot/src/index.ts
2
+ index 22332c3..ae00ae0 100644
3
+ --- a/packages/boot/app-boot/src/index.ts
4
+ +++ b/packages/boot/app-boot/src/index.ts
5
+ @@ -156,6 +156,19 @@ const BOOTSTRAP_NAMES = new Set([
6
+ /** Name prefixes no discovered file may set. */
7
+ const BOOTSTRAP_PREFIXES = ['DSH_', 'XDG_', 'DYLD_', 'BASH_FUNC_']
8
+
9
+ +/**
10
+ + * Names the prefix rule must not refuse, because they decide nothing about process launch,
11
+ + * module resolution, VCS hooks, or network trust. The rule protects what a variable controls;
12
+ + * a shared prefix is a proxy for that property rather than the property itself, so the names
13
+ + * that lack it are listed here instead of the prefix being narrowed for every deployment.
14
+ + *
15
+ + * DSH_OFFICE_FONT_DIR names the directory the office-to-pdf provider indexes for document
16
+ + * fonts. The deployment that ships the provider declares that row in its own bundle, so the
17
+ + * value belongs with the bundle configuration and must be settable from a .env like any other
18
+ + * deployment value. The launch-critical names (DSH_HOME, DSH_SNAPSHOT) stay refused.
19
+ + */
20
+ +const BOOTSTRAP_EXEMPT_NAMES = new Set(['DSH_OFFICE_FONT_DIR'])
21
+ +
22
+ /**
23
+ * The bootstrap names the Harness-home `.env` alone may set. A proxy chooses the route every
24
+ * request takes, so the invoking directory's file — which arrives with a clone — keeps refusing
25
+ @@ -174,6 +187,7 @@ const HOME_LAYER_PROXY_NAMES = new Set(['HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY'
26
+ */
27
+ function isBootstrapOnly(name: string): boolean {
28
+ const upper = name.toUpperCase()
29
+ + if (BOOTSTRAP_EXEMPT_NAMES.has(upper)) return false
30
+ return BOOTSTRAP_NAMES.has(upper) || BOOTSTRAP_PREFIXES.some(prefix => upper.startsWith(prefix))
31
+ }
32
+
@@ -0,0 +1,127 @@
1
+ diff --git a/packages/client/connection/src/browser-auth.ts b/packages/client/connection/src/browser-auth.ts
2
+ index 5c8594df6f..e5f5fa9f44 100644
3
+ --- a/packages/client/connection/src/browser-auth.ts
4
+ +++ b/packages/client/connection/src/browser-auth.ts
5
+ @@ -198,12 +198,40 @@ async function initializeSecret(credentials: CredentialProvider): Promise<Buffer
6
+ return secret
7
+ }
8
+
9
+ +/** Browser-session policy selected once while Connection activates. */
10
+ +export interface BrowserAuthentication {
11
+ + /** Add any authentication input to the clean application URL. */
12
+ + authenticatedUrl(baseUrl: string): string
13
+ + /** Authorize one frontend index request, owning the response when denied or redirected. */
14
+ + authorizeIndex(req: ConnectionIndexRequest, res: ConnectionIndexResponse, secure?: boolean): boolean
15
+ + /** Authorize one connect request, owning the response when denied or redirected. */
16
+ + authorizeConnect(
17
+ + req: ConnectionIndexRequest,
18
+ + res: ConnectionIndexResponse,
19
+ + secure: boolean,
20
+ + publish: (interactions: ConnectionInteractions) => PeerId,
21
+ + ): void
22
+ + /** Decide whether a trusted Host request has the required browser identity. */
23
+ + isAuthenticated(request: ConnectionTrustRequest, secure?: boolean): boolean
24
+ + /** Resolve the peer identity of an authenticated API request, or undefined when it is not. */
25
+ + authenticateApi(request: ConnectionTrustRequest, secure?: boolean): PeerId | undefined
26
+ +}
27
+ +
28
+ +/** Profile-selected policy that admits every request after the independent Host/Origin fence. */
29
+ +export const DISABLED_BROWSER_AUTHENTICATION: BrowserAuthentication = {
30
+ + authenticatedUrl: baseUrl => new URL(baseUrl).href,
31
+ + authorizeIndex: () => true,
32
+ + authorizeConnect: () => undefined,
33
+ + isAuthenticated: () => true,
34
+ + authenticateApi: () => undefined,
35
+ +}
36
+ +
37
+ /**
38
+ * Process launch-token exchange and persistent signed-cookie verification.
39
+ * Connection loads the credential provider's signing secret during activation
40
+ * and retains it for synchronous request authentication.
41
+ */
42
+ -export class BrowserAuth {
43
+ +export class BrowserAuth implements BrowserAuthentication {
44
+ private readonly launchToken: string
45
+ private readonly maxAgeMilliseconds: number
46
+
47
+ diff --git a/packages/client/connection/src/index.ts b/packages/client/connection/src/index.ts
48
+ index d563cd40e8..0c0d2279f8 100644
49
+ --- a/packages/client/connection/src/index.ts
50
+ +++ b/packages/client/connection/src/index.ts
51
+ @@ -10,7 +10,7 @@ import { API_PATH } from './api-path.ts'
52
+ import { CONNECT_PATH } from './connect-protocol.ts'
53
+ import { bridge, DEFAULT_MAX_REQUEST_BODY_BYTES } from './http-bridge.ts'
54
+ import { assertTrustedAuthority } from './api-request-trust.ts'
55
+ -import { BrowserAuth } from './browser-auth.ts'
56
+ +import { BrowserAuth, DISABLED_BROWSER_AUTHENTICATION } from './browser-auth.ts'
57
+ import { HostConnectionService } from './rpc-host.ts'
58
+ import { ConnectionRecoveryConfigSchema, resolveConnectionConfig, type ConnectionRecoveryConfig } from './recovery-config.ts'
59
+
60
+ @@ -103,6 +103,8 @@ export interface ConnectionConfig {
61
+ * an entry here. A non-canonical or non-bare authority fails plugin load.
62
+ */
63
+ trustedHosts?: string[]
64
+ + /** Host API browser identity policy; Host/Origin trust remains independent. Default: `required`. */
65
+ + browserAuthentication?: 'required' | 'disabled'
66
+ /** Absolute browser and API cookie lifetime in days. Default: 30. */
67
+ cookieMaxAgeDays?: number
68
+ /** Maximum buffered JSON body for every `/api` request. Default: 300 MiB. */
69
+ @@ -112,6 +114,7 @@ export interface ConnectionConfig {
70
+ export const Config: z<ConnectionConfig> = z.object({
71
+ recovery: ConnectionRecoveryConfigSchema.default({}),
72
+ trustedHosts: z.array(String).default([]),
73
+ + browserAuthentication: z.union(['required', 'disabled']).default('required'),
74
+ cookieMaxAgeDays: z.natural().min(1).default(30),
75
+ maxRequestBodyBytes: z.natural().min(1).default(DEFAULT_MAX_REQUEST_BODY_BYTES),
76
+ })
77
+ @@ -127,16 +130,21 @@ export async function apply(ctx: Context, config?: ConnectionConfig): Promise<vo
78
+ const recovery = resolveConnectionConfig(config?.recovery)
79
+ // The Loader resolves schema defaults; hand-built test contexts may pass none.
80
+ const trustedHosts = config?.trustedHosts ?? []
81
+ + const browserAuthentication = config?.browserAuthentication ?? 'required'
82
+ const cookieMaxAgeDays = config?.cookieMaxAgeDays ?? 30
83
+ const maxRequestBodyBytes = config?.maxRequestBodyBytes ?? DEFAULT_MAX_REQUEST_BODY_BYTES
84
+ // Config boundary: a malformed entry fails the load loudly here rather than
85
+ // silently authorizing its hostname prefix at request time.
86
+ for (const entry of trustedHosts) assertTrustedAuthority(entry)
87
+ assertImageBodyCapacity(ctx, maxRequestBodyBytes)
88
+ + // 配置在激活时只选择一次策略;disabled不会创建token或credential record。
89
+ + const browserAuth = browserAuthentication === 'required'
90
+ + ? await BrowserAuth.create(ctx.root, ctx.credentials, cookieMaxAgeDays)
91
+ + : DISABLED_BROWSER_AUTHENTICATION
92
+ const connection = new HostConnectionService(
93
+ ctx,
94
+ trustedHosts,
95
+ - await BrowserAuth.create(ctx.root, ctx.credentials, cookieMaxAgeDays),
96
+ + browserAuth,
97
+ )
98
+ ctx.inject(['webServer'], (webCtx) => {
99
+ assertImageBodyCapacity(webCtx, maxRequestBodyBytes)
100
+ diff --git a/packages/client/connection/src/rpc-host.ts b/packages/client/connection/src/rpc-host.ts
101
+ index 88e7b48baa..43a3feb74c 100644
102
+ --- a/packages/client/connection/src/rpc-host.ts
103
+ +++ b/packages/client/connection/src/rpc-host.ts
104
+ @@ -13,7 +13,7 @@ import { clientRequestSchema } from './rpc-schema.ts'
105
+ import { bridge } from './http-bridge.ts'
106
+ import { isRemoteAuthority, isTrustedApiRequest } from './api-request-trust.ts'
107
+ import { API_PATH } from './api-path.ts'
108
+ -import type { BrowserAuth } from './browser-auth.ts'
109
+ +import type { BrowserAuthentication } from './browser-auth.ts'
110
+ import { Peer } from './peer.ts'
111
+ import type {
112
+ PeerAdmission,
113
+ @@ -82,12 +82,12 @@ export class HostConnectionService extends Service implements HostConnectionHand
114
+ * Provide the Host half over the active HTTP server.
115
+ * @param ctx - owning Connection plugin context.
116
+ * @param trustedHosts - deployment authorities accepted by the Host/Origin fence.
117
+ - * @param browserAuth - process token and persistent browser-session owner.
118
+ + * @param browserAuth - profile-selected browser-session policy.
119
+ */
120
+ constructor(
121
+ ctx: Context,
122
+ private readonly trustedHosts: readonly string[],
123
+ - private readonly browserAuth: BrowserAuth,
124
+ + private readonly browserAuth: BrowserAuthentication,
125
+ ) {
126
+ super(ctx, 'connection')
127
+ this.owner = ctx