@veewo/dsh-better-tasks 0.0.0-stage → 0.4.4
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.
- package/LICENSE +21 -0
- package/README.md +64 -2
- package/cordis.patch.yml +32 -0
- package/docs/dsh-0.2.2-rc.3-support.md +68 -0
- package/docs/install-rc3.md +94 -0
- package/docs/install.md +156 -0
- package/docs/rc2-slash-submit-catch.md +58 -0
- package/docs/release.md +65 -0
- package/docs/rollback.md +78 -0
- package/docs/sidebar-layout.md +72 -0
- package/docs/task-session-activity.md +13 -0
- package/lib/client.js +3184 -0
- package/package.json +78 -4
- package/src/client.js +2355 -0
- package/src/final-message.mjs +75 -0
- package/src/index.js +222 -0
- package/src/native-pin-host.mjs +309 -0
- package/src/pin-client.mjs +204 -0
- package/src/pin-model.mjs +118 -0
- package/src/project-appearance.mjs +147 -0
- package/src/project-icon-catalog.mjs +4 -0
- package/src/skill-picker.mjs +26 -0
- package/src/task-actions.mjs +80 -0
- package/src/task-execution.mjs +37 -0
- package/src/task-input-surface.mjs +152 -0
- package/src/task-model.mjs +235 -0
- package/src/task-preferences.mjs +139 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
export const SIDEBAR_CHILD_SLOTS = Object.freeze([
|
|
2
|
+
'sidebar.brand.mark',
|
|
3
|
+
'sidebar.brand.name',
|
|
4
|
+
'sidebar.panellist',
|
|
5
|
+
'sidebar.panellist.actions',
|
|
6
|
+
'sidebar.workspaces',
|
|
7
|
+
'sidebar.settings',
|
|
8
|
+
'sidebar.footer.action',
|
|
9
|
+
'sidebar.session.action',
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
export function createSidebarChildDeclarations({ iconsInFactory = false } = {}) {
|
|
13
|
+
return Object.fromEntries(SIDEBAR_CHILD_SLOTS.filter(name => !iconsInFactory || name !== 'sidebar.panellist').map((name) => [name, {
|
|
14
|
+
kind: name === 'sidebar.session.action' ? 'hoc'
|
|
15
|
+
: name === 'sidebar.panellist' || name === 'sidebar.panellist.actions' || name === 'sidebar.footer.action' ? 'list' : 'single',
|
|
16
|
+
scope: 'root',
|
|
17
|
+
}]))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const ACTIVITY_RANGE_DAYS = Object.freeze({
|
|
21
|
+
all: null,
|
|
22
|
+
'1d': 1,
|
|
23
|
+
'3d': 3,
|
|
24
|
+
'7d': 7,
|
|
25
|
+
'14d': 14,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const DAY_MS = 24 * 60 * 60 * 1000
|
|
29
|
+
|
|
30
|
+
function compareStableRecency(left, right) {
|
|
31
|
+
if (left.updatedAt !== right.updatedAt) return right.updatedAt - left.updatedAt
|
|
32
|
+
const leftId = String(left.id)
|
|
33
|
+
const rightId = String(right.id)
|
|
34
|
+
return leftId < rightId ? -1 : leftId > rightId ? 1 : 0
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function beforeIdForTaskDrop(orderedIds, overId, half) {
|
|
38
|
+
if (half !== 'before' && half !== 'after') throw new Error(`unsupported drop half: ${half}`)
|
|
39
|
+
const index = orderedIds.indexOf(overId)
|
|
40
|
+
if (index === -1) throw new Error(`unknown drop target: ${overId}`)
|
|
41
|
+
return half === 'before' ? overId : orderedIds[index + 1] ?? null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function indexWorkspacesBySession(workspaces) {
|
|
45
|
+
const index = new Map()
|
|
46
|
+
for (const workspace of workspaces) {
|
|
47
|
+
for (const sessionId of workspace.sessionIds) {
|
|
48
|
+
if (!index.has(sessionId)) index.set(sessionId, workspace)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return index
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function loadProjectAppearances(fetchImpl, url = '/api/project-appearance') {
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetchImpl(url, { cache: 'no-store' })
|
|
57
|
+
if (!response.ok) throw new Error(`appearance request failed: ${response.status}`)
|
|
58
|
+
const payload = await response.json()
|
|
59
|
+
if (!Array.isArray(payload.items) || !Array.isArray(payload.icons)) {
|
|
60
|
+
throw new Error('appearance response is missing items or icons')
|
|
61
|
+
}
|
|
62
|
+
return Object.freeze({
|
|
63
|
+
status: 'ready',
|
|
64
|
+
items: payload.items,
|
|
65
|
+
icons: payload.icons,
|
|
66
|
+
colorKeys: Array.isArray(payload.colorKeys) ? payload.colorKeys : [],
|
|
67
|
+
})
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
status: 'unavailable',
|
|
71
|
+
items: [],
|
|
72
|
+
icons: [],
|
|
73
|
+
colorKeys: [],
|
|
74
|
+
error: String(error?.message ?? error),
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function summarizeGoal(value) {
|
|
80
|
+
if (value === undefined) return Object.freeze({ availability: 'unknown' })
|
|
81
|
+
if (value === null || value.goal.phase === 'complete') return Object.freeze({ availability: 'none' })
|
|
82
|
+
const reason = value.goal.blockedReason
|
|
83
|
+
return Object.freeze({
|
|
84
|
+
availability: 'ready',
|
|
85
|
+
id: value.goal.id,
|
|
86
|
+
phase: value.goal.phase,
|
|
87
|
+
objective: value.goal.objective,
|
|
88
|
+
roundsStarted: value.roundsStarted,
|
|
89
|
+
maxGoalRounds: value.goal.maxGoalRounds,
|
|
90
|
+
blockedReason: reason === undefined ? undefined : Object.freeze({ code: reason.code, message: reason.message }),
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function summarizeTodos(value) {
|
|
95
|
+
if (value === undefined) return Object.freeze({ availability: 'unknown' })
|
|
96
|
+
if (value === null) return Object.freeze({ availability: 'none' })
|
|
97
|
+
const counts = { pending: 0, inProgress: 0, completed: 0, total: value.length }
|
|
98
|
+
const items = value.map((todo) => {
|
|
99
|
+
if (todo.status === 'pending') counts.pending += 1
|
|
100
|
+
else if (todo.status === 'in_progress') counts.inProgress += 1
|
|
101
|
+
else if (todo.status === 'completed') counts.completed += 1
|
|
102
|
+
return Object.freeze({ content: todo.content, status: todo.status })
|
|
103
|
+
})
|
|
104
|
+
return Object.freeze({ availability: 'ready', ...counts, items: Object.freeze(items) })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Warning is presentation only: keep idle actions available for stopped tasks. */
|
|
108
|
+
export function taskExecutionWarning(summary, pendingInteraction, connectionState, catalogReady = true) {
|
|
109
|
+
if (connectionState === 'disconnected' || connectionState === 'connecting') return { kind: 'disconnected', detail: '' }
|
|
110
|
+
if (pendingInteraction != null || summary.running || !catalogReady) return null
|
|
111
|
+
const execution = summary.projectionValues?.betterTasksExecution
|
|
112
|
+
if (execution == null) return null // No capability / not yet hydrated is not failure.
|
|
113
|
+
if (execution.phase === 'running') return { kind: 'interrupted', detail: '' }
|
|
114
|
+
if (execution.phase !== 'ended') return null
|
|
115
|
+
const kind = execution.outcome
|
|
116
|
+
if (!['error', 'aborted', 'interrupted', 'max-tokens'].includes(kind)) return null
|
|
117
|
+
return { kind, detail: typeof execution.detail === 'string' ? execution.detail : '' }
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function summarizeSession(summary, pendingInteraction) {
|
|
121
|
+
if (pendingInteraction !== undefined) {
|
|
122
|
+
return Object.freeze({ status: 'block', interaction: pendingInteraction.kind })
|
|
123
|
+
}
|
|
124
|
+
if (summary.running) return Object.freeze({ status: 'running' })
|
|
125
|
+
return Object.freeze({ status: 'idle' })
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Fold native live status through native subagent catalog edges, not fork ancestry.
|
|
130
|
+
* Status events win over older list rows, including explicit false. Walk upward
|
|
131
|
+
* once so grandchildren count even while their direct parent is idle. No retain,
|
|
132
|
+
* polling, execution ownership, or inference from a persisted open turn.
|
|
133
|
+
*/
|
|
134
|
+
export function runningSessionTreeIds(list, sessionStatuses = new Map()) {
|
|
135
|
+
const rows = list.byId
|
|
136
|
+
const projections = list.projectionsBySession ?? {}
|
|
137
|
+
const parents = new Map()
|
|
138
|
+
const ids = new Set([...Object.keys(rows), ...Object.keys(projections), ...sessionStatuses.keys()])
|
|
139
|
+
for (const id of ids) {
|
|
140
|
+
const catalog = projections[id]?.values?.subagentCatalog ?? rows[id]?.projectionValues?.subagentCatalog
|
|
141
|
+
if (!Array.isArray(catalog)) continue
|
|
142
|
+
for (const child of catalog) {
|
|
143
|
+
ids.add(child.id)
|
|
144
|
+
if (!parents.has(child.id)) parents.set(child.id, new Set())
|
|
145
|
+
parents.get(child.id).add(id)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const running = new Set([...ids].filter((id) => (sessionStatuses.get(id)?.running ?? rows[id]?.running) === true))
|
|
149
|
+
const queue = [...running]
|
|
150
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
151
|
+
for (const parent of parents.get(queue[index]) ?? []) {
|
|
152
|
+
if (running.has(parent)) continue // Also bounds malformed cyclic catalogs.
|
|
153
|
+
running.add(parent)
|
|
154
|
+
queue.push(parent)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return running
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function projectTask(summary, currentId, pendingInteraction) {
|
|
161
|
+
const projections = summary.projectionValues
|
|
162
|
+
return Object.freeze({
|
|
163
|
+
id: summary.id,
|
|
164
|
+
title: summary.displayTitle ?? summary.title ?? String(summary.id),
|
|
165
|
+
cwd: summary.cwd,
|
|
166
|
+
updatedAt: summary.updatedAt,
|
|
167
|
+
current: summary.id === currentId,
|
|
168
|
+
goal: summarizeGoal(projections?.goal),
|
|
169
|
+
todos: summarizeTodos(projections?.todos),
|
|
170
|
+
session: summarizeSession(summary, pendingInteraction),
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Match the native 0.2 browser's main-view selection, not Agent running state. */
|
|
175
|
+
export function currentTaskSessionId(list, panelActive = false) {
|
|
176
|
+
if (panelActive) return undefined
|
|
177
|
+
const rows = Object.values(list.byId)
|
|
178
|
+
// 0.2's catalog removed list.current; retention is updated as the view switches.
|
|
179
|
+
// Never fall back to an obsolete current id once the new contract is present.
|
|
180
|
+
if (rows.some((row) => Object.hasOwn(row, 'retainedBy'))) {
|
|
181
|
+
return rows.find((row) => (row.retainedBy?.mainView ?? 0) > 0)?.id
|
|
182
|
+
}
|
|
183
|
+
return list.current // Retained 0.1.5 compatibility only.
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Derive task cards in the exact Host-owned pin order. */
|
|
187
|
+
export function derivePinnedTasks({ list, pinnedSessionIds, pendingInteractions = new Map(), panelActive = false, sessionStatuses = new Map() }) {
|
|
188
|
+
if (!Array.isArray(pinnedSessionIds)) throw new Error('pinnedSessionIds must be an array')
|
|
189
|
+
const seen = new Set()
|
|
190
|
+
const tasks = []
|
|
191
|
+
const currentId = currentTaskSessionId(list, panelActive)
|
|
192
|
+
const runningIds = runningSessionTreeIds(list, sessionStatuses)
|
|
193
|
+
for (const id of pinnedSessionIds) {
|
|
194
|
+
if (seen.has(id)) continue
|
|
195
|
+
seen.add(id)
|
|
196
|
+
const summary = list.byId[id]
|
|
197
|
+
if (summary === undefined || summary.blank || summary.origin === 'subagent') continue
|
|
198
|
+
tasks.push(projectTask({ ...summary, running: runningIds.has(id) }, currentId, pendingInteractions.get(id)))
|
|
199
|
+
}
|
|
200
|
+
return tasks
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Legacy bounded recent projection retained for compatibility tests and rollback.
|
|
205
|
+
* The task UI no longer uses it as membership or ordering authority.
|
|
206
|
+
*/
|
|
207
|
+
export function deriveRecentTasks({
|
|
208
|
+
list,
|
|
209
|
+
archivedSessionIds = [],
|
|
210
|
+
pendingInteractions = new Map(),
|
|
211
|
+
activityRange = 'all',
|
|
212
|
+
now = Date.now(),
|
|
213
|
+
limit = 10,
|
|
214
|
+
}) {
|
|
215
|
+
if (!Object.hasOwn(ACTIVITY_RANGE_DAYS, activityRange)) {
|
|
216
|
+
throw new Error(`unsupported activity range: ${activityRange}`)
|
|
217
|
+
}
|
|
218
|
+
if (!Number.isSafeInteger(limit) || limit < 0) throw new Error('limit must be a non-negative safe integer')
|
|
219
|
+
|
|
220
|
+
const archived = new Set(archivedSessionIds)
|
|
221
|
+
const days = ACTIVITY_RANGE_DAYS[activityRange]
|
|
222
|
+
const cutoff = days === null ? Number.NEGATIVE_INFINITY : now - days * DAY_MS
|
|
223
|
+
const visible = []
|
|
224
|
+
|
|
225
|
+
for (const id of list.ids) {
|
|
226
|
+
const summary = list.byId[id]
|
|
227
|
+
if (summary === undefined) continue
|
|
228
|
+
if (summary.blank || summary.origin === 'subagent' || archived.has(summary.id)) continue
|
|
229
|
+
if (summary.updatedAt < cutoff) continue
|
|
230
|
+
visible.push(summary)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
visible.sort(compareStableRecency)
|
|
234
|
+
return visible.slice(0, limit).map((summary) => projectTask(summary, list.current, pendingInteractions.get(summary.id)))
|
|
235
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export const TASK_SETTINGS_NAMESPACE = 'better-tasks-ui'
|
|
2
|
+
// 0.2 dsh-settings.describe emits ns=entry.options.id and write resolves that
|
|
3
|
+
// same options.id (lib/index.js:443,502 in 0.2.0-rc.2). Inspect's qualified
|
|
4
|
+
// loader id (include:better-tasks-ui) is NOT the settings namespace.
|
|
5
|
+
export const TASK_SETTINGS_ENTRY_ID = TASK_SETTINGS_NAMESPACE
|
|
6
|
+
export const TASK_DETAIL_FONT_MIN = 11
|
|
7
|
+
export const TASK_DETAIL_FONT_MAX = 16
|
|
8
|
+
export const TASK_COLUMN_LAYOUTS = Object.freeze(['auto', 'single', 'double'])
|
|
9
|
+
export const TASK_TITLE_EMPHASES = Object.freeze(['project', 'task'])
|
|
10
|
+
/** Sidebar body the browser shows: the native session list or the task swimlane. */
|
|
11
|
+
export const TASK_SIDEBAR_VIEWS = Object.freeze(['native', 'tasks'])
|
|
12
|
+
export const DEFAULT_TASK_PREFERENCES = Object.freeze({
|
|
13
|
+
detailFontSize: 13,
|
|
14
|
+
defaultTodoExpanded: false,
|
|
15
|
+
defaultGoalExpanded: false,
|
|
16
|
+
defaultFinalExpanded: true,
|
|
17
|
+
defaultInputExpanded: false,
|
|
18
|
+
columnLayout: 'auto',
|
|
19
|
+
titleEmphasis: 'task',
|
|
20
|
+
sidebarView: 'native',
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const FIELDS = new Set(Object.keys(DEFAULT_TASK_PREFERENCES))
|
|
24
|
+
|
|
25
|
+
export function decodeTaskPreferences(value) {
|
|
26
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined
|
|
27
|
+
const detailFontSize = value.detailFontSize
|
|
28
|
+
const columnLayout = value.columnLayout
|
|
29
|
+
const titleEmphasis = value.titleEmphasis === undefined ? DEFAULT_TASK_PREFERENCES.titleEmphasis : value.titleEmphasis
|
|
30
|
+
if (!TASK_TITLE_EMPHASES.includes(titleEmphasis)) return undefined
|
|
31
|
+
// Absent reads as the default, like titleEmphasis: rows written before this
|
|
32
|
+
// preference existed must keep decoding.
|
|
33
|
+
const sidebarView = value.sidebarView === undefined ? DEFAULT_TASK_PREFERENCES.sidebarView : value.sidebarView
|
|
34
|
+
if (!TASK_SIDEBAR_VIEWS.includes(sidebarView)) return undefined
|
|
35
|
+
if (!Number.isInteger(detailFontSize) || detailFontSize < TASK_DETAIL_FONT_MIN || detailFontSize > TASK_DETAIL_FONT_MAX) return undefined
|
|
36
|
+
if (!TASK_COLUMN_LAYOUTS.includes(columnLayout)) return undefined
|
|
37
|
+
for (const field of ['defaultTodoExpanded', 'defaultGoalExpanded', 'defaultFinalExpanded', 'defaultInputExpanded']) {
|
|
38
|
+
if (typeof value[field] !== 'boolean') return undefined
|
|
39
|
+
}
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
detailFontSize,
|
|
42
|
+
defaultTodoExpanded: value.defaultTodoExpanded,
|
|
43
|
+
defaultGoalExpanded: value.defaultGoalExpanded,
|
|
44
|
+
defaultFinalExpanded: value.defaultFinalExpanded,
|
|
45
|
+
defaultInputExpanded: value.defaultInputExpanded,
|
|
46
|
+
columnLayout,
|
|
47
|
+
titleEmphasis,
|
|
48
|
+
sidebarView,
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function fallbackSnapshot() {
|
|
53
|
+
return Object.freeze({
|
|
54
|
+
status: 'unavailable',
|
|
55
|
+
value: DEFAULT_TASK_PREFERENCES,
|
|
56
|
+
revision: undefined,
|
|
57
|
+
writable: false,
|
|
58
|
+
mode: 'memory',
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createTaskPreferencesStore() {
|
|
63
|
+
let snapshot = fallbackSnapshot()
|
|
64
|
+
let scope
|
|
65
|
+
let unsubscribe
|
|
66
|
+
const listeners = new Set()
|
|
67
|
+
|
|
68
|
+
const publish = (next) => {
|
|
69
|
+
if (snapshot.status === next.status && snapshot.value === next.value && snapshot.revision === next.revision && snapshot.writable === next.writable && snapshot.mode === next.mode) return
|
|
70
|
+
snapshot = Object.freeze(next)
|
|
71
|
+
for (const listener of listeners) listener()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const sync = () => {
|
|
75
|
+
if (scope === undefined) return publish(fallbackSnapshot())
|
|
76
|
+
const source = scope.getSnapshot()
|
|
77
|
+
const decoded = decodeTaskPreferences(source.value)
|
|
78
|
+
publish({
|
|
79
|
+
status: source.status,
|
|
80
|
+
value: decoded ?? DEFAULT_TASK_PREFERENCES,
|
|
81
|
+
revision: source.revision,
|
|
82
|
+
// ConfigFormController may report the document's writable=true even when
|
|
83
|
+
// this entry is absent. Never write defaults into an unserved namespace.
|
|
84
|
+
writable: source.status === 'ready' && decoded !== undefined && source.writable === true,
|
|
85
|
+
mode: source.mode,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const disconnect = () => {
|
|
90
|
+
unsubscribe?.()
|
|
91
|
+
unsubscribe = undefined
|
|
92
|
+
scope = undefined
|
|
93
|
+
sync()
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return Object.freeze({
|
|
97
|
+
source: Object.freeze({
|
|
98
|
+
getSnapshot: () => snapshot,
|
|
99
|
+
subscribe(listener) {
|
|
100
|
+
listeners.add(listener)
|
|
101
|
+
return () => listeners.delete(listener)
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
104
|
+
connect(nextScope) {
|
|
105
|
+
disconnect()
|
|
106
|
+
scope = nextScope
|
|
107
|
+
sync()
|
|
108
|
+
unsubscribe = nextScope.subscribe(sync)
|
|
109
|
+
return () => {
|
|
110
|
+
if (scope !== nextScope) return
|
|
111
|
+
disconnect()
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
set(field, value) {
|
|
115
|
+
if (!FIELDS.has(field)) return Promise.reject(new TypeError(`unknown Better Tasks setting: ${String(field)}`))
|
|
116
|
+
if (scope === undefined || !snapshot.writable) return Promise.reject(new Error('Better Tasks settings are not writable'))
|
|
117
|
+
const optimistic = decodeTaskPreferences({ ...snapshot.value, [field]: value })
|
|
118
|
+
if (optimistic === undefined) return Promise.reject(new TypeError(`invalid Better Tasks setting: ${field}`))
|
|
119
|
+
const writeScope = scope
|
|
120
|
+
publish({ ...snapshot, value: optimistic })
|
|
121
|
+
const rejected = (error) => {
|
|
122
|
+
if (scope === writeScope) sync()
|
|
123
|
+
throw error
|
|
124
|
+
}
|
|
125
|
+
let result
|
|
126
|
+
try {
|
|
127
|
+
result = writeScope.set(field, value)
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return Promise.reject(error).catch(rejected)
|
|
130
|
+
}
|
|
131
|
+
return Promise.resolve(result).then((accepted) => {
|
|
132
|
+
// 0.2 resolves false after a rejected mutation and recovery read; it does
|
|
133
|
+
// not reject. Legacy settings scopes may resolve void on success.
|
|
134
|
+
if (accepted === false) throw new Error('Better Tasks settings write was rejected by Host')
|
|
135
|
+
return accepted
|
|
136
|
+
}).catch(rejected)
|
|
137
|
+
},
|
|
138
|
+
})
|
|
139
|
+
}
|