@dsh-electron/dsh-plugin-multi-root-workspace 0.1.10 → 0.1.12
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/CHANGELOG.en.md +25 -1
- package/CHANGELOG.md +25 -1
- package/README.en.md +11 -9
- package/README.md +11 -9
- package/cordis.patch.yml +3 -2
- package/lib/auto-review.js +1 -1
- package/lib/client.js +252 -129
- package/lib/client.js.map +1 -1
- package/lib/{command-BGdz7VBH.js → command-CmLekG1M.js} +3 -2
- package/lib/command.js +1 -1
- package/lib/{compat-D-MljsPV.js → compat-Bg2HFVMX.js} +6 -1
- package/lib/compat.js +1 -1
- package/lib/{sandbox-CXk5h-ct.js → dialects-Dr4rDR-H.js} +6 -264
- package/lib/{fs-BTXySwSG.js → fs-CfzJq2wd.js} +23 -8
- package/lib/fs.js +1 -1
- package/lib/index.js +7 -6
- package/lib/{instructions-MePoRigy.js → instructions-KsUwUFE0.js} +53 -37
- package/lib/instructions.js +1 -1
- package/lib/lsp.js +19 -16
- package/lib/native-win32-2waCRkPI.js +312 -0
- package/lib/process-fence.js +62 -12
- package/lib/protocol-4zdwV7u4.js +641 -0
- package/lib/{registry-KSsmU393.js → registry-YTUOjmVu.js} +368 -25
- package/lib/registry-operation-C2ZV3Jhw.js +116 -0
- package/lib/registry.js +3 -2
- package/lib/{review-context-AH-SXHmj.js → review-context-NDpKeMCc.js} +1 -1
- package/lib/review-context.js +1 -1
- package/lib/sandbox-C-vCJCp3.js +278 -0
- package/lib/sandbox.js +1 -1
- package/lib/scope.js +70 -10
- package/lib/skills.js +48 -29
- package/lib/types/authority/native-win32.d.ts +7 -0
- package/lib/types/authority/native.d.ts +23 -0
- package/lib/types/authority/protocol.d.ts +70 -0
- package/lib/types/client/open-in-app/OpenInAppAction.d.ts +5 -3
- package/lib/types/client/open-in-app/OpenTargetButton.d.ts +16 -10
- package/lib/types/client/open-in-app/adapter.d.ts +5 -1
- package/lib/types/client/open-in-app/controller.d.ts +5 -2
- package/lib/types/client/open-in-app/device.d.ts +11 -0
- package/lib/types/client/open-in-app/locales.d.ts +1 -1
- package/lib/types/client/open-in-app/open-path.d.ts +6 -5
- package/lib/types/client/open-in-app/open-toast.d.ts +39 -0
- package/lib/types/client/open-in-app/outcome.d.ts +23 -0
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/compat/authority-storage.d.ts +6 -0
- package/lib/types/compat/dsh-version.d.ts +4 -4
- package/lib/types/compat/open-in-app-device.d.ts +23 -0
- package/lib/types/compat/process-entrypoints.d.ts +2 -0
- package/lib/types/compat/process-profile.d.ts +3 -0
- package/lib/types/compat/sandbox-confine.d.ts +2 -0
- package/lib/types/fs.d.ts +4 -1
- package/lib/types/instructions.d.ts +0 -47
- package/lib/types/registry-operation.d.ts +31 -0
- package/lib/types/registry.d.ts +47 -6
- package/lib/types/scope.d.ts +12 -0
- package/lib/types/skills/catalog.d.ts +3 -0
- package/lib/types/skills/pool.d.ts +1 -1
- package/lib/workspace-files.js +63 -7
- package/package.json +87 -80
- package/lib/types/client/open-in-app/open-failure-toast.d.ts +0 -11
package/lib/skills.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { t as wrapMethod } from "./method-wrapper-CFY3p3j2.js";
|
|
2
|
+
import { c as withRegistryRead } from "./registry-operation-C2ZV3Jhw.js";
|
|
2
3
|
import { r as isPackageInstalled, t as createInstructionMessage } from "./llm-message-fYORacvh.js";
|
|
3
4
|
import { n as skillsMetadataValid } from "./metadata-DgqSdVX_.js";
|
|
4
5
|
import { constants, statSync } from "node:fs";
|
|
@@ -6,8 +7,8 @@ import { createHash } from "node:crypto";
|
|
|
6
7
|
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
8
|
import { Service } from "@deepseek-ai/cordis";
|
|
8
9
|
import { canonicalPath } from "@deepseek-ai/dsh-sandbox";
|
|
9
|
-
import { lstat, open, readdir, stat } from "node:fs/promises";
|
|
10
10
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
11
|
+
import { lstat, open, readdir, stat } from "node:fs/promises";
|
|
11
12
|
import z from "@deepseek-ai/schemastery";
|
|
12
13
|
//#region \0@oxc-project+runtime@0.149.0/helpers/esm/usingCtx.js
|
|
13
14
|
function _usingCtx() {
|
|
@@ -191,9 +192,10 @@ var SkillReaderPool = class {
|
|
|
191
192
|
return this.entries.size;
|
|
192
193
|
}
|
|
193
194
|
get roots() {
|
|
194
|
-
return [...this.entries.
|
|
195
|
+
return [...new Set([...this.entries.values()].map((entry) => entry.root))];
|
|
195
196
|
}
|
|
196
|
-
async use(
|
|
197
|
+
async use(root, create, run, signal, identity = root) {
|
|
198
|
+
const key = identity;
|
|
197
199
|
for (;;) {
|
|
198
200
|
signal?.throwIfAborted();
|
|
199
201
|
if (this.closing) throw new Error("multi-root skills: reader pool is closing");
|
|
@@ -225,6 +227,7 @@ var SkillReaderPool = class {
|
|
|
225
227
|
const controller = new AbortController();
|
|
226
228
|
entry = {
|
|
227
229
|
key,
|
|
230
|
+
root,
|
|
228
231
|
controller,
|
|
229
232
|
reader: create(controller.signal),
|
|
230
233
|
busy: 0
|
|
@@ -235,7 +238,7 @@ var SkillReaderPool = class {
|
|
|
235
238
|
this.entries.set(key, entry);
|
|
236
239
|
entry.busy++;
|
|
237
240
|
try {
|
|
238
|
-
if (entry.stamp === void 0) entry.stamp = await skillTreeStamp(
|
|
241
|
+
if (entry.stamp === void 0) entry.stamp = await skillTreeStamp(entry.root);
|
|
239
242
|
signal?.throwIfAborted();
|
|
240
243
|
return await run(await entry.reader);
|
|
241
244
|
} finally {
|
|
@@ -249,7 +252,7 @@ var SkillReaderPool = class {
|
|
|
249
252
|
this.polling ??= (async () => {
|
|
250
253
|
let changed = false;
|
|
251
254
|
for (const entry of [...this.entries.values()]) {
|
|
252
|
-
const stamp = await skillTreeStamp(entry.
|
|
255
|
+
const stamp = await skillTreeStamp(entry.root);
|
|
253
256
|
if (entry.stamp !== void 0 && entry.stamp !== stamp) changed = true;
|
|
254
257
|
entry.stamp = stamp;
|
|
255
258
|
}
|
|
@@ -285,10 +288,10 @@ var SkillReaderPool = class {
|
|
|
285
288
|
/** No symlink target outside the Root is read or enumerated. Content reads stay on demand. */
|
|
286
289
|
async function skillTreeStamp(root) {
|
|
287
290
|
const rows = [canonicalPath(root)];
|
|
288
|
-
const stamp = async (path) => {
|
|
291
|
+
const stamp = async (path, identityOnly = false) => {
|
|
289
292
|
try {
|
|
290
293
|
const link = await lstat(path, { bigint: true });
|
|
291
|
-
rows.push(`${path}:${link.dev}:${link.ino}:${link.size}:${link.mtimeNs}:${link.ctimeNs}:${canonicalPath(path)}`);
|
|
294
|
+
rows.push(identityOnly ? `${path}:${link.dev}:${link.ino}:${link.mode}:${canonicalPath(path)}` : `${path}:${link.dev}:${link.ino}:${link.size}:${link.mtimeNs}:${link.ctimeNs}:${canonicalPath(path)}`);
|
|
292
295
|
if (!containsPath(root, canonicalPath(path))) return void 0;
|
|
293
296
|
return await stat(path);
|
|
294
297
|
} catch {
|
|
@@ -296,7 +299,7 @@ async function skillTreeStamp(root) {
|
|
|
296
299
|
return;
|
|
297
300
|
}
|
|
298
301
|
};
|
|
299
|
-
await stamp(root);
|
|
302
|
+
await stamp(root, true);
|
|
300
303
|
for (const directory of [".dsh/skills", ".agents/skills"]) {
|
|
301
304
|
const path = join(root, directory);
|
|
302
305
|
if (!(await stamp(path))?.isDirectory()) continue;
|
|
@@ -336,7 +339,7 @@ var AdditionalSkillCatalog = class {
|
|
|
336
339
|
return this.generation();
|
|
337
340
|
}
|
|
338
341
|
current(primary, id) {
|
|
339
|
-
if (this.signal.aborted || !this.enabled() || this.ctx.multiRootRegistry.
|
|
342
|
+
if (this.signal.aborted || !this.enabled() || !this.ctx.multiRootRegistry.available || this.ctx.multiRootScope.snapshot({
|
|
340
343
|
mode: "read-only",
|
|
341
344
|
workspaceRoot: primary
|
|
342
345
|
}).authority !== "available") return void 0;
|
|
@@ -355,13 +358,17 @@ var AdditionalSkillCatalog = class {
|
|
|
355
358
|
if (root === void 0) return await run(void 0);
|
|
356
359
|
return await this.requests.run({
|
|
357
360
|
primary,
|
|
358
|
-
id
|
|
361
|
+
id,
|
|
362
|
+
epoch: this.ctx.multiRootRegistry.epoch
|
|
359
363
|
}, () => this.pool.use(root.path, (signal) => rootReader(this.ctx, root.path, AbortSignal.any([signal, this.signal]), () => {
|
|
360
364
|
const request = this.requests.getStore();
|
|
361
|
-
return request !== void 0 && this.current(request.primary, request.id)?.path === root.path;
|
|
362
|
-
}), run, options.signal));
|
|
365
|
+
return request !== void 0 && request.epoch === this.ctx.multiRootRegistry.epoch && this.current(request.primary, request.id)?.path === root.path;
|
|
366
|
+
}), run, options.signal, `${this.ctx.multiRootRegistry.epoch ?? "unavailable"}\0${root.path}`));
|
|
363
367
|
}
|
|
364
368
|
async list(primary, options) {
|
|
369
|
+
return withRegistryRead(this.ctx, primary, "skills", () => this.listAdmitted(primary, options));
|
|
370
|
+
}
|
|
371
|
+
async listAdmitted(primary, options) {
|
|
365
372
|
options.signal?.throwIfAborted();
|
|
366
373
|
if (!this.enabled()) return [];
|
|
367
374
|
const roots = await this.ctx.multiRootRegistry.refresh(primary);
|
|
@@ -381,6 +388,7 @@ var AdditionalSkillCatalog = class {
|
|
|
381
388
|
seen.add(candidate.name);
|
|
382
389
|
const locator = {
|
|
383
390
|
primary,
|
|
391
|
+
authorityEpoch: this.ctx.multiRootRegistry.epoch,
|
|
384
392
|
rootId: root.id,
|
|
385
393
|
aliasId: root.aliasId,
|
|
386
394
|
candidate
|
|
@@ -408,8 +416,11 @@ var AdditionalSkillCatalog = class {
|
|
|
408
416
|
}
|
|
409
417
|
/** Locators are created here and are not accepted from a client path or object. */
|
|
410
418
|
async load(primary, entry, options, user) {
|
|
419
|
+
return withRegistryRead(this.ctx, primary, "skills", () => this.loadAdmitted(primary, entry, options, user));
|
|
420
|
+
}
|
|
421
|
+
async loadAdmitted(primary, entry, options, user) {
|
|
411
422
|
const locator = entry.locator;
|
|
412
|
-
if (typeof locator !== "object" || locator === null || !this.locators.has(locator) || locator.primary !== primary) return void 0;
|
|
423
|
+
if (typeof locator !== "object" || locator === null || !this.locators.has(locator) || locator.primary !== primary || locator.authorityEpoch !== this.ctx.multiRootRegistry.epoch) return void 0;
|
|
413
424
|
const root = this.current(primary, locator.rootId);
|
|
414
425
|
if (root === void 0 || root.aliasId !== locator.aliasId || entry.name !== internalSkillName(primary, root.id, locator.candidate.name)) return void 0;
|
|
415
426
|
const revision = this.generation();
|
|
@@ -446,14 +457,16 @@ var AdditionalSkillCatalog = class {
|
|
|
446
457
|
}
|
|
447
458
|
async admissionValid(definition) {
|
|
448
459
|
const receipt = this.loadedReceipts.get(definition);
|
|
449
|
-
if (receipt === void 0) return false;
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
460
|
+
if (receipt === void 0 || receipt.locator.authorityEpoch !== this.ctx.multiRootRegistry.epoch) return false;
|
|
461
|
+
return withRegistryRead(this.ctx, receipt.locator.primary, "skills", async () => {
|
|
462
|
+
const current = this.current(receipt.locator.primary, receipt.locator.rootId);
|
|
463
|
+
if (current === void 0 || current.aliasId !== receipt.locator.aliasId || receipt.revision !== this.generation()) return false;
|
|
464
|
+
try {
|
|
465
|
+
return receipt.stamp === await fileStamp(definition.path) && receipt.revision === this.generation() && this.current(receipt.locator.primary, receipt.locator.rootId)?.aliasId === receipt.locator.aliasId;
|
|
466
|
+
} catch {
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
});
|
|
457
470
|
}
|
|
458
471
|
async dispose() {
|
|
459
472
|
await this.pool.dispose();
|
|
@@ -574,16 +587,22 @@ function installSkillRouting(ctx) {
|
|
|
574
587
|
fibers.push(ctx.inject(["skills"], (scope) => {
|
|
575
588
|
const registry = scope.skills;
|
|
576
589
|
scope.effect(() => wrapMethod(registry, "snapshot", (original) => async function(options = {}) {
|
|
577
|
-
const
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
if (
|
|
582
|
-
|
|
583
|
-
|
|
590
|
+
const primaryOrigin = skillOrigin(scope, options) ?? cold.getStore();
|
|
591
|
+
const run = async () => {
|
|
592
|
+
const result = await original.call(this, optionsFor(options));
|
|
593
|
+
const primary = skillOrigin(scope, options);
|
|
594
|
+
if (primary !== void 0) for (const skill of result.skills) {
|
|
595
|
+
if (skill.provider === "multi-root-workspace") continue;
|
|
596
|
+
if (scope.multiRootRegistry.list(primary).some((root) => skill.name.startsWith(internalSkillName(primary, root.id, "")))) scope.logger.warn(`multi-root skills: internal ID conflict; native winner retained: ${skill.name} (${skill.provider})`);
|
|
597
|
+
}
|
|
598
|
+
return result;
|
|
599
|
+
};
|
|
600
|
+
return primaryOrigin === void 0 ? run() : withRegistryRead(scope, primaryOrigin, "skills", run);
|
|
584
601
|
}));
|
|
585
602
|
scope.effect(() => wrapMethod(registry, "get", (original) => function(name, options = {}) {
|
|
586
|
-
|
|
603
|
+
const primary = skillOrigin(scope, options) ?? cold.getStore();
|
|
604
|
+
const run = () => original.call(this, name, optionsFor(options));
|
|
605
|
+
return primary === void 0 ? run() : withRegistryRead(scope, primary, "skills", run);
|
|
587
606
|
}));
|
|
588
607
|
}));
|
|
589
608
|
fibers.push(ctx.inject(["sessionSkillCatalog", "sessionQuery"], (scope) => {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Channel, Listener } from './native.ts';
|
|
2
|
+
export declare function connectWindows(endpoint: string): Promise<Channel>;
|
|
3
|
+
export declare function listenWindows(endpoint: string, accepted: (channel: Channel) => void): Promise<Listener>;
|
|
4
|
+
export declare function windowsHostAvailability(): Promise<{
|
|
5
|
+
available: boolean;
|
|
6
|
+
reason?: string;
|
|
7
|
+
}>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { EventEmitter } from 'node:events';
|
|
2
|
+
export interface Channel {
|
|
3
|
+
on: EventEmitter['on'];
|
|
4
|
+
readonly writableLength?: number;
|
|
5
|
+
write(data: string): boolean;
|
|
6
|
+
destroy(): unknown;
|
|
7
|
+
}
|
|
8
|
+
export interface Listener {
|
|
9
|
+
close(): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function storeIdentity(root: string): {
|
|
12
|
+
root: string;
|
|
13
|
+
id: string;
|
|
14
|
+
endpoint: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function connectNative(endpoint: string): Promise<Channel>;
|
|
17
|
+
/** Called only by the kernel lease holder. Never removes another epoch's socket. */
|
|
18
|
+
export declare function listenNative(endpoint: string, accepted: (channel: Channel) => void): Promise<Listener>;
|
|
19
|
+
/** Capability probe for native tests and diagnostics; not an authorization bypass. */
|
|
20
|
+
export declare function nativeHostAvailability(): Promise<{
|
|
21
|
+
available: boolean;
|
|
22
|
+
reason?: string;
|
|
23
|
+
}>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const methods: readonly ["hello", "snapshot", "admit", "add", "removeAt", "setAccess", "setAlias", "setSkillsEnabled", "setAliasId", "move", "recheck"];
|
|
3
|
+
export type Method = typeof methods[number];
|
|
4
|
+
export declare const snapshotSchema: z.ZodObject<{
|
|
5
|
+
protocol: z.ZodLiteral<1>;
|
|
6
|
+
store: z.ZodString;
|
|
7
|
+
epoch: z.ZodString;
|
|
8
|
+
revision: z.ZodNumber;
|
|
9
|
+
primary: z.ZodString;
|
|
10
|
+
roots: z.ZodArray<z.ZodObject<{
|
|
11
|
+
id: z.ZodString;
|
|
12
|
+
path: z.ZodString;
|
|
13
|
+
access: z.ZodEnum<{
|
|
14
|
+
inherit: "inherit";
|
|
15
|
+
"read-only": "read-only";
|
|
16
|
+
"read-write": "read-write";
|
|
17
|
+
}>;
|
|
18
|
+
addedAt: z.ZodString;
|
|
19
|
+
recordedPath: z.ZodOptional<z.ZodString>;
|
|
20
|
+
readOnlyRecordedPath: z.ZodOptional<z.ZodString>;
|
|
21
|
+
readWriteRecordedPath: z.ZodOptional<z.ZodString>;
|
|
22
|
+
alias: z.ZodOptional<z.ZodString>;
|
|
23
|
+
aliasId: z.ZodOptional<z.ZodUnknown>;
|
|
24
|
+
skillsEnabled: z.ZodOptional<z.ZodUnknown>;
|
|
25
|
+
state: z.ZodEnum<{
|
|
26
|
+
available: "available";
|
|
27
|
+
missing: "missing";
|
|
28
|
+
redirected: "redirected";
|
|
29
|
+
invalid: "invalid";
|
|
30
|
+
}>;
|
|
31
|
+
detail: z.ZodOptional<z.ZodString>;
|
|
32
|
+
invalidReason: z.ZodOptional<z.ZodLiteral<"membership-conflict">>;
|
|
33
|
+
}, z.core.$strict>>;
|
|
34
|
+
available: z.ZodBoolean;
|
|
35
|
+
}, z.core.$strict>;
|
|
36
|
+
export type Snapshot = z.infer<typeof snapshotSchema>;
|
|
37
|
+
export declare class AuthorityRpcError extends Error {
|
|
38
|
+
readonly code: string;
|
|
39
|
+
constructor(code: string, message: string);
|
|
40
|
+
}
|
|
41
|
+
export declare class AuthorityClient {
|
|
42
|
+
private readonly channel;
|
|
43
|
+
private readonly store;
|
|
44
|
+
private readonly limit;
|
|
45
|
+
private readonly pending;
|
|
46
|
+
private closed;
|
|
47
|
+
epoch: string | undefined;
|
|
48
|
+
private constructor();
|
|
49
|
+
static connect(endpoint: string, store: string, limit: number, onChange: (snapshot: Snapshot) => void, disconnected: () => void): Promise<AuthorityClient>;
|
|
50
|
+
request(method: Method, args: readonly unknown[]): Promise<unknown>;
|
|
51
|
+
close(): void;
|
|
52
|
+
}
|
|
53
|
+
export declare class AuthorityServer {
|
|
54
|
+
private readonly store;
|
|
55
|
+
private readonly epoch;
|
|
56
|
+
private readonly limit;
|
|
57
|
+
private readonly dispatch;
|
|
58
|
+
private listener;
|
|
59
|
+
private readonly peers;
|
|
60
|
+
private readonly readyPeers;
|
|
61
|
+
private closed;
|
|
62
|
+
private inFlight;
|
|
63
|
+
private constructor();
|
|
64
|
+
static listen(endpoint: string, store: string, epoch: string, limit: number, dispatch: (method: Method, args: unknown[]) => Promise<unknown>): Promise<AuthorityServer>;
|
|
65
|
+
private accept;
|
|
66
|
+
changed(snapshot: Snapshot): void;
|
|
67
|
+
close(): void;
|
|
68
|
+
}
|
|
69
|
+
export declare function parseArguments(method: Method, args: unknown[]): unknown[];
|
|
70
|
+
export {};
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
2
2
|
import type { InjectFace, PropsLocale } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
3
|
import type { OpenInAppLaunchState } from './controller.ts';
|
|
4
|
+
import type { OpenInAppOutcome } from './outcome.ts';
|
|
4
5
|
import type { ShortcutCatalogEntry } from '@deepseek-ai/dsh-client-shortcuts/client';
|
|
5
6
|
import { NS } from './locales.ts';
|
|
7
|
+
import { type OpenTargetReporter } from './OpenTargetButton.tsx';
|
|
6
8
|
/** Browser operations and state shared by the Session header and file tree contributions. */
|
|
7
|
-
export interface OpenInAppActionInjected {
|
|
9
|
+
export interface OpenInAppActionInjected extends OpenTargetReporter {
|
|
8
10
|
hooks: {
|
|
9
11
|
openInAppApps: ObservableSnapshot<readonly string[] | null>;
|
|
10
12
|
openInAppChoice: ObservableSnapshot<string>;
|
|
11
13
|
openInAppLaunch: ObservableSnapshot<OpenInAppLaunchState>;
|
|
12
14
|
shortcuts: ObservableSnapshot<readonly ShortcutCatalogEntry[]>;
|
|
13
15
|
};
|
|
14
|
-
launch: (appId: string, path: string) => Promise<
|
|
16
|
+
launch: (appId: string, path: string) => Promise<OpenInAppOutcome>;
|
|
15
17
|
choose: (appId: string) => void;
|
|
16
18
|
iconUrl: (appId: string) => string;
|
|
17
19
|
}
|
|
@@ -21,7 +23,7 @@ export type OpenInAppActionProps = PropsLocale<typeof NS> & InjectFace<OpenInApp
|
|
|
21
23
|
};
|
|
22
24
|
/**
|
|
23
25
|
* Adapt the installed directory catalog to the shared opening control.
|
|
24
|
-
* @param props - displayed directory, installed catalog, and
|
|
26
|
+
* @param props - displayed directory, installed catalog, launch operations, and the announcement seat.
|
|
25
27
|
* @returns the shared control, or null without an eligible application.
|
|
26
28
|
*/
|
|
27
29
|
export declare function OpenInAppAction(props: OpenInAppActionProps): React.JSX.Element | null;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { ShortcutCatalogEntry } from '@deepseek-ai/dsh-client-shortcuts/client';
|
|
3
3
|
import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
|
-
import type {
|
|
4
|
+
import type { OpenInAppOutcome } from './outcome.ts';
|
|
5
5
|
import type { NS } from './locales.ts';
|
|
6
6
|
/** Application metadata supplied by either the directory catalog or a file association query. */
|
|
7
7
|
export interface OpenTargetApplication {
|
|
@@ -18,8 +18,13 @@ export type OpenTargetOperation = {
|
|
|
18
18
|
} | {
|
|
19
19
|
readonly kind: 'reveal';
|
|
20
20
|
};
|
|
21
|
+
/** Seat a contribution sends one settled gesture to for app-wide announcement. */
|
|
22
|
+
export interface OpenTargetReporter {
|
|
23
|
+
/** Report the settled outcome; `{ kind: 'skipped' }` announces nothing. */
|
|
24
|
+
readonly report: (outcome: OpenInAppOutcome) => void;
|
|
25
|
+
}
|
|
21
26
|
/** Inputs shared by both target adapters and the file empty-state action. */
|
|
22
|
-
export interface OpenTargetButtonProps {
|
|
27
|
+
export interface OpenTargetButtonProps extends OpenTargetReporter {
|
|
23
28
|
readonly shortcut?: Pick<ShortcutCatalogEntry, 'keys' | 'aria'> | undefined;
|
|
24
29
|
readonly kind: 'file' | 'directory';
|
|
25
30
|
readonly applications: readonly OpenTargetApplication[];
|
|
@@ -29,24 +34,25 @@ export interface OpenTargetButtonProps {
|
|
|
29
34
|
readonly loading?: boolean;
|
|
30
35
|
readonly prominent?: boolean;
|
|
31
36
|
readonly t: TranslateNS<typeof NS>;
|
|
32
|
-
|
|
37
|
+
/** Resolve every gesture to an outcome, including Host and transport failures; never reject. */
|
|
38
|
+
readonly execute: (operation: OpenTargetOperation) => Promise<OpenInAppOutcome>;
|
|
33
39
|
readonly refresh?: () => void;
|
|
34
40
|
}
|
|
35
41
|
/**
|
|
36
|
-
* Serialize gestures and
|
|
37
|
-
*
|
|
38
|
-
* @param
|
|
39
|
-
* @
|
|
42
|
+
* Serialize gestures and report the outcome each one settles on to the app-wide
|
|
43
|
+
* announcer, which outlives this control and the panel holding it.
|
|
44
|
+
* @param execute - target adapter that settles on the gesture's outcome.
|
|
45
|
+
* @param report - announcement seat the settled outcome is sent to.
|
|
46
|
+
* @returns the pending state and the guarded action callback.
|
|
40
47
|
*/
|
|
41
|
-
export declare function useOpenTargetGesture(execute: OpenTargetButtonProps['execute'],
|
|
48
|
+
export declare function useOpenTargetGesture(execute: OpenTargetButtonProps['execute'], report: OpenTargetButtonProps['report']): {
|
|
42
49
|
pending: boolean;
|
|
43
|
-
toast: ReactNode;
|
|
44
50
|
act: (operation: OpenTargetOperation) => void;
|
|
45
51
|
};
|
|
46
52
|
/**
|
|
47
53
|
* Render identical split buttons for files and directories. File reveal always
|
|
48
54
|
* stays last; it is the default only when no application is registered.
|
|
49
55
|
* @param props - target applications, default selection, and operations.
|
|
50
|
-
* @returns the control
|
|
56
|
+
* @returns the control alone: its settled outcomes are announced by the seat it reports to.
|
|
51
57
|
*/
|
|
52
58
|
export declare function OpenTargetButton(props: OpenTargetButtonProps): ReactNode;
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import type { ObservableSnapshot } from '@deepseek-ai/dsh-client-store';
|
|
26
26
|
import type { ShortcutCatalogEntry } from '@deepseek-ai/dsh-client-shortcuts/client';
|
|
27
27
|
import { OpenInAppAction, type OpenInAppActionProps } from './OpenInAppAction.tsx';
|
|
28
|
+
import { type OpenInAppToastSource } from './open-toast.tsx';
|
|
28
29
|
import { NS } from './locales.ts';
|
|
29
30
|
/**
|
|
30
31
|
* The props `OpenInAppAction` consumes, minus the per-render `absolutePath`
|
|
@@ -42,5 +43,8 @@ export type OpenInAppAdapter = Omit<OpenInAppActionProps, 'absolutePath'>;
|
|
|
42
43
|
export declare function createOpenInAppAdapter(deps: {
|
|
43
44
|
readonly t: (key: string, params?: Record<string, unknown>) => string;
|
|
44
45
|
readonly shortcuts?: ObservableSnapshot<readonly ShortcutCatalogEntry[]> | undefined;
|
|
45
|
-
}):
|
|
46
|
+
}): {
|
|
47
|
+
readonly adapter: OpenInAppAdapter;
|
|
48
|
+
readonly toast: OpenInAppToastSource;
|
|
49
|
+
} | undefined;
|
|
46
50
|
export { OpenInAppAction, NS as OPEN_IN_APP_NS };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Browser availability/choice state and the launch carrier for the split button. */
|
|
2
2
|
import { type SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
3
|
+
import type { OpenInAppOutcome } from './outcome.ts';
|
|
3
4
|
/** Shared launch status for controls targeting the captured workspace path. */
|
|
4
5
|
export interface OpenInAppLaunchState {
|
|
5
6
|
readonly phase: 'idle' | 'busy' | 'error';
|
|
@@ -45,9 +46,11 @@ export declare class OpenInAppController {
|
|
|
45
46
|
* @param appId - catalog id from the availability list.
|
|
46
47
|
* @param path - the session's absolute workspace directory.
|
|
47
48
|
* Concurrent gestures are ignored until the current Host request settles.
|
|
48
|
-
* @returns
|
|
49
|
+
* @returns the acknowledged handoff, or `{ kind: 'skipped' }` while another
|
|
50
|
+
* launch holds the operation, or a named failure when the Host refuses or
|
|
51
|
+
* cannot be reached. Host and transport failures resolve without rejecting.
|
|
49
52
|
*/
|
|
50
|
-
launch(appId: string, path: string): Promise<
|
|
53
|
+
launch(appId: string, path: string): Promise<OpenInAppOutcome>;
|
|
51
54
|
private run;
|
|
52
55
|
}
|
|
53
56
|
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fork-facing shim for `isMobileDevice` from
|
|
3
|
+
* `@deepseek-ai/dsh-client-ui-primitives`.
|
|
4
|
+
*
|
|
5
|
+
* The verbatim `open-toast.tsx` spells this specifier where upstream writes
|
|
6
|
+
* the primitives import (joined only in `0.2.2-alpha.2`); the
|
|
7
|
+
* release-dependent probe and fallback live in
|
|
8
|
+
* `src/compat/open-in-app-device.ts` — version-dependent code belongs only
|
|
9
|
+
* under `src/compat/`. See FORK.md.
|
|
10
|
+
*/
|
|
11
|
+
export { isMobileDevice } from '../../compat/open-in-app-device.ts';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Dictionary namespace owned by upstream's plugin (verbatim). */
|
|
2
2
|
export declare const NS = "open-in-app";
|
|
3
3
|
/** Key domain of the `open-in-app` namespace (verbatim union of upstream's zh keys). */
|
|
4
|
-
export type OpenInAppKey = 'open.title' | 'path.appDefault' | 'path.appsError' | 'shortcut.busy' | 'shortcut.unavailable' | 'open.tooltip' | 'path.open' | 'path.more' | 'path.reveal' | 'path.openError' | 'path.revealError' | 'app.cursor' | 'app.vscode' | 'app.vscodeinsiders' | 'app.windsurf' | 'app.zed' | 'app.sublimetext' | 'app.xcode' | 'app.androidstudio' | 'app.intellij' | 'app.pycharm' | 'app.webstorm' | 'app.phpstorm' | 'app.goland' | 'app.rider' | 'app.rustrover' | 'app.fork' | 'app.sourcetree' | 'app.github' | 'app.tower' | 'app.gitkraken' | 'app.smartgit' | 'app.sublimemerge' | 'app.ghostty' | 'app.warp' | 'app.iterm' | 'app.kitty' | 'app.terminal' | 'app.windowsterminal' | 'app.gitbash' | 'app.gnometerminal' | 'app.konsole' | 'app.finder' | 'app.explorer' | 'app.filemanager';
|
|
4
|
+
export type OpenInAppKey = 'open.title' | 'path.appDefault' | 'path.appsError' | 'shortcut.busy' | 'shortcut.unavailable' | 'open.tooltip' | 'path.open' | 'path.more' | 'path.reveal' | 'path.opened' | 'path.revealed' | 'path.openError' | 'path.revealError' | 'app.cursor' | 'app.vscode' | 'app.vscodeinsiders' | 'app.windsurf' | 'app.zed' | 'app.sublimetext' | 'app.xcode' | 'app.androidstudio' | 'app.intellij' | 'app.pycharm' | 'app.webstorm' | 'app.phpstorm' | 'app.goland' | 'app.rider' | 'app.rustrover' | 'app.fork' | 'app.sourcetree' | 'app.github' | 'app.tower' | 'app.gitkraken' | 'app.smartgit' | 'app.sublimemerge' | 'app.ghostty' | 'app.warp' | 'app.iterm' | 'app.kitty' | 'app.terminal' | 'app.windowsterminal' | 'app.gitbash' | 'app.gnometerminal' | 'app.konsole' | 'app.finder' | 'app.explorer' | 'app.filemanager';
|
|
5
5
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
6
6
|
interface LocaleNamespaceMap {
|
|
7
7
|
/** Session-header "open workspace in application" copy (upstream declaration). */
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Fork-facing shim for upstream `ui-open-in-app/src/client/open-path.ts`.
|
|
3
3
|
*
|
|
4
|
-
* The fork only needs the failure union
|
|
4
|
+
* The fork only needs the failure union the path gestures' `execute`
|
|
5
5
|
* returns; upstream's file also carries `OpenInAppPathController`, the
|
|
6
6
|
* document-preview path controller that speaks to a Session Remote this
|
|
7
7
|
* plugin does not hold. Keeping the verbatim file would pull
|
|
8
8
|
* `@deepseek-ai/dsh-api-remotes` and `dsh-api-session-controller` into the
|
|
9
|
-
* bundle for a class nothing here calls
|
|
10
|
-
*
|
|
9
|
+
* bundle for a class nothing here calls. From `0.2.2-alpha.2` the failure
|
|
10
|
+
* union lives in upstream's `outcome.ts` (an alias of the deliverables
|
|
11
|
+
* `PresentedOpenFailure`), so this shim re-exports the fork's own outcome
|
|
12
|
+
* shim under the same name. See FORK.md.
|
|
11
13
|
*/
|
|
12
|
-
|
|
13
|
-
export type OpenInAppPathFailure = 'openError' | 'revealError';
|
|
14
|
+
export type { OpenInAppPathFailure } from './outcome.ts';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Native-open feedback that survives the initiating panel: mobile success and all failures. */
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import type { HostObservable, InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
4
|
+
import { NS } from './locales.ts';
|
|
5
|
+
import type { OpenInAppOutcome } from './outcome.ts';
|
|
6
|
+
/** One announced gesture; `seq` keys the banner so a repeat restarts it. */
|
|
7
|
+
export type OpenInAppToastState = Exclude<OpenInAppOutcome, {
|
|
8
|
+
kind: 'skipped';
|
|
9
|
+
}> & {
|
|
10
|
+
readonly seq: number;
|
|
11
|
+
};
|
|
12
|
+
/** Store every contribution reports into and the overlay entry reads. */
|
|
13
|
+
export interface OpenInAppToastSource {
|
|
14
|
+
/** Current notice for the overlay entry, or null while none shows. */
|
|
15
|
+
readonly hooks: {
|
|
16
|
+
readonly toast: HostObservable<OpenInAppToastState | null>;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Announce failures on every device and acknowledged opens or reveals on mobile.
|
|
20
|
+
* Desktop successes and skipped gestures leave the current notice unchanged.
|
|
21
|
+
* @param outcome - settled native-open result.
|
|
22
|
+
*/
|
|
23
|
+
readonly report: (outcome: OpenInAppOutcome) => void;
|
|
24
|
+
/** Clear the shown notice. */
|
|
25
|
+
readonly dismiss: () => void;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create the one outcome store the overlay entry shows.
|
|
29
|
+
* @returns the observable notice with its report and dismiss actions.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createOpenInAppToastSource(): OpenInAppToastSource;
|
|
32
|
+
/** Props of the `shell.overlay` entry: the notice store, its dismissal, and localized copy. */
|
|
33
|
+
export type OpenInAppToastProps = PropsRuntime<'shell.overlay'> & PropsLocale<typeof NS> & InjectFace<Omit<OpenInAppToastSource, 'report'>>;
|
|
34
|
+
/**
|
|
35
|
+
* Render a mobile handoff confirmation or a native-open failure.
|
|
36
|
+
* @param props - the notice hook, its dismissal, and the locale seat.
|
|
37
|
+
* @returns the banner on display, or null.
|
|
38
|
+
*/
|
|
39
|
+
export declare function OpenInAppToast({ useToast, dismiss, t }: OpenInAppToastProps): ReactNode;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fork-facing shim for upstream `ui-open-in-app/src/client/outcome.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Upstream's file aliases `PresentedOpenFailure`/`PresentedOpenOutcome` out
|
|
5
|
+
* of `@deepseek-ai/dsh-client-ui-deliverables` — a package this plugin does
|
|
6
|
+
* not depend on. Both aliases are types only, so this shim spells the same
|
|
7
|
+
* structural unions verbatim (upstream
|
|
8
|
+
* `ui-deliverables/src/client/present-open.ts`) instead of importing them.
|
|
9
|
+
* See FORK.md.
|
|
10
|
+
*/
|
|
11
|
+
/** Failure kind of one settled gesture, keyed to the `path.*` copy it announces. */
|
|
12
|
+
export type OpenInAppPathFailure = 'openError' | 'revealError';
|
|
13
|
+
/** Acknowledged open or reveal, named failure, or deliberately skipped gesture. */
|
|
14
|
+
export type OpenInAppOutcome = {
|
|
15
|
+
readonly kind: 'opened';
|
|
16
|
+
} | {
|
|
17
|
+
readonly kind: 'revealed';
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: 'failed';
|
|
20
|
+
readonly failure: OpenInAppPathFailure;
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: 'skipped';
|
|
23
|
+
};
|