dsh-reference-anything 0.4.4 → 0.5.0

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 (119) hide show
  1. package/NOTICE.md +58 -0
  2. package/README.md +146 -65
  3. package/cordis.patch.yml +17 -15
  4. package/images/agents/LICENSE +22 -0
  5. package/images/agents/README.md +3 -0
  6. package/images/agents/antigravity.svg +1 -0
  7. package/images/agents/chatgpt.svg +1 -0
  8. package/images/agents/claude.svg +1 -0
  9. package/images/agents/cline.svg +1 -0
  10. package/images/agents/codex.svg +1 -0
  11. package/images/agents/continue.svg +1 -0
  12. package/images/agents/crush.svg +1 -0
  13. package/images/agents/cursor.svg +1 -0
  14. package/images/agents/dsh.svg +1 -0
  15. package/images/agents/gemini.svg +1 -0
  16. package/images/agents/goose.svg +1 -0
  17. package/images/agents/grokbuild.svg +1 -0
  18. package/images/agents/hermes.svg +1 -0
  19. package/images/agents/kilocode.svg +1 -0
  20. package/images/agents/kimi.svg +1 -0
  21. package/images/agents/mimocode.svg +1 -0
  22. package/images/agents/openclaw.svg +1 -0
  23. package/images/agents/opencode.svg +1 -0
  24. package/images/agents/pi.svg +1 -0
  25. package/images/agents/qoder.svg +1 -0
  26. package/images/agents/qwen.svg +1 -0
  27. package/images/agents/reasonix.svg +6 -0
  28. package/images/agents/teleagent.svg +1 -0
  29. package/images/agents/trae.svg +1 -0
  30. package/images/agents/vibe.svg +1 -0
  31. package/images/agents/workbuddy.svg +37 -0
  32. package/images/agents/zcode.svg +1 -0
  33. package/images/agents/zed.svg +1 -0
  34. package/lib/agent-scan-cache.d.ts +13 -0
  35. package/lib/agent-scan-cache.js +43 -0
  36. package/lib/chat-import-compatibility.d.ts +18 -0
  37. package/lib/chat-import-compatibility.js +87 -0
  38. package/lib/chat-import-discovery.d.ts +32 -0
  39. package/lib/chat-import-discovery.js +17 -0
  40. package/lib/chat-import-reader.d.ts +27 -0
  41. package/lib/chat-import-reader.js +111 -0
  42. package/lib/chat-import-runtime.d.ts +4 -0
  43. package/lib/chat-import-runtime.js +4 -0
  44. package/lib/client/index.d.ts +5 -0
  45. package/lib/client.js +4 -34024
  46. package/lib/contract.d.ts +50 -3
  47. package/lib/contract.js +14 -4
  48. package/lib/conversation-availability.d.ts +13 -0
  49. package/lib/conversation-availability.js +76 -0
  50. package/lib/errors.d.ts +2 -2
  51. package/lib/host.d.ts +55 -11
  52. package/lib/host.js +197 -14
  53. package/lib/index.d.ts +3 -1
  54. package/lib/index.js +31 -3
  55. package/lib/local-conversation-reader.d.ts +3 -0
  56. package/lib/local-conversation-reader.js +81 -0
  57. package/lib/local-conversation-scanner.d.ts +18 -0
  58. package/lib/local-conversation-scanner.js +55 -0
  59. package/lib/local-locator.d.ts +14 -0
  60. package/lib/local-locator.js +34 -0
  61. package/lib/local-reference-tools.d.ts +4 -0
  62. package/lib/local-reference-tools.js +195 -0
  63. package/lib/local-reference.d.ts +118 -0
  64. package/lib/local-reference.js +289 -0
  65. package/lib/opencli.d.ts +5 -2
  66. package/lib/opencli.js +88 -64
  67. package/lib/reference.d.ts +5 -3
  68. package/lib/reference.js +167 -72
  69. package/lib/render.d.ts +1 -1
  70. package/lib/render.js +4 -4
  71. package/lib/retain.d.ts +14 -0
  72. package/lib/retain.js +1 -1
  73. package/lib/serialize.d.ts +1 -1
  74. package/lib/serialize.js +2 -2
  75. package/lib/sources/local-agent/index.d.ts +23 -16
  76. package/lib/sources/local-agent/index.js +101 -15
  77. package/lib/store/spec.d.ts +40 -7
  78. package/lib/sync/index.js +4 -0
  79. package/lib/tool.d.ts +2 -0
  80. package/lib/tool.js +36 -41
  81. package/lib/typert.js +2 -0
  82. package/lib/types.d.ts +2 -0
  83. package/lib/update.d.ts +2 -0
  84. package/lib/update.js +12 -0
  85. package/lib/uri-codec.d.ts +1 -0
  86. package/lib/uri-codec.js +64 -3
  87. package/lib/uri.d.ts +1 -10
  88. package/lib/uri.js +16 -5
  89. package/lib/wire.d.ts +59 -0
  90. package/lib/wire.js +13 -4
  91. package/opencli-plugin/common.js +5 -3
  92. package/opencli-plugin/opencli-plugin.json +1 -1
  93. package/opencli-plugin/package.json +1 -1
  94. package/package.json +28 -4
  95. package/release-notes/0.4.4/commands.png +0 -0
  96. package/release-notes/0.4.4/demos/agent-import.gif +0 -0
  97. package/release-notes/0.4.4/demos/chat-import-upgrade.gif +0 -0
  98. package/release-notes/0.4.4/demos/folders.gif +0 -0
  99. package/release-notes/0.4.4/demos/local-plugins.gif +0 -0
  100. package/release-notes/0.4.4/demos/shortcuts.gif +0 -0
  101. package/release-notes/0.4.4/features.json +36 -0
  102. package/release-notes/0.4.4/files.png +0 -0
  103. package/release-notes/0.5.0/demos/agent-import.gif +0 -0
  104. package/release-notes/0.5.0/demos/chat-import-upgrade.gif +0 -0
  105. package/release-notes/0.5.0/demos/folders.gif +0 -0
  106. package/release-notes/0.5.0/demos/local-plugins.gif +0 -0
  107. package/release-notes/0.5.0/demos/local-references.gif +0 -0
  108. package/release-notes/0.5.0/demos/shortcuts.gif +0 -0
  109. package/release-notes/0.5.0/features.json +65 -0
  110. package/release-notes/README.md +36 -0
  111. package/vendor/chat-import-reader/LICENSE +22 -0
  112. package/vendor/chat-import-reader/LICENSE.fzstd +21 -0
  113. package/vendor/chat-import-reader/README.md +48 -0
  114. package/vendor/chat-import-reader/availability-worker.mjs +114 -0
  115. package/vendor/chat-import-reader/parsers.d.mts +6 -0
  116. package/vendor/chat-import-reader/parsers.mjs +11168 -0
  117. package/vendor/chat-import-reader/scan-worker.mjs +7 -0
  118. package/vendor/chat-import-reader/scanners.mjs +4286 -0
  119. package/vendor/chat-import-reader/upstream-sha256.json +101 -0
package/lib/contract.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { InvocationDescriptor } from '@deepseek-ai/dsh-typert-protocol';
3
3
  /**
4
4
  * One local-agent transcript as the `@` menu sees it.
5
5
  *
6
- * `id` is the source-scoped `kind:relPath`, not a `dsh-ref:` URI — the client
6
+ * `id` is a source-scoped `kind:relPath` or an opaque import-only selection, not a `dsh-ref:` URI — the client
7
7
  * wraps it, the way it does for web chats, so the `'local-agent'` source string
8
8
  * stays hardcoded in exactly one place. Deliberately carries no `origin`: the
9
9
  * absolute path adds nothing a menu row can use and everything a screen-share
@@ -15,9 +15,19 @@ export declare const agentCandidateSchema: z.ZodReadonly<z.ZodObject<{
15
15
  label: z.ZodString;
16
16
  provider: z.ZodString;
17
17
  updatedAt: z.ZodOptional<z.ZodNumber>;
18
+ referenceId: z.ZodOptional<z.ZodString>;
19
+ importOnly: z.ZodOptional<z.ZodBoolean>;
20
+ importStatus: z.ZodOptional<z.ZodEnum<{
21
+ imported: "imported";
22
+ partial: "partial";
23
+ "not-imported": "not-imported";
24
+ archived: "archived";
25
+ }>>;
26
+ warning: z.ZodOptional<z.ZodString>;
18
27
  }, z.core.$strip>>;
19
28
  export declare const agentStatsSchema: z.ZodReadonly<z.ZodObject<{
20
29
  agent: z.ZodEnum<{
30
+ chatgpt: "chatgpt";
21
31
  kimi: "kimi";
22
32
  "claude-code": "claude-code";
23
33
  codex: "codex";
@@ -32,8 +42,26 @@ export declare const agentStatsSchema: z.ZodReadonly<z.ZodObject<{
32
42
  opencode: "opencode";
33
43
  mimocode: "mimocode";
34
44
  zcode: "zcode";
45
+ antigravity: "antigravity";
46
+ kilocode: "kilocode";
47
+ workbuddy: "workbuddy";
48
+ qwen: "qwen";
49
+ continue: "continue";
50
+ cline: "cline";
51
+ goose: "goose";
52
+ dsh4: "dsh4";
53
+ zed: "zed";
54
+ crush: "crush";
55
+ teleagent: "teleagent";
56
+ trae: "trae";
57
+ vibe: "vibe";
58
+ dsh: "dsh";
35
59
  }>;
36
60
  conversations: z.ZodNumber;
61
+ error: z.ZodOptional<z.ZodString>;
62
+ unavailable: z.ZodOptional<z.ZodBoolean>;
63
+ paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
64
+ readable: z.ZodOptional<z.ZodBoolean>;
37
65
  }, z.core.$strip>>;
38
66
  export declare const driveCandidateSchema: z.ZodReadonly<z.ZodObject<{
39
67
  id: z.ZodString;
@@ -71,8 +99,8 @@ export declare const searchInputSchema: z.ZodReadonly<z.ZodObject<{
71
99
  limit: z.ZodNumber;
72
100
  }, z.core.$strip>>;
73
101
  export declare const matchedViaSchema: z.ZodEnum<{
74
- recent: "recent";
75
102
  title: "title";
103
+ recent: "recent";
76
104
  content: "content";
77
105
  }>;
78
106
  export declare const searchResultSchema: z.ZodReadonly<z.ZodObject<{
@@ -92,8 +120,8 @@ export declare const searchResultSchema: z.ZodReadonly<z.ZodObject<{
92
120
  partial: z.ZodBoolean;
93
121
  syncedAt: z.ZodString;
94
122
  matchedVia: z.ZodEnum<{
95
- recent: "recent";
96
123
  title: "title";
124
+ recent: "recent";
97
125
  content: "content";
98
126
  }>;
99
127
  snippet: z.ZodOptional<z.ZodString>;
@@ -120,6 +148,9 @@ export declare const healthSchema: z.ZodReadonly<z.ZodObject<{
120
148
  }>;
121
149
  extensionVersion: z.ZodOptional<z.ZodString>;
122
150
  profileCount: z.ZodOptional<z.ZodNumber>;
151
+ latestExtensionVersion: z.ZodOptional<z.ZodString>;
152
+ extensionUpdateAvailable: z.ZodOptional<z.ZodBoolean>;
153
+ extensionCompatible: z.ZodOptional<z.ZodBoolean>;
123
154
  opencliCompatible: z.ZodBoolean;
124
155
  daemonVersion: z.ZodOptional<z.ZodString>;
125
156
  daemonStale: z.ZodBoolean;
@@ -155,6 +186,8 @@ export declare const packageUpdateStatusSchema: z.ZodReadonly<z.ZodObject<{
155
186
  releaseNotes: z.ZodOptional<z.ZodString>;
156
187
  releaseUrl: z.ZodOptional<z.ZodString>;
157
188
  error: z.ZodOptional<z.ZodString>;
189
+ currentReleaseNotes: z.ZodOptional<z.ZodString>;
190
+ currentReleaseUrl: z.ZodOptional<z.ZodString>;
158
191
  }, z.core.$strip>>;
159
192
  export declare const packageUpdateResultSchema: z.ZodReadonly<z.ZodObject<{
160
193
  version: z.ZodString;
@@ -465,4 +498,18 @@ export declare const openListDisableMountSchema: z.ZodReadonly<z.ZodObject<{
465
498
  export declare const openListUpgradeInputSchema: z.ZodReadonly<z.ZodObject<{
466
499
  rollback: z.ZodOptional<z.ZodBoolean>;
467
500
  }, z.core.$strict>>;
501
+ export declare const chatImportCompatibilitySchema: z.ZodReadonly<z.ZodObject<{
502
+ state: z.ZodEnum<{
503
+ unavailable: "unavailable";
504
+ disabled: "disabled";
505
+ ready: "ready";
506
+ incompatible: "incompatible";
507
+ installing: "installing";
508
+ "restart-required": "restart-required";
509
+ }>;
510
+ version: z.ZodString;
511
+ missing: z.ZodArray<z.ZodString>;
512
+ canUpgrade: z.ZodBoolean;
513
+ error: z.ZodOptional<z.ZodString>;
514
+ }, z.core.$strict>>;
468
515
  export declare const REFERENCE_ANYTHING_INVOCATIONS: readonly InvocationDescriptor[];
package/lib/contract.js CHANGED
@@ -3,14 +3,14 @@ import { localAgentSchema, providerSchema, settingsRecordSchema } from "./wire.j
3
3
  /**
4
4
  * One local-agent transcript as the `@` menu sees it.
5
5
  *
6
- * `id` is the source-scoped `kind:relPath`, not a `dsh-ref:` URI — the client
6
+ * `id` is a source-scoped `kind:relPath` or an opaque import-only selection, not a `dsh-ref:` URI — the client
7
7
  * wraps it, the way it does for web chats, so the `'local-agent'` source string
8
8
  * stays hardcoded in exactly one place. Deliberately carries no `origin`: the
9
9
  * absolute path adds nothing a menu row can use and everything a screen-share
10
10
  * leaks.
11
11
  */
12
- export const agentCandidateSchema = z.object({ id: z.string(), kind: z.string(), label: z.string(), provider: z.string(), updatedAt: z.number().optional() }).readonly();
13
- export const agentStatsSchema = z.object({ agent: localAgentSchema, conversations: z.number().int().nonnegative() }).readonly();
12
+ export const agentCandidateSchema = z.object({ id: z.string(), kind: z.string(), label: z.string(), provider: z.string(), updatedAt: z.number().optional(), referenceId: z.string().optional(), importOnly: z.boolean().optional(), importStatus: z.enum(['imported', 'partial', 'not-imported', 'archived']).optional(), warning: z.string().optional() }).readonly();
13
+ export const agentStatsSchema = z.object({ agent: localAgentSchema, conversations: z.number().int().nonnegative(), error: z.string().optional(), unavailable: z.boolean().optional(), paths: z.array(z.string()).optional(), readable: z.boolean().optional() }).readonly();
14
14
  // `origin` is the drive's own display path, shown under the candidate. It is
15
15
  // never a download URL: those are signed with the account's credential and
16
16
  // must not cross this wire.
@@ -49,6 +49,7 @@ export const healthSchema = z.object({
49
49
  version: z.string(), daemon: z.string(), pluginInstalled: z.boolean(),
50
50
  daemonRunning: z.boolean(), extensionConnected: z.boolean(), extensionState: extensionStateSchema,
51
51
  extensionVersion: z.string().optional(), profileCount: z.number().int().nonnegative().optional(),
52
+ latestExtensionVersion: z.string().optional(), extensionUpdateAvailable: z.boolean().optional(), extensionCompatible: z.boolean().optional(),
52
53
  opencliCompatible: z.boolean(), daemonVersion: z.string().optional(), daemonStale: z.boolean(),
53
54
  connectivityOk: z.boolean(), connectivityChecked: z.boolean(), pluginVersion: z.string().optional(), adapterCommandsReady: z.boolean(), adapterCompatible: z.boolean(), latestVersion: z.string().optional(), updateAvailable: z.boolean(),
54
55
  versionError: z.string().optional(), daemonError: z.string().optional(), pluginError: z.string().optional(), doctorError: z.string().optional(),
@@ -60,6 +61,7 @@ export const openCliDiscoverySchema = z.object({
60
61
  export const packageUpdateStatusSchema = z.object({
61
62
  currentVersion: z.string(), latestVersion: z.string(), updateAvailable: z.boolean(),
62
63
  checkedAt: z.number().int().nonnegative(), releaseNotes: z.string().max(12_000).optional(), releaseUrl: z.string().url().optional(), error: z.string().optional(),
64
+ currentReleaseNotes: z.string().max(12_000).optional(), currentReleaseUrl: z.string().url().optional(),
63
65
  }).readonly();
64
66
  export const packageUpdateResultSchema = z.object({
65
67
  version: z.string(), restartRequired: z.boolean(),
@@ -125,8 +127,16 @@ export const openListMountCreateSchema = z.object({ id: z.string().min(1).option
125
127
  export const openListMountInputSchema = z.object({ id: z.string().min(1) }).strict().readonly();
126
128
  export const openListDisableMountSchema = z.object({ id: z.string().min(1), disabled: z.boolean().optional() }).strict().readonly();
127
129
  export const openListUpgradeInputSchema = z.object({ rollback: z.boolean().optional() }).strict().readonly();
130
+ export const chatImportCompatibilitySchema = z.object({
131
+ state: z.enum(['ready', 'incompatible', 'unavailable', 'disabled', 'installing', 'restart-required']),
132
+ version: z.string(), missing: z.array(z.string()), canUpgrade: z.boolean(), error: z.string().optional(),
133
+ }).strict().readonly();
128
134
  export const REFERENCE_ANYTHING_INVOCATIONS = [
129
- descriptor('agentSearch', [agentLookup, { name: 'input', wire: 'input', source: 'json', codec: strict('AgentSearchInput', z.object({ query: z.string(), limit: z.number().int().min(1).max(100) }).readonly()) }], strict('AgentCandidate[]', z.array(agentCandidateSchema)), true),
135
+ descriptor('chatImportStatus', [], strict('ChatImportCompatibility', chatImportCompatibilitySchema)),
136
+ descriptor('chatImportUpgradeInstall', [], strict('ChatImportCompatibility', chatImportCompatibilitySchema), true),
137
+ descriptor('agentResume', [agentLookup, { name: 'input', wire: 'input', source: 'json', codec: strict('AgentResumeInput', z.object({ id: z.string().min(1) }).strict().readonly()) }], strict('ResumeSummary', z.string().min(1)), true),
138
+ descriptor('agentImport', [agentLookup, { name: 'input', wire: 'input', source: 'json', codec: strict('AgentImportInput', z.object({ id: z.string().min(1) }).strict().readonly()) }], strict('ImportedSessionId', z.string().min(1)), true),
139
+ descriptor('agentSearch', [agentLookup, { name: 'input', wire: 'input', source: 'json', codec: strict('AgentSearchInput', z.object({ query: z.string(), limit: z.number().int().min(1).max(100), kind: localAgentSchema.optional() }).readonly()) }], strict('AgentCandidate[]', z.array(agentCandidateSchema)), true),
130
140
  descriptor('agentStats', [], strict('AgentStats[]', z.array(agentStatsSchema)), true),
131
141
  // No session lookup, unlike `agentSearch`: a drive is the same drive from
132
142
  // every workspace, so there is nothing to scope the query to.
@@ -0,0 +1,13 @@
1
+ import type { ChatImportTarget } from './chat-import-reader.ts';
2
+ /** Unknown/error rows stay visible. One background parser never delays discovery. */
3
+ export declare class ConversationAvailability {
4
+ private cache;
5
+ private queued;
6
+ private worker?;
7
+ private active?;
8
+ private timer?;
9
+ visible(target: ChatImportTarget): Promise<boolean>;
10
+ private next;
11
+ private failed;
12
+ dispose(): void;
13
+ }
@@ -0,0 +1,76 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { spawn } from 'node:child_process';
3
+ import { fileURLToPath } from 'node:url';
4
+ /** Unknown/error rows stay visible. One background parser never delays discovery. */
5
+ export class ConversationAvailability {
6
+ cache = new Map();
7
+ queued = new Map();
8
+ worker;
9
+ active;
10
+ timer;
11
+ async visible(target) {
12
+ const key = JSON.stringify(target);
13
+ const entry = this.cache.get(key);
14
+ if (entry) {
15
+ const unchanged = (await Promise.all(entry.dependencies.map(async ([path, version]) => {
16
+ try {
17
+ const s = await stat(path);
18
+ return version === `${s.size}:${s.mtimeMs}:${s.ctimeMs}`;
19
+ }
20
+ catch (error) {
21
+ return error.code === 'ENOENT' && version === 'missing';
22
+ }
23
+ }))).every(Boolean);
24
+ if (unchanged && (entry.state !== 'unknown' || Date.now() - entry.checked < 300_000))
25
+ return entry.state !== 'empty';
26
+ this.cache.delete(key);
27
+ }
28
+ if (this.active !== key && this.queued.size < 128)
29
+ this.queued.set(key, target);
30
+ this.next();
31
+ return true;
32
+ }
33
+ next() {
34
+ if (this.active || !this.queued.size)
35
+ return;
36
+ const [id, target] = this.queued.entries().next().value;
37
+ this.queued.delete(id);
38
+ this.active = id;
39
+ if (!this.worker) {
40
+ // Native parser failures and V8 OOM must not terminate the DSH host.
41
+ this.worker = spawn(process.execPath, ['--max-old-space-size=256', fileURLToPath(new URL('../vendor/chat-import-reader/availability-worker.mjs', import.meta.url))], {
42
+ windowsHide: true, stdio: ['ignore', 'ignore', 'ignore', 'ipc'],
43
+ });
44
+ this.worker.on('message', (result) => {
45
+ if (result.id !== this.active)
46
+ return;
47
+ clearTimeout(this.timer);
48
+ if (this.cache.size >= 2000)
49
+ this.cache.delete(this.cache.keys().next().value);
50
+ this.cache.set(result.id, { ...result, checked: Date.now() });
51
+ this.active = undefined;
52
+ this.next();
53
+ });
54
+ this.worker.on('error', () => this.failed());
55
+ this.worker.on('exit', () => this.failed());
56
+ this.worker.unref();
57
+ this.worker.channel?.unref();
58
+ }
59
+ this.timer = setTimeout(() => this.failed(), 15_000);
60
+ this.timer.unref();
61
+ this.worker.send({ id, target });
62
+ }
63
+ failed() {
64
+ if (this.active)
65
+ this.cache.set(this.active, { state: 'unknown', dependencies: [], checked: Date.now() });
66
+ this.dispose();
67
+ this.next();
68
+ }
69
+ dispose() {
70
+ clearTimeout(this.timer);
71
+ this.worker?.removeAllListeners();
72
+ this.worker?.kill();
73
+ this.worker = undefined;
74
+ this.active = undefined;
75
+ }
76
+ }
package/lib/errors.d.ts CHANGED
@@ -5,9 +5,9 @@
5
5
  */
6
6
  import { HarnessError } from '@deepseek-ai/dsh-llm';
7
7
  /** Stable, machine-routable failure classes; route on these, never on message text. */
8
- export type ReferenceErrorCode =
8
+ export type ReferenceErrorCode = 'INVALID_ARGUMENT' | 'SOURCE_DISABLED' | 'ACCESS_DENIED' | 'NOT_FOUND' | 'REVISION_REQUIRED' | 'REVISION_CHANGED' | 'UNSUPPORTED_PART' | 'IMAGE_UNAVAILABLE' | 'IMAGE_TOO_LARGE' | 'BUDGET_TOO_SMALL' | 'READ_FAILED' | 'TIMEOUT' | 'CANCELLED'
9
9
  /** Two sources claimed the same registry id. */
10
- 'SOURCE_DUPLICATE'
10
+ | 'SOURCE_DUPLICATE'
11
11
  /** A reference named a source that is not registered. */
12
12
  | 'SOURCE_UNKNOWN'
13
13
  /** The owning source is registered but reported itself unusable. */
package/lib/host.d.ts CHANGED
@@ -3,8 +3,50 @@ import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
3
3
  import type { ChatProvider, SettingsRecord } from './store/spec.ts';
4
4
  import type { SyncMode } from './sync/index.ts';
5
5
  import type { Agent } from '@deepseek-ai/dsh-agent';
6
+ import { type LocalAgent } from './wire.ts';
7
+ import type { AgentCandidate } from './client/remote.ts';
8
+ declare module '@deepseek-ai/cordis' {
9
+ interface Context {
10
+ chatImport: {
11
+ readConversation?(input: {
12
+ format: string;
13
+ path: string;
14
+ sessionId?: string;
15
+ limit?: number;
16
+ before?: number;
17
+ revision?: string;
18
+ detail?: 'summary' | 'full';
19
+ }, signal?: AbortSignal): Promise<unknown>;
20
+ discoveryRoots?(): Record<string, string | string[] | null>;
21
+ scanDiscover(input: {
22
+ format?: string;
23
+ path?: string;
24
+ query?: string;
25
+ }, signal?: AbortSignal): Promise<unknown>;
26
+ importPath(input: {
27
+ format: string;
28
+ path: string;
29
+ sessionIds?: string[];
30
+ }, signal?: AbortSignal): Promise<unknown>;
31
+ resumeSummary(input: {
32
+ format: 'claude' | 'codex';
33
+ path: string;
34
+ }, signal?: AbortSignal): Promise<unknown>;
35
+ };
36
+ }
37
+ }
6
38
  export declare class ReferenceAnythingRemote extends TypertRemoteService {
39
+ chatImportStatus(): Promise<import("./chat-import-compatibility.ts").ChatImportCompatibility>;
40
+ chatImportUpgradeInstall(signal: AbortSignal): Promise<import("./chat-import-compatibility.ts").ChatImportCompatibility>;
41
+ private readonly agentScans;
42
+ private readonly scannedTargets;
7
43
  constructor(ctx: Context);
44
+ agentResume(_agent: Agent, input: {
45
+ id: string;
46
+ }, signal: AbortSignal): Promise<string>;
47
+ agentImport(_agent: Agent, input: {
48
+ id: string;
49
+ }, signal: AbortSignal): Promise<string>;
8
50
  /**
9
51
  * Discovery for the `@` menu's local-agent group.
10
52
  *
@@ -17,18 +59,17 @@ export declare class ReferenceAnythingRemote extends TypertRemoteService {
17
59
  agentSearch(agent: Agent, input: {
18
60
  query: string;
19
61
  limit: number;
20
- }, signal: AbortSignal): Promise<{
21
- searchIncomplete?: boolean | undefined;
22
- updatedAt?: number | undefined;
23
- id: string;
24
- kind: string;
25
- label: string;
26
- provider: string;
27
- }[]>;
62
+ kind?: LocalAgent;
63
+ }, signal: AbortSignal): Promise<AgentCandidate[]>;
28
64
  agentStats(signal: AbortSignal): Promise<{
29
- agent: import("./sources/local-agent/types.ts").AgentKind;
65
+ readable: boolean;
66
+ paths?: string[];
67
+ agent: LocalAgent;
30
68
  conversations: number;
69
+ error?: string;
70
+ unavailable?: boolean;
31
71
  }[]>;
72
+ private assertScannedSelection;
32
73
  /**
33
74
  * Discovery for the `@` menu's cloud-drive group.
34
75
  *
@@ -86,10 +127,10 @@ export declare class ReferenceAnythingRemote extends TypertRemoteService {
86
127
  historyMode: "metadata-only" | "offline-mirror";
87
128
  syncHistoryDays: number | null;
88
129
  enabledProviders: ("chatgpt" | "claude" | "gemini" | "deepseek" | "grok" | "kimi")[];
89
- enabledAgents: ("kimi" | "claude-code" | "codex" | "cursor" | "qoder" | "reasonix" | "openclaw" | "grokbuild" | "hermes" | "gemini-cli" | "pi" | "opencode" | "mimocode" | "zcode")[];
130
+ enabledAgents: ("chatgpt" | "kimi" | "claude-code" | "codex" | "cursor" | "qoder" | "reasonix" | "openclaw" | "grokbuild" | "hermes" | "gemini-cli" | "pi" | "opencode" | "mimocode" | "zcode" | "antigravity" | "kilocode" | "workbuddy" | "qwen" | "continue" | "cline" | "goose" | "dsh4" | "zed" | "crush" | "teleagent" | "trae" | "vibe" | "dsh")[];
90
131
  maxReadTurns: number;
91
132
  inputRenderMode: "pill" | "raw-text";
92
- agentDirectories?: Partial<Record<"kimi" | "claude-code" | "codex" | "cursor" | "qoder" | "reasonix" | "openclaw" | "grokbuild" | "hermes" | "gemini-cli" | "pi" | "opencode" | "mimocode" | "zcode", string>> | undefined;
133
+ agentDirectories?: Partial<Record<"chatgpt" | "kimi" | "claude-code" | "codex" | "cursor" | "qoder" | "reasonix" | "openclaw" | "grokbuild" | "hermes" | "gemini-cli" | "pi" | "opencode" | "mimocode" | "zcode" | "antigravity" | "kilocode" | "workbuddy" | "qwen" | "continue" | "cline" | "goose" | "dsh4" | "zed" | "crush" | "teleagent" | "trae" | "vibe" | "dsh", string>> | undefined;
93
134
  enabledDriveMounts?: string[] | undefined;
94
135
  picker?: {
95
136
  displayMode: "collapse" | "native-scroll";
@@ -140,6 +181,9 @@ export declare class ReferenceAnythingRemote extends TypertRemoteService {
140
181
  maxCandidates: number;
141
182
  sort: "name" | "updated";
142
183
  shortcut: string;
184
+ showImport: boolean;
185
+ showSummary: boolean;
186
+ skipConfirmation: boolean;
143
187
  aliases?: string[] | undefined;
144
188
  sourceAliases?: Record<string, string[]> | undefined;
145
189
  };
package/lib/host.js CHANGED
@@ -1,10 +1,75 @@
1
+ import { AgentScanCache } from "./agent-scan-cache.js";
2
+ import { localConversationRoots } from "./local-conversation-reader.js";
1
3
  import { TypertRemoteService } from '@deepseek-ai/dsh-typert-protocol';
2
4
  import { tmpdir } from 'node:os';
3
5
  import { spawn } from 'node:child_process';
4
6
  import { validateDownloadDirectory } from "./download-directory.js";
5
7
  import { resolveLocalPlugins } from "./local-plugins.js";
8
+ import { z } from 'zod';
9
+ import { randomUUID } from 'node:crypto';
10
+ import { discoverySchema, scanFormat, agentKind } from "./chat-import-discovery.js";
11
+ import { ALL_LOCAL_AGENTS, LOCAL_AGENT_LABEL } from "./wire.js";
12
+ import { expandRoot } from "./sources/local-agent/registry.js";
13
+ import { homedir } from 'node:os';
14
+ import { inWorkspace } from "./sources/local-agent/path.js";
15
+ import { chatImportCompatibility, upgradeChatImport } from "./chat-import-compatibility.js";
16
+ const importedItemSchema = z.object({
17
+ status: z.string(), sessionId: z.string().min(1).optional(),
18
+ error: z.string().optional(), reason: z.string().optional(), skipReason: z.string().optional(),
19
+ });
20
+ const chatImportResultSchema = z.discriminatedUnion('mode', [
21
+ importedItemSchema.extend({ mode: z.literal('single') }),
22
+ z.object({ mode: z.literal('batch'), results: z.array(importedItemSchema) }),
23
+ ]);
6
24
  export class ReferenceAnythingRemote extends TypertRemoteService {
25
+ async chatImportStatus() { return chatImportCompatibility(this.ctx); }
26
+ async chatImportUpgradeInstall(signal) {
27
+ await upgradeChatImport(this.ctx, signal);
28
+ return this.chatImportStatus();
29
+ }
30
+ agentScans = new AgentScanCache();
31
+ scannedTargets = new Map();
7
32
  constructor(ctx) { super(ctx, 'referenceAnything'); }
33
+ async agentResume(_agent, input, signal) {
34
+ signal.throwIfAborted();
35
+ this.assertScannedSelection(_agent, input.id);
36
+ const importer = this.ctx.get('chatImport');
37
+ if (typeof importer?.resumeSummary !== 'function')
38
+ throw new Error('Chat Import API unavailable. Open Reference Anything settings → Local Agent conversations to check compatibility and upgrade, then restart DSH.');
39
+ const agents = this.ctx.get('referenceLocalAgents');
40
+ if (!agents)
41
+ throw new Error('Local agent conversations are unavailable.');
42
+ const { format, path } = await agents.importTarget(input.id);
43
+ if (format !== 'claude' && format !== 'codex')
44
+ throw new Error('Chat Import Resume supports Claude Code and Codex only.');
45
+ signal.throwIfAborted();
46
+ const result = await importer.resumeSummary({ format, path }, signal);
47
+ return z.object({ summary: z.string().min(1) }).parse(result).summary;
48
+ }
49
+ async agentImport(_agent, input, signal) {
50
+ signal.throwIfAborted();
51
+ const importer = this.ctx.get('chatImport');
52
+ if (typeof importer?.importPath !== 'function')
53
+ throw new Error('Chat Import API unavailable. Open Reference Anything settings → Local Agent conversations to check compatibility and upgrade, then restart DSH.');
54
+ const agents = this.ctx.get('referenceLocalAgents');
55
+ if (!agents)
56
+ throw new Error('Local agent conversations are unavailable.');
57
+ const scanned = input.id.startsWith('scan:') || input.id.includes(':chat-import:')
58
+ ? this.scannedTargets.get(`${_agent.session.header.id}:${input.id}`) : undefined;
59
+ this.assertScannedSelection(_agent, input.id);
60
+ const target = scanned ? { format: scanned.format, path: scanned.path, sessionIds: scanned.sessionIds } : await agents.importTarget(input.id);
61
+ signal.throwIfAborted();
62
+ const result = await importer.importPath(target, signal);
63
+ const parsed = chatImportResultSchema.parse(result);
64
+ const items = parsed.mode === 'single' ? [parsed] : parsed.results;
65
+ if (items.length !== 1)
66
+ throw new Error('Expected exactly one imported conversation; no conversation was opened.');
67
+ const item = items[0];
68
+ if (!['imported', 'replaced', 'already-imported', 'appended'].includes(item.status) || !item.sessionId) {
69
+ throw new Error(`Conversation was not imported (${item.status}): ${item.error ?? item.reason ?? item.skipReason ?? ''}`);
70
+ }
71
+ return item.sessionId;
72
+ }
8
73
  /**
9
74
  * Discovery for the `@` menu's local-agent group.
10
75
  *
@@ -18,26 +83,144 @@ export class ReferenceAnythingRemote extends TypertRemoteService {
18
83
  const agents = this.ctx.get('referenceLocalAgents');
19
84
  if (agents === undefined)
20
85
  return [];
21
- // A session with no cwd of its own is rare and has no workspace to scope
22
- // to; falling back to the process's directory is the same guess the
23
- // `ReferenceSource.list` seam makes, rather than an empty group.
24
- const rows = await agents.listForWorkspace(input.query, input.limit, agent.session.header.cwd ?? process.cwd(), signal);
25
- return rows.map(row => ({
26
- id: row.ref.id,
27
- kind: row.ref.id.slice(0, row.ref.id.indexOf(':')),
28
- label: row.label,
29
- provider: row.provider ?? '',
30
- ...row.updatedAt === undefined ? {} : { updatedAt: row.updatedAt },
31
- ...row.searchIncomplete === true ? { searchIncomplete: true } : {},
32
- }));
86
+ const importer = this.ctx.get('chatImport');
87
+ {
88
+ const settings = this.ctx.referenceChatHistory.getSettings();
89
+ agents.setAgentDirectories(settings.agentDirectories ?? {});
90
+ const policy = agents.discoveryPolicy();
91
+ const requests = policy.roots.map(root => ({ format: scanFormat(root.kind), path: root.path }));
92
+ {
93
+ for (const kind of ALL_LOCAL_AGENTS) {
94
+ if (!(settings.enabledAgents ?? ALL_LOCAL_AGENTS).includes(kind))
95
+ continue;
96
+ const format = scanFormat(kind);
97
+ requests.push({ format });
98
+ const custom = expandRoot(settings.agentDirectories?.[kind] ?? '', homedir());
99
+ if (custom)
100
+ requests.push({ format, path: custom });
101
+ }
102
+ }
103
+ const candidates = [];
104
+ const discovered = [];
105
+ const warnings = [];
106
+ const seen = new Set();
107
+ const owner = agent.session.header.id;
108
+ // Scope before scanning and deduplicate explicit roots repeated by settings.
109
+ const scannedRequests = new Set();
110
+ for (const request of requests) {
111
+ const kind = agentKind(request.format);
112
+ if ((input.kind && kind !== input.kind) || !(settings.enabledAgents ?? ALL_LOCAL_AGENTS).includes(kind))
113
+ continue;
114
+ const requestKey = JSON.stringify([request.format, request.path ?? null]);
115
+ if (scannedRequests.has(requestKey))
116
+ continue;
117
+ scannedRequests.add(requestKey);
118
+ signal.throwIfAborted();
119
+ try {
120
+ const result = await agents.scanner.scanDiscover(request, signal);
121
+ for (const warning of result.warnings)
122
+ warnings.push({ id: `warning:${warnings.length}`, kind: 'scan-warning', label: warning.format, provider: '', warning: `${warning.target}: ${warning.error}` });
123
+ for (const row of result.sessions) {
124
+ const query = input.query.trim().toLowerCase();
125
+ if (query && ![row.title, row.project, row.sourcePath].some(value => value?.toLowerCase().includes(query)))
126
+ continue;
127
+ const key = JSON.stringify([row.format, row.sourcePath, row.sessionId]);
128
+ if ((input.kind && agentKind(row.format) !== input.kind) || seen.has(key) || (policy.scope === 'workspace' && !inWorkspace(row.cwd ?? '', agent.session.header.cwd ?? process.cwd())))
129
+ continue;
130
+ seen.add(key);
131
+ discovered.push({ ...row });
132
+ }
133
+ }
134
+ catch (error) {
135
+ signal.throwIfAborted();
136
+ warnings.push({ id: `warning:${warnings.length}`, kind: 'scan-warning', label: request.format, provider: '', warning: error instanceof Error ? error.message : String(error) });
137
+ }
138
+ }
139
+ if (importer?.scanDiscover) {
140
+ const statuses = new Map();
141
+ await Promise.all([...scannedRequests].map(async (key) => {
142
+ const [format, path] = JSON.parse(key);
143
+ try {
144
+ const result = await this.agentScans.read(importer, '', key, () => importer.scanDiscover({ format, ...(path ? { path } : {}) }, AbortSignal.timeout(2_000)), AbortSignal.any([signal, AbortSignal.timeout(2_000)]));
145
+ for (const row of result.sessions)
146
+ statuses.set(JSON.stringify([row.format, row.sourcePath, row.sessionId]), row.importStatus);
147
+ }
148
+ catch {
149
+ signal.throwIfAborted();
150
+ }
151
+ }));
152
+ for (const row of discovered)
153
+ row.importStatus = statuses.get(JSON.stringify([row.format, row.sourcePath, row.sessionId]));
154
+ }
155
+ discovered.sort((a, b) => (b.lastActiveAt ?? 0) - (a.lastActiveAt ?? 0));
156
+ for (const row of discovered) {
157
+ signal.throwIfAborted();
158
+ if (candidates.length >= input.limit)
159
+ break;
160
+ if (!await agents.conversationAvailability.visible({ format: row.format, path: row.sourcePath, sessionId: row.sessionId }))
161
+ continue;
162
+ const kind = agentKind(row.format);
163
+ const referenceId = await agents.referenceForDiscovered(kind, row.sourcePath, row.sessionId);
164
+ if (!referenceId && settings.picker?.agents.showImport === false)
165
+ continue;
166
+ const id = referenceId ?? `scan:${randomUUID()}`;
167
+ if (!referenceId || referenceId.includes(':chat-import:')) {
168
+ this.scannedTargets.set(`${owner}:${id}`, { owner, policy: JSON.stringify(settings), format: row.format, path: row.sourcePath, sessionIds: [row.sessionId] });
169
+ if (this.scannedTargets.size > 2000)
170
+ this.scannedTargets.delete(this.scannedTargets.keys().next().value);
171
+ }
172
+ candidates.push({ id, kind, label: row.title || row.sessionId, provider: LOCAL_AGENT_LABEL[kind] ?? row.format,
173
+ ...(referenceId ? { referenceId } : { importOnly: true }), importStatus: row.importStatus,
174
+ ...(row.lastActiveAt == null ? {} : { updatedAt: row.lastActiveAt }) });
175
+ }
176
+ return [...warnings, ...candidates];
177
+ }
33
178
  }
34
179
  async agentStats(signal) {
35
180
  signal.throwIfAborted();
36
181
  const agents = this.ctx.get('referenceLocalAgents');
37
182
  if (agents === undefined)
38
183
  return [];
39
- agents.setAgentDirectories(this.ctx.referenceChatHistory.getSettings().agentDirectories ?? {});
40
- return (await agents.stats(signal)).map(row => ({ agent: row.kind, conversations: row.conversations }));
184
+ const settings = this.ctx.referenceChatHistory.getSettings();
185
+ agents.setAgentDirectories(settings.agentDirectories ?? {});
186
+ const rows = [];
187
+ for (const kind of ALL_LOCAL_AGENTS) {
188
+ const format = scanFormat(kind);
189
+ signal.throwIfAborted();
190
+ const sessions = new Set();
191
+ const errors = [];
192
+ const custom = expandRoot(settings.agentDirectories?.[kind] ?? '', homedir());
193
+ for (const path of custom ? [undefined, custom] : [undefined]) {
194
+ try {
195
+ const result = discoverySchema.parse(await agents.scanner.scanDiscover({ format, ...(path ? { path } : {}) }, signal));
196
+ for (const row of result.sessions) {
197
+ if (await agents.conversationAvailability.visible({ format: row.format, path: row.sourcePath, sessionId: row.sessionId }))
198
+ sessions.add(JSON.stringify([row.sourcePath, row.sessionId]));
199
+ }
200
+ errors.push(...result.warnings.map(warning => `${warning.target}: ${warning.error}`));
201
+ }
202
+ catch (error) {
203
+ signal.throwIfAborted();
204
+ errors.push(error instanceof Error ? error.message : String(error));
205
+ }
206
+ }
207
+ rows.push({ agent: kind, conversations: sessions.size, ...(errors.length ? { error: errors.join('\n') } : {}) });
208
+ }
209
+ const roots = localConversationRoots();
210
+ return rows.map(row => {
211
+ const root = roots?.[scanFormat(row.agent)];
212
+ const custom = settings.agentDirectories?.[row.agent];
213
+ const paths = [...new Set([...(Array.isArray(root) ? root : root ? [root] : []), ...(custom ? [custom] : [])])];
214
+ return { ...row, ...(roots ? { paths } : {}), readable: true };
215
+ });
216
+ }
217
+ assertScannedSelection(agent, id) {
218
+ if (!id.startsWith('scan:') && !id.includes(':chat-import:'))
219
+ return;
220
+ const selected = this.scannedTargets.get(`${agent.session.header.id}:${id}`);
221
+ if (!selected || selected.owner !== agent.session.header.id || selected.policy !== JSON.stringify(this.ctx.referenceChatHistory.getSettings())) {
222
+ throw new Error('Conversation selection expired. Search and select it again.');
223
+ }
41
224
  }
42
225
  /**
43
226
  * Discovery for the `@` menu's cloud-drive group.
package/lib/index.d.ts CHANGED
@@ -76,6 +76,8 @@ export default class ReferenceRuntime extends Service {
76
76
  assertGranted(sessionId: string | undefined, ref: ReferenceRef): void;
77
77
  /** Allow a gated URI granted live or named in this task's durable user/tool-result history. */
78
78
  assertSessionGranted(session: Session | undefined, ref: ReferenceRef): void;
79
+ /** Resolve short handles only against references already named in this task. */
80
+ resolveReadReference(session: Session | undefined, uri: string): ReferenceRef;
79
81
  /** Release task-local grants when a host knows the task is gone. */
80
82
  revoke(sessionId: string): void;
81
83
  /**
@@ -90,7 +92,7 @@ export default class ReferenceRuntime extends Service {
90
92
  * @param signal - cancellation from the caller.
91
93
  * @returns matching items, capped at `limit`.
92
94
  */
93
- list(query?: string, limit?: number, signal?: AbortSignal): Promise<ReferenceSummary[]>;
95
+ list(query?: string, limit?: number, signal?: AbortSignal, externalOnly?: boolean): Promise<ReferenceSummary[]>;
94
96
  /**
95
97
  * Read one window of turns from a reference.
96
98
  * @param ref - the reference to resolve; its `source` selects the owning implementation.