dsh-reference-anything 0.2.5 → 0.3.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.
- package/NOTICE.md +5 -34
- package/README.md +68 -43
- package/cordis.patch.yml +18 -20
- package/lib/client.js +980 -1443
- package/lib/contract.d.ts +14 -13
- package/lib/contract.js +6 -8
- package/lib/host.d.ts +12 -11
- package/lib/host.js +15 -7
- package/lib/opencli.d.ts +5 -1
- package/lib/opencli.js +26 -43
- package/lib/profile-mode.d.ts +30 -0
- package/lib/profile-mode.js +90 -0
- package/lib/reference.d.ts +3 -11
- package/lib/reference.js +14 -55
- package/lib/sources/web-chat/index.d.ts +1 -0
- package/lib/sources/web-chat/index.js +5 -0
- package/lib/store/spec.d.ts +13 -0
- package/lib/sync/index.js +4 -4
- package/lib/typert.js +2 -2
- package/lib/web.js +1 -1
- package/lib/wire.d.ts +29 -0
- package/lib/wire.js +16 -7
- package/opencli-plugin/common.js +65 -21
- package/opencli-plugin/opencli-plugin.json +1 -1
- package/opencli-plugin/package.json +1 -1
- package/package.json +21 -16
- package/lib/workspace.d.ts +0 -18
- package/lib/workspace.js +0 -81
package/lib/reference.js
CHANGED
|
@@ -8,21 +8,17 @@
|
|
|
8
8
|
* the loop durably appends, so injected material is logged by construction and
|
|
9
9
|
* a replay reconstructs the request exactly.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
* is resolved through `ctx.references`.
|
|
13
|
-
*
|
|
14
|
-
* session-surface semantics — compaction folding, shadowed events, cited event
|
|
15
|
-
* seqs — that do not generalize to an outside conversation.
|
|
11
|
+
* This plugin owns only `dsh-ref:`, which names material outside the harness
|
|
12
|
+
* and is resolved through `ctx.references`. Native file and DSH-session
|
|
13
|
+
* references are owned end-to-end by their official DSH services.
|
|
16
14
|
*
|
|
17
15
|
* @module dsh-reference-anything/reference
|
|
18
16
|
*/
|
|
19
17
|
import z from '@deepseek-ai/schemastery';
|
|
20
18
|
import { boundContextSummary, createUserMessage, freezeMessage } from '@deepseek-ai/dsh-llm';
|
|
21
|
-
import { SESSION_REFERENCE_SCHEME, parseSessionReferenceText, } from '@deepseek-ai/dsh-session-reference';
|
|
22
19
|
import { ReferenceAnythingError } from "./errors.js";
|
|
23
20
|
import { renderDeferredReferences } from "./render.js";
|
|
24
21
|
import { mayContainReference, parseReferenceText } from "./uri.js";
|
|
25
|
-
import { WORKSPACE_REFERENCE_SCHEME, parseWorkspaceReferenceText, renderWorkspaceReferences } from "./workspace.js";
|
|
26
22
|
/** Cordis plugin name used by loader diagnostics. */
|
|
27
23
|
export const name = 'reference-mention';
|
|
28
24
|
/** The registry this expander reads through. */
|
|
@@ -32,7 +28,6 @@ export const MAX_REFERENCES = 3;
|
|
|
32
28
|
/** Runtime schema for {@link Config}. */
|
|
33
29
|
export const Config = z.object({
|
|
34
30
|
maxReferences: z.number().step(1).min(1).max(MAX_REFERENCES).default(MAX_REFERENCES),
|
|
35
|
-
serveSessionScheme: z.boolean().default(true),
|
|
36
31
|
});
|
|
37
32
|
/**
|
|
38
33
|
* Register the pre-step mention expander.
|
|
@@ -41,7 +36,6 @@ export const Config = z.object({
|
|
|
41
36
|
*/
|
|
42
37
|
export function apply(ctx, config = {}) {
|
|
43
38
|
const maxReferences = Math.min(config.maxReferences ?? MAX_REFERENCES, MAX_REFERENCES);
|
|
44
|
-
const serveSessionScheme = config.serveSessionScheme ?? true;
|
|
45
39
|
ctx.on('agent/pre-step', async ({ agent, signal }, next) => {
|
|
46
40
|
const decision = await next();
|
|
47
41
|
if (decision.kind === 'reject' || signal.aborted)
|
|
@@ -49,7 +43,7 @@ export function apply(ctx, config = {}) {
|
|
|
49
43
|
// The overwhelmingly common case is a prompt with no mentions at all, and
|
|
50
44
|
// this runs on every step of every turn. Screening with substring scans
|
|
51
45
|
// before any parsing keeps that case free.
|
|
52
|
-
const target = decision.messages.findIndex(candidate => candidate.source.kind === 'user' && messageText(candidate).some(
|
|
46
|
+
const target = decision.messages.findIndex(candidate => candidate.source.kind === 'user' && messageText(candidate).some(mayContainReference));
|
|
53
47
|
if (target < 0)
|
|
54
48
|
return decision;
|
|
55
49
|
const original = decision.messages[target];
|
|
@@ -57,7 +51,7 @@ export function apply(ctx, config = {}) {
|
|
|
57
51
|
return decision;
|
|
58
52
|
let parsed;
|
|
59
53
|
try {
|
|
60
|
-
parsed = parseMessage(original
|
|
54
|
+
parsed = parseMessage(original);
|
|
61
55
|
}
|
|
62
56
|
catch (error) {
|
|
63
57
|
// A malformed URI inside an explicit mention is the user asking for
|
|
@@ -65,11 +59,11 @@ export function apply(ctx, config = {}) {
|
|
|
65
59
|
// reference silently resolves to nothing.
|
|
66
60
|
return entered(decision, target, [notice(describe(error))], original);
|
|
67
61
|
}
|
|
68
|
-
if (parsed.references.length === 0
|
|
62
|
+
if (parsed.references.length === 0)
|
|
69
63
|
return decision;
|
|
70
64
|
let contexts;
|
|
71
65
|
try {
|
|
72
|
-
contexts = await resolve(ctx, agent, parsed,
|
|
66
|
+
contexts = await resolve(ctx, agent, parsed, maxReferences, signal);
|
|
73
67
|
}
|
|
74
68
|
catch (error) {
|
|
75
69
|
if (signal.aborted)
|
|
@@ -85,28 +79,13 @@ export function apply(ctx, config = {}) {
|
|
|
85
79
|
* Authorize and render the named references without reading their bodies.
|
|
86
80
|
* The agent decides whether the current request warrants `reference_read`.
|
|
87
81
|
*/
|
|
88
|
-
async function resolve(ctx, agent, parsed,
|
|
82
|
+
async function resolve(ctx, agent, parsed, maxReferences, signal) {
|
|
89
83
|
const contexts = [];
|
|
90
|
-
if (parsed.workspaceReferences.length > 0) {
|
|
91
|
-
contexts.push(createUserMessage({
|
|
92
|
-
content: [{ type: 'text', text: await renderWorkspaceReferences(agent, parsed.workspaceReferences) }],
|
|
93
|
-
source: { kind: 'plugin', plugin: name, form: 'recall' },
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
if (parsed.sessionReferences.length > 0) {
|
|
97
|
-
const resolver = ctx.get('sessionReferenceResolver');
|
|
98
|
-
if (resolver === undefined) {
|
|
99
|
-
throw new ReferenceAnythingError('this deployment cannot reference other harness sessions: the cross-session resolver is not mounted', 'SOURCE_UNAVAILABLE');
|
|
100
|
-
}
|
|
101
|
-
const prepared = await resolver.prepare(agent, contentOf(parsed.message), [...parsed.sessionReferences], signal);
|
|
102
|
-
if (prepared.additionalContext !== undefined)
|
|
103
|
-
contexts.push(prepared.additionalContext);
|
|
104
|
-
}
|
|
105
84
|
if (parsed.references.length > 0) {
|
|
106
85
|
const inputs = dedupe(parsed.references);
|
|
107
|
-
if (inputs.length >
|
|
108
|
-
throw new ReferenceAnythingError(`a message may reference at most ${
|
|
109
|
-
+ `${
|
|
86
|
+
if (inputs.length > maxReferences) {
|
|
87
|
+
throw new ReferenceAnythingError(`a message may reference at most ${maxReferences} outside conversation`
|
|
88
|
+
+ `${maxReferences === 1 ? '' : 's'}; this one named ${inputs.length}`, 'REFERENCE_TOO_MANY');
|
|
110
89
|
}
|
|
111
90
|
for (const input of inputs)
|
|
112
91
|
ctx.references.grant(String(agent.session.id), input.ref);
|
|
@@ -134,28 +113,16 @@ function entered(decision, target, contexts, message) {
|
|
|
134
113
|
const replaced = decision.messages.with(target, message);
|
|
135
114
|
return { kind: 'enter', messages: replaced.toSpliced(target, 0, ...contexts) };
|
|
136
115
|
}
|
|
137
|
-
/** Rewrite
|
|
138
|
-
function parseMessage(message
|
|
116
|
+
/** Rewrite external-conversation mentions to readable text, preserving message identity. */
|
|
117
|
+
function parseMessage(message) {
|
|
139
118
|
const references = [];
|
|
140
|
-
const sessionReferences = [];
|
|
141
|
-
const workspaceReferences = [];
|
|
142
119
|
let changed = false;
|
|
143
120
|
const content = message.content.map((block) => {
|
|
144
121
|
if (block.type !== 'text')
|
|
145
122
|
return block;
|
|
146
123
|
const own = parseReferenceText(block.text);
|
|
147
124
|
references.push(...own.references);
|
|
148
|
-
|
|
149
|
-
if (text.includes(WORKSPACE_REFERENCE_SCHEME)) {
|
|
150
|
-
const workspace = parseWorkspaceReferenceText(text);
|
|
151
|
-
workspaceReferences.push(...workspace.references);
|
|
152
|
-
text = workspace.text;
|
|
153
|
-
}
|
|
154
|
-
if (serveSessionScheme && text.includes(SESSION_REFERENCE_SCHEME)) {
|
|
155
|
-
const session = parseSessionReferenceText(text);
|
|
156
|
-
sessionReferences.push(...session.references);
|
|
157
|
-
text = session.text;
|
|
158
|
-
}
|
|
125
|
+
const text = own.text;
|
|
159
126
|
if (text === block.text)
|
|
160
127
|
return block;
|
|
161
128
|
changed = true;
|
|
@@ -166,8 +133,6 @@ function parseMessage(message, serveSessionScheme) {
|
|
|
166
133
|
return {
|
|
167
134
|
message: changed ? freezeMessage({ ...message, content }) : message,
|
|
168
135
|
references,
|
|
169
|
-
sessionReferences,
|
|
170
|
-
workspaceReferences,
|
|
171
136
|
};
|
|
172
137
|
}
|
|
173
138
|
/** First-appearance order, one entry per distinct reference. */
|
|
@@ -183,15 +148,9 @@ function dedupe(references) {
|
|
|
183
148
|
}
|
|
184
149
|
return unique;
|
|
185
150
|
}
|
|
186
|
-
function mentions(text, serveSessionScheme) {
|
|
187
|
-
return mayContainReference(text) || text.includes(WORKSPACE_REFERENCE_SCHEME) || (serveSessionScheme && text.includes(SESSION_REFERENCE_SCHEME));
|
|
188
|
-
}
|
|
189
151
|
function messageText(message) {
|
|
190
152
|
return message.content.flatMap(block => block.type === 'text' ? [block.text] : []);
|
|
191
153
|
}
|
|
192
|
-
function contentOf(message) {
|
|
193
|
-
return message.content.map(block => ({ ...block }));
|
|
194
|
-
}
|
|
195
154
|
function notice(summary) {
|
|
196
155
|
return createUserMessage({
|
|
197
156
|
content: [{
|
|
@@ -94,6 +94,7 @@ export default class WebChatHistoryService extends Service implements ReferenceS
|
|
|
94
94
|
installOpenCli(signal?: AbortSignal): Promise<OpenCliDiscovery>;
|
|
95
95
|
updateSettings(value: unknown): Promise<SettingsRecord>;
|
|
96
96
|
health(signal?: AbortSignal): Promise<import("../../opencli.ts").OpenCliHealth>;
|
|
97
|
+
quickHealth(signal?: AbortSignal): Promise<import("../../opencli.ts").OpenCliHealth>;
|
|
97
98
|
profiles(signal?: AbortSignal): Promise<import("../../opencli.ts").BrowserProfile[]>;
|
|
98
99
|
private runner;
|
|
99
100
|
installAdapter(signal?: AbortSignal): Promise<boolean>;
|
|
@@ -229,6 +229,11 @@ export default class WebChatHistoryService extends Service {
|
|
|
229
229
|
return new OpenCliRunner({ executable: settings.opencliPath, profile: settings.profile,
|
|
230
230
|
timeoutMs: this.config.timeoutMs, maxStdoutBytes: this.config.maxStdoutBytes }).health(signal);
|
|
231
231
|
}
|
|
232
|
+
quickHealth(signal) {
|
|
233
|
+
const settings = this.store.settings;
|
|
234
|
+
return new OpenCliRunner({ executable: settings.opencliPath, profile: settings.profile,
|
|
235
|
+
timeoutMs: this.config.timeoutMs, maxStdoutBytes: this.config.maxStdoutBytes }).quickHealth(signal);
|
|
236
|
+
}
|
|
232
237
|
profiles(signal) {
|
|
233
238
|
const settings = this.store.settings;
|
|
234
239
|
return new OpenCliRunner({ executable: settings.opencliPath, profile: '',
|
package/lib/store/spec.d.ts
CHANGED
|
@@ -192,31 +192,44 @@ export declare const referenceAnythingDomainSpec: {
|
|
|
192
192
|
pill: "pill";
|
|
193
193
|
"raw-text": "raw-text";
|
|
194
194
|
}>>;
|
|
195
|
+
referenceUiMode: z.ZodOptional<z.ZodEnum<{
|
|
196
|
+
plugin: "plugin";
|
|
197
|
+
official: "official";
|
|
198
|
+
}>>;
|
|
195
199
|
picker: z.ZodOptional<z.ZodObject<{
|
|
200
|
+
displayMode: z.ZodDefault<z.ZodEnum<{
|
|
201
|
+
collapse: "collapse";
|
|
202
|
+
"native-scroll": "native-scroll";
|
|
203
|
+
}>>;
|
|
196
204
|
commands: z.ZodObject<{
|
|
197
205
|
enabled: z.ZodBoolean;
|
|
198
206
|
order: z.ZodNumber;
|
|
199
207
|
limit: z.ZodNumber;
|
|
208
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
200
209
|
}, z.core.$strip>;
|
|
201
210
|
skills: z.ZodObject<{
|
|
202
211
|
enabled: z.ZodBoolean;
|
|
203
212
|
order: z.ZodNumber;
|
|
204
213
|
limit: z.ZodNumber;
|
|
214
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
205
215
|
}, z.core.$strip>;
|
|
206
216
|
files: z.ZodObject<{
|
|
207
217
|
enabled: z.ZodBoolean;
|
|
208
218
|
order: z.ZodNumber;
|
|
209
219
|
limit: z.ZodNumber;
|
|
220
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
210
221
|
}, z.core.$strip>;
|
|
211
222
|
sessions: z.ZodObject<{
|
|
212
223
|
enabled: z.ZodBoolean;
|
|
213
224
|
order: z.ZodNumber;
|
|
214
225
|
limit: z.ZodNumber;
|
|
226
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
215
227
|
}, z.core.$strip>;
|
|
216
228
|
conversations: z.ZodObject<{
|
|
217
229
|
enabled: z.ZodBoolean;
|
|
218
230
|
order: z.ZodNumber;
|
|
219
231
|
limit: z.ZodNumber;
|
|
232
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
220
233
|
}, z.core.$strip>;
|
|
221
234
|
}, z.core.$strip>>;
|
|
222
235
|
}, z.core.$strip>;
|
package/lib/sync/index.js
CHANGED
|
@@ -109,9 +109,9 @@ export class ConversationSyncManager {
|
|
|
109
109
|
const failures = new Map();
|
|
110
110
|
let succeeded = 0;
|
|
111
111
|
try {
|
|
112
|
-
|
|
112
|
+
for (const provider of job.providers) {
|
|
113
113
|
if (job.controller.signal.aborted)
|
|
114
|
-
|
|
114
|
+
break;
|
|
115
115
|
const started = Date.now();
|
|
116
116
|
try {
|
|
117
117
|
await this.syncProvider(job, provider, mode, options);
|
|
@@ -123,11 +123,11 @@ export class ConversationSyncManager {
|
|
|
123
123
|
// logged-out account would otherwise permanently starve every
|
|
124
124
|
// provider after it in this list.
|
|
125
125
|
if (job.controller.signal.aborted)
|
|
126
|
-
|
|
126
|
+
break;
|
|
127
127
|
failures.set(provider, describe(error));
|
|
128
128
|
this.logger?.warn(`reference sync: ${provider} failed: ${describe(error)}`);
|
|
129
129
|
}
|
|
130
|
-
}
|
|
130
|
+
}
|
|
131
131
|
job.status = job.controller.signal.aborted ? 'cancelled'
|
|
132
132
|
: failures.size === 0 ? 'complete'
|
|
133
133
|
: succeeded === 0 ? 'failed' : 'partial';
|
package/lib/typert.js
CHANGED
|
@@ -5,10 +5,9 @@ export const TYPERT_MANIFEST = {
|
|
|
5
5
|
key: 'referenceAnything', exportName: 'ReferenceAnythingRemote',
|
|
6
6
|
description: 'Local conversation mirror, synchronization, and settings for @Conversation.', tags: [],
|
|
7
7
|
members: [
|
|
8
|
-
{ kind: 'method', name: 'workspaceSearch', signature: 'workspaceSearch(agent: Agent, signal: AbortSignal): Promise<WorkspaceEntry[]>' },
|
|
9
|
-
{ kind: 'method', name: 'sessionSearch', signature: 'sessionSearch(agent: Agent, input: SessionSearchInput, signal: AbortSignal): Promise<SessionCandidate[]>' },
|
|
10
8
|
{ kind: 'method', name: 'search', signature: 'search(input: SearchInput): Promise<SearchResult[]>' },
|
|
11
9
|
{ kind: 'method', name: 'health', signature: 'health(signal: AbortSignal): Promise<Health>' },
|
|
10
|
+
{ kind: 'method', name: 'quickHealth', signature: 'quickHealth(signal: AbortSignal): Promise<Health>' },
|
|
12
11
|
{ kind: 'method', name: 'profiles', signature: 'profiles(signal: AbortSignal): Promise<BrowserProfile[]>' },
|
|
13
12
|
{ kind: 'method', name: 'discoverOpenCli', signature: 'discoverOpenCli(signal: AbortSignal): Promise<OpenCliDiscovery>' },
|
|
14
13
|
{ kind: 'method', name: 'installOpenCli', signature: 'installOpenCli(signal: AbortSignal): Promise<OpenCliDiscovery>' },
|
|
@@ -17,6 +16,7 @@ export const TYPERT_MANIFEST = {
|
|
|
17
16
|
{ kind: 'method', name: 'updateStatus', signature: 'updateStatus(signal: AbortSignal): Promise<PackageUpdateStatus>' },
|
|
18
17
|
{ kind: 'method', name: 'checkUpdate', signature: 'checkUpdate(signal: AbortSignal): Promise<PackageUpdateStatus>' },
|
|
19
18
|
{ kind: 'method', name: 'installUpdate', signature: 'installUpdate(signal: AbortSignal): Promise<PackageUpdateResult>' },
|
|
19
|
+
{ kind: 'method', name: 'switchReferenceUiMode', signature: 'switchReferenceUiMode(input: ReferenceUiModeInput, signal: AbortSignal): Promise<ReferenceUiSwitchResult>' },
|
|
20
20
|
{ kind: 'method', name: 'stats', signature: 'stats(): ProviderStats[]' },
|
|
21
21
|
{ kind: 'method', name: 'syncStart', signature: 'syncStart(input: SyncStart): string' },
|
|
22
22
|
{ kind: 'method', name: 'syncStatus', signature: 'syncStatus(input: JobInput): SyncStatus | undefined' },
|
package/lib/web.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReferenceAnythingRemote } from "./host.js";
|
|
2
2
|
import { TYPERT_MANIFEST } from "./typert.js";
|
|
3
3
|
export const name = 'reference-anything-web';
|
|
4
|
-
export const inject = ['typert', 'referenceChatHistory'
|
|
4
|
+
export const inject = ['typert', 'referenceChatHistory'];
|
|
5
5
|
export function apply(ctx) {
|
|
6
6
|
new ReferenceAnythingRemote(ctx);
|
|
7
7
|
ctx.effect(() => {
|
package/lib/wire.d.ts
CHANGED
|
@@ -22,41 +22,57 @@ declare const pickerSourceSettingsSchema: z.ZodObject<{
|
|
|
22
22
|
enabled: z.ZodBoolean;
|
|
23
23
|
order: z.ZodNumber;
|
|
24
24
|
limit: z.ZodNumber;
|
|
25
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
25
26
|
}, z.core.$strip>;
|
|
26
27
|
export type PickerSourceSettings = z.infer<typeof pickerSourceSettingsSchema>;
|
|
27
28
|
export declare const pickerSettingsSchema: z.ZodObject<{
|
|
29
|
+
displayMode: z.ZodDefault<z.ZodEnum<{
|
|
30
|
+
collapse: "collapse";
|
|
31
|
+
"native-scroll": "native-scroll";
|
|
32
|
+
}>>;
|
|
28
33
|
commands: z.ZodObject<{
|
|
29
34
|
enabled: z.ZodBoolean;
|
|
30
35
|
order: z.ZodNumber;
|
|
31
36
|
limit: z.ZodNumber;
|
|
37
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
32
38
|
}, z.core.$strip>;
|
|
33
39
|
skills: z.ZodObject<{
|
|
34
40
|
enabled: z.ZodBoolean;
|
|
35
41
|
order: z.ZodNumber;
|
|
36
42
|
limit: z.ZodNumber;
|
|
43
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
37
44
|
}, z.core.$strip>;
|
|
38
45
|
files: z.ZodObject<{
|
|
39
46
|
enabled: z.ZodBoolean;
|
|
40
47
|
order: z.ZodNumber;
|
|
41
48
|
limit: z.ZodNumber;
|
|
49
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
42
50
|
}, z.core.$strip>;
|
|
43
51
|
sessions: z.ZodObject<{
|
|
44
52
|
enabled: z.ZodBoolean;
|
|
45
53
|
order: z.ZodNumber;
|
|
46
54
|
limit: z.ZodNumber;
|
|
55
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
47
56
|
}, z.core.$strip>;
|
|
48
57
|
conversations: z.ZodObject<{
|
|
49
58
|
enabled: z.ZodBoolean;
|
|
50
59
|
order: z.ZodNumber;
|
|
51
60
|
limit: z.ZodNumber;
|
|
61
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
52
62
|
}, z.core.$strip>;
|
|
53
63
|
}, z.core.$strip>;
|
|
54
64
|
export type PickerSettings = z.infer<typeof pickerSettingsSchema>;
|
|
65
|
+
export type PickerDisplayMode = PickerSettings['displayMode'];
|
|
55
66
|
export declare const inputRenderModeSchema: z.ZodEnum<{
|
|
56
67
|
pill: "pill";
|
|
57
68
|
"raw-text": "raw-text";
|
|
58
69
|
}>;
|
|
59
70
|
export type InputRenderMode = z.infer<typeof inputRenderModeSchema>;
|
|
71
|
+
export declare const referenceUiModeSchema: z.ZodEnum<{
|
|
72
|
+
plugin: "plugin";
|
|
73
|
+
official: "official";
|
|
74
|
+
}>;
|
|
75
|
+
export type ReferenceUiMode = z.infer<typeof referenceUiModeSchema>;
|
|
60
76
|
/** Defaults used before the user saves the General section. */
|
|
61
77
|
export declare function defaultPickerSettings(): PickerSettings;
|
|
62
78
|
/** Picker settings have a fixed schema, so explicit comparison is cheap and stable. */
|
|
@@ -85,31 +101,44 @@ export declare const settingsRecordSchema: z.ZodObject<{
|
|
|
85
101
|
pill: "pill";
|
|
86
102
|
"raw-text": "raw-text";
|
|
87
103
|
}>>;
|
|
104
|
+
referenceUiMode: z.ZodOptional<z.ZodEnum<{
|
|
105
|
+
plugin: "plugin";
|
|
106
|
+
official: "official";
|
|
107
|
+
}>>;
|
|
88
108
|
picker: z.ZodOptional<z.ZodObject<{
|
|
109
|
+
displayMode: z.ZodDefault<z.ZodEnum<{
|
|
110
|
+
collapse: "collapse";
|
|
111
|
+
"native-scroll": "native-scroll";
|
|
112
|
+
}>>;
|
|
89
113
|
commands: z.ZodObject<{
|
|
90
114
|
enabled: z.ZodBoolean;
|
|
91
115
|
order: z.ZodNumber;
|
|
92
116
|
limit: z.ZodNumber;
|
|
117
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
93
118
|
}, z.core.$strip>;
|
|
94
119
|
skills: z.ZodObject<{
|
|
95
120
|
enabled: z.ZodBoolean;
|
|
96
121
|
order: z.ZodNumber;
|
|
97
122
|
limit: z.ZodNumber;
|
|
123
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
98
124
|
}, z.core.$strip>;
|
|
99
125
|
files: z.ZodObject<{
|
|
100
126
|
enabled: z.ZodBoolean;
|
|
101
127
|
order: z.ZodNumber;
|
|
102
128
|
limit: z.ZodNumber;
|
|
129
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
103
130
|
}, z.core.$strip>;
|
|
104
131
|
sessions: z.ZodObject<{
|
|
105
132
|
enabled: z.ZodBoolean;
|
|
106
133
|
order: z.ZodNumber;
|
|
107
134
|
limit: z.ZodNumber;
|
|
135
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
108
136
|
}, z.core.$strip>;
|
|
109
137
|
conversations: z.ZodObject<{
|
|
110
138
|
enabled: z.ZodBoolean;
|
|
111
139
|
order: z.ZodNumber;
|
|
112
140
|
limit: z.ZodNumber;
|
|
141
|
+
maxCandidates: z.ZodDefault<z.ZodNumber>;
|
|
113
142
|
}, z.core.$strip>;
|
|
114
143
|
}, z.core.$strip>>;
|
|
115
144
|
}, z.core.$strip>;
|
package/lib/wire.js
CHANGED
|
@@ -7,8 +7,10 @@ const pickerSourceSettingsSchema = z.object({
|
|
|
7
7
|
enabled: z.boolean(),
|
|
8
8
|
order: z.number().int().min(0).max(100),
|
|
9
9
|
limit: z.number().int().min(1).max(50),
|
|
10
|
+
maxCandidates: z.number().int().min(1).max(50).default(50),
|
|
10
11
|
});
|
|
11
12
|
export const pickerSettingsSchema = z.object({
|
|
13
|
+
displayMode: z.enum(['collapse', 'native-scroll']).default('collapse'),
|
|
12
14
|
commands: pickerSourceSettingsSchema,
|
|
13
15
|
skills: pickerSourceSettingsSchema,
|
|
14
16
|
files: pickerSourceSettingsSchema,
|
|
@@ -16,21 +18,26 @@ export const pickerSettingsSchema = z.object({
|
|
|
16
18
|
conversations: pickerSourceSettingsSchema,
|
|
17
19
|
});
|
|
18
20
|
export const inputRenderModeSchema = z.enum(['pill', 'raw-text']);
|
|
21
|
+
export const referenceUiModeSchema = z.enum(['plugin', 'official']);
|
|
19
22
|
/** Defaults used before the user saves the General section. */
|
|
20
23
|
export function defaultPickerSettings() {
|
|
21
24
|
return {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
displayMode: 'collapse',
|
|
26
|
+
commands: { enabled: true, order: 0, limit: 6, maxCandidates: 50 },
|
|
27
|
+
skills: { enabled: true, order: 5, limit: 6, maxCandidates: 50 },
|
|
28
|
+
files: { enabled: true, order: 10, limit: 6, maxCandidates: 50 },
|
|
29
|
+
sessions: { enabled: true, order: 20, limit: 6, maxCandidates: 50 },
|
|
30
|
+
conversations: { enabled: true, order: 30, limit: 6, maxCandidates: 50 },
|
|
27
31
|
};
|
|
28
32
|
}
|
|
29
33
|
/** Picker settings have a fixed schema, so explicit comparison is cheap and stable. */
|
|
30
34
|
export function samePickerSettings(left, right) {
|
|
31
|
-
|
|
35
|
+
if (left.displayMode !== right.displayMode)
|
|
36
|
+
return false;
|
|
37
|
+
return pickerSourceSchema.options.every(key => left[key].enabled === right[key].enabled &&
|
|
32
38
|
left[key].order === right[key].order &&
|
|
33
|
-
left[key].limit === right[key].limit
|
|
39
|
+
left[key].limit === right[key].limit &&
|
|
40
|
+
left[key].maxCandidates === right[key].maxCandidates);
|
|
34
41
|
}
|
|
35
42
|
export const settingsRecordSchema = z.object({
|
|
36
43
|
opencliPath: z.string(),
|
|
@@ -43,6 +50,8 @@ export const settingsRecordSchema = z.object({
|
|
|
43
50
|
enabledProviders: z.array(providerSchema).default([...ALL_PROVIDERS]),
|
|
44
51
|
maxReadTurns: z.number().int().min(1).max(100).default(10),
|
|
45
52
|
inputRenderMode: inputRenderModeSchema.default('pill'),
|
|
53
|
+
// Optional keeps settings written by earlier plugin versions readable.
|
|
54
|
+
referenceUiMode: referenceUiModeSchema.optional(),
|
|
46
55
|
// Optional keeps existing on-disk settings forward-compatible. The client
|
|
47
56
|
// uses defaultPickerSettings() until the user saves the General section.
|
|
48
57
|
picker: pickerSettingsSchema.optional(),
|
package/opencli-plugin/common.js
CHANGED
|
@@ -49,10 +49,54 @@ function assertResult(result, domain, operation) {
|
|
|
49
49
|
return result.rows
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
const PROVIDER_READY_TIMEOUT_MS = 1200
|
|
53
|
+
const PROVIDER_READY_INITIAL_DELAY_MS = 50
|
|
54
|
+
const PROVIDER_READY_MAX_DELAY_MS = 400
|
|
55
|
+
|
|
56
|
+
function terminalProviderResult(result) {
|
|
57
|
+
return result?.ok === true || result?.code === 'AUTH' || result?.code === 'RATE_LIMIT'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function terminalIdentityResult(result) {
|
|
61
|
+
return result?.code === 'AUTH' || result?.code === 'RATE_LIMIT'
|
|
62
|
+
|| (result?.ok === true && typeof result.identity === 'string' && result.identity.length > 0)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Run the provider API as the readiness probe. The happy path performs one
|
|
67
|
+
* evaluate immediately after navigation; transient bootstrap failures retry
|
|
68
|
+
* with bounded exponential backoff instead of waiting for the whole SPA DOM
|
|
69
|
+
* to become quiet first.
|
|
70
|
+
*/
|
|
71
|
+
export async function evaluateWhenProviderReady(page, script, args, operation, options = {}) {
|
|
72
|
+
const timeoutMs = options.timeoutMs ?? PROVIDER_READY_TIMEOUT_MS
|
|
73
|
+
const maxDelayMs = options.maxDelayMs ?? PROVIDER_READY_MAX_DELAY_MS
|
|
74
|
+
const accepts = options.accepts ?? terminalProviderResult
|
|
75
|
+
const deadline = Date.now() + timeoutMs
|
|
76
|
+
let delayMs = options.initialDelayMs ?? PROVIDER_READY_INITIAL_DELAY_MS
|
|
77
|
+
let lastResult
|
|
78
|
+
let lastError
|
|
79
|
+
for (;;) {
|
|
80
|
+
try {
|
|
81
|
+
lastResult = await evaluate(page, script, args, operation)
|
|
82
|
+
lastError = undefined
|
|
83
|
+
if (accepts(lastResult)) return lastResult
|
|
84
|
+
} catch (error) {
|
|
85
|
+
lastError = error
|
|
86
|
+
}
|
|
87
|
+
const remaining = deadline - Date.now()
|
|
88
|
+
if (remaining <= 0) {
|
|
89
|
+
if (lastError) throw lastError
|
|
90
|
+
return lastResult
|
|
91
|
+
}
|
|
92
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(delayMs, remaining)))
|
|
93
|
+
delayMs = Math.min(Math.max(delayMs * 2, 1), maxDelayMs)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
52
97
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* Browser Bridge can keep and reuse the background container window.
|
|
98
|
+
* Each command owns an ephemeral Browser Bridge tab. Close that tab after the
|
|
99
|
+
* command finishes while preserving the provider result or failure.
|
|
56
100
|
*/
|
|
57
101
|
async function inTemporaryTab(page, task) {
|
|
58
102
|
try {
|
|
@@ -80,10 +124,12 @@ export function registerProvider(config) {
|
|
|
80
124
|
...browserSession,
|
|
81
125
|
navigateBefore: false,
|
|
82
126
|
args: [],
|
|
83
|
-
columns: ['identity'],
|
|
127
|
+
columns: ['identity'],
|
|
84
128
|
func: async (page) => inTemporaryTab(page, async () => {
|
|
85
|
-
await page.goto(config.home, {
|
|
86
|
-
const result = await
|
|
129
|
+
await page.goto(config.home, { waitUntil: 'none' })
|
|
130
|
+
const result = await evaluateWhenProviderReady(
|
|
131
|
+
page, config.whoamiScript, {}, `${config.site} whoami`, { accepts: terminalIdentityResult },
|
|
132
|
+
)
|
|
87
133
|
if (result?.code === 'AUTH') throw new AuthRequiredError(config.domain)
|
|
88
134
|
if (result?.ok !== true || typeof result.identity !== 'string' || !result.identity) {
|
|
89
135
|
throw new CommandExecutionError(`${config.site} whoami: ${result?.message || 'stable account identity unavailable'}`)
|
|
@@ -110,8 +156,10 @@ export function registerProvider(config) {
|
|
|
110
156
|
func: async (page, kwargs) => inTemporaryTab(page, async () => {
|
|
111
157
|
const since = String(kwargs.since || '').trim()
|
|
112
158
|
if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
|
|
113
|
-
await page.goto(config.home, {
|
|
114
|
-
const identity = await
|
|
159
|
+
await page.goto(config.home, { waitUntil: 'none' })
|
|
160
|
+
const identity = await evaluateWhenProviderReady(
|
|
161
|
+
page, config.whoamiScript, {}, `${config.site} sync-index identity`, { accepts: terminalIdentityResult },
|
|
162
|
+
)
|
|
115
163
|
if (identity?.code === 'AUTH') throw new AuthRequiredError(config.domain)
|
|
116
164
|
if (identity?.ok !== true || typeof identity.identity !== 'string' || !identity.identity) {
|
|
117
165
|
throw new CommandExecutionError(`${config.site} sync-index: ${identity?.message || 'stable account identity unavailable'}`)
|
|
@@ -147,12 +195,12 @@ export function registerProvider(config) {
|
|
|
147
195
|
columns: HISTORY_COLUMNS,
|
|
148
196
|
func: async (page, kwargs) => inTemporaryTab(page, async () => {
|
|
149
197
|
const since = String(kwargs.since || '').trim()
|
|
150
|
-
if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
|
|
151
|
-
await page.goto(config.home, {
|
|
152
|
-
const rows = assertResult(
|
|
153
|
-
await
|
|
154
|
-
config.domain,
|
|
155
|
-
`${config.site} history-all`,
|
|
198
|
+
if (since && Number.isNaN(Date.parse(since))) throw new ArgumentError('since must be an ISO 8601 instant')
|
|
199
|
+
await page.goto(config.home, { waitUntil: 'none' })
|
|
200
|
+
const rows = assertResult(
|
|
201
|
+
await evaluateWhenProviderReady(page, config.historyScript, { since }, `${config.site} history-all`),
|
|
202
|
+
config.domain,
|
|
203
|
+
`${config.site} history-all`,
|
|
156
204
|
)
|
|
157
205
|
return rows
|
|
158
206
|
}),
|
|
@@ -247,16 +295,12 @@ export function registerProvider(config) {
|
|
|
247
295
|
localPath: output,
|
|
248
296
|
}]
|
|
249
297
|
}),
|
|
250
|
-
})
|
|
298
|
+
})
|
|
251
299
|
}
|
|
252
300
|
|
|
253
|
-
/**
|
|
254
|
-
* Keep one stable, background Browser Bridge session per provider site.
|
|
255
|
-
* Persistence suppresses OpenCLI's `close-window` action; inTemporaryTab closes
|
|
256
|
-
* only the finished command's tab so the reusable container window stays alive.
|
|
257
|
-
*/
|
|
301
|
+
/** Browser Bridge lifecycle shared by every web-chat command. */
|
|
258
302
|
export const SYNC_BROWSER_SESSION = Object.freeze({
|
|
259
|
-
siteSession: '
|
|
303
|
+
siteSession: 'ephemeral',
|
|
260
304
|
defaultWindowMode: 'background',
|
|
261
305
|
})
|
|
262
306
|
|