@frockbot/plugin-shell 0.0.0 → 0.1.1
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/frockbot.json +68 -0
- package/package.json +87 -6
- package/src/agent.test.ts +372 -0
- package/src/agent.ts +335 -0
- package/src/approvals.test.ts +224 -0
- package/src/approvals.ts +530 -0
- package/src/backend-assignment.test.ts +161 -0
- package/src/backend-assignment.ts +274 -0
- package/src/backend-authoring.test.ts +518 -0
- package/src/backend-authoring.ts +531 -0
- package/src/backend-bot-identity.test.ts +215 -0
- package/src/backend-completion.test.ts +289 -0
- package/src/backend-completion.ts +95 -0
- package/src/backend-composition.ts +242 -0
- package/src/backend-computer.ts +76 -0
- package/src/backend-configuration.test.ts +1757 -0
- package/src/backend-contracts.test.ts +189 -0
- package/src/backend-contracts.ts +44 -0
- package/src/backend-debug.test.ts +202 -0
- package/src/backend-execution.ts +55 -0
- package/src/backend-flock.ts +96 -0
- package/src/backend-image.test.ts +115 -0
- package/src/backend-image.ts +180 -0
- package/src/backend-isolate.test.ts +238 -0
- package/src/backend-isolate.ts +409 -0
- package/src/backend-machine.ts +144 -0
- package/src/backend-memory.ts +89 -0
- package/src/backend-recovery-integration.test.ts +1575 -0
- package/src/backend-recovery.ts +106 -0
- package/src/backend-routines.ts +375 -0
- package/src/backend-runner.ts +251 -0
- package/src/backend-skills.test.ts +126 -0
- package/src/backend-skills.ts +198 -0
- package/src/backend-stop.test.ts +356 -0
- package/src/backend-subagents.ts +459 -0
- package/src/backend.ts +6035 -0
- package/src/client/FrockBotApp.vue +1026 -0
- package/src/client/SendPayloadView.vue +337 -0
- package/src/client/composer-draft.test.ts +31 -0
- package/src/client/composer-draft.ts +35 -0
- package/src/client/cordis-client-shim.d.ts +15 -0
- package/src/client/index.test.ts +2548 -0
- package/src/client/index.ts +2346 -0
- package/src/client/model-presentation.test.ts +35 -0
- package/src/client/model-presentation.ts +19 -0
- package/src/client/notify.test.ts +89 -0
- package/src/client/notify.ts +101 -0
- package/src/client/skill-invocation.test.ts +143 -0
- package/src/client/skill-invocation.ts +175 -0
- package/src/client/styles.css +1043 -0
- package/src/composition-views.ts +118 -0
- package/src/debug-protocol.test.ts +80 -0
- package/src/debug-protocol.ts +165 -0
- package/src/env.d.ts +10 -0
- package/src/history.test.ts +163 -0
- package/src/history.ts +108 -0
- package/src/host.ts +20 -0
- package/src/index.ts +2 -0
- package/src/manifest.ts +3 -0
- package/src/run-cursor.ts +28 -0
- package/src/run-protocol.test.ts +1281 -0
- package/src/run-protocol.ts +1417 -0
- package/src/settings-links.test.ts +106 -0
- package/src/settings-links.ts +289 -0
- package/src/shared.ts +338 -0
- package/src/skill-protocol.ts +117 -0
- package/src/terminal-records.test.ts +217 -0
- package/src/terminal-records.ts +150 -0
- package/src/unread.test.ts +362 -0
- package/src/unread.ts +675 -0
- package/tsconfig.json +18 -0
- package/vite.config.ts +32 -0
- package/README.md +0 -3
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/*
|
|
3
|
+
* One user-facing send, drawn in the thread on the Bot's side.
|
|
4
|
+
*
|
|
5
|
+
* Read-only by design, with one exception. A widget shows the question and its
|
|
6
|
+
* options but does not answer it: answering queues a normal chat Turn, which is
|
|
7
|
+
* a later slice, and a control that looks live but is not would be worse than
|
|
8
|
+
* none. An approval card *is* live, because its answer is not a Turn — it is a
|
|
9
|
+
* durable decision recorded against a record the Bot Durable Object already
|
|
10
|
+
* holds, and the buttons submit exactly that command.
|
|
11
|
+
*
|
|
12
|
+
* The card renders the decision the backend reports, never the click: a card
|
|
13
|
+
* answered on another device, or expired by the alarm, shows what was actually
|
|
14
|
+
* recorded.
|
|
15
|
+
*
|
|
16
|
+
* Anything this client cannot draw — a payload shape newer than this bundle,
|
|
17
|
+
* or one the decoder refused — becomes a plain line saying so. A Turn's
|
|
18
|
+
* history has to render on a client older than the Bot that produced it.
|
|
19
|
+
*/
|
|
20
|
+
import { UiButton, UiMarkdown } from "@frockbot/client-ui";
|
|
21
|
+
import { computed, inject, ref } from "vue";
|
|
22
|
+
import { settingsLinkV1 } from "../settings-links.js";
|
|
23
|
+
import { frockBotWebDataKey, type WebSendPayload } from "../shared.js";
|
|
24
|
+
|
|
25
|
+
const props = defineProps<{ send: WebSendPayload }>();
|
|
26
|
+
|
|
27
|
+
const web = inject(frockBotWebDataKey);
|
|
28
|
+
const deciding = ref(false);
|
|
29
|
+
|
|
30
|
+
const payload = computed(() =>
|
|
31
|
+
props.send.kind === "payload" ? props.send.payload : undefined,
|
|
32
|
+
);
|
|
33
|
+
const text = computed(() =>
|
|
34
|
+
payload.value?.type === "text" ? payload.value.text : undefined,
|
|
35
|
+
);
|
|
36
|
+
const widget = computed(() =>
|
|
37
|
+
payload.value?.type === "widget" ? payload.value.widget : undefined,
|
|
38
|
+
);
|
|
39
|
+
const attachment = computed(() =>
|
|
40
|
+
payload.value?.type === "attachment" ? payload.value : undefined,
|
|
41
|
+
);
|
|
42
|
+
/**
|
|
43
|
+
* The connect card. It carries no URL and never will: the Bot recorded a
|
|
44
|
+
* pending decision, and only the User — in Settings, where the host authors
|
|
45
|
+
* the link at the moment they press it — can complete one. So this draws the
|
|
46
|
+
* request and points at the place the decision is made, and is deliberately
|
|
47
|
+
* not a button that authorizes anything.
|
|
48
|
+
*/
|
|
49
|
+
const connectCard = computed(() =>
|
|
50
|
+
payload.value?.type === "connect-card" ? payload.value : undefined,
|
|
51
|
+
);
|
|
52
|
+
const connectionsLink = settingsLinkV1({ anchor: "user-packages" });
|
|
53
|
+
const approval = computed(() =>
|
|
54
|
+
payload.value?.type === "approval" ? payload.value : undefined,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/** What the backend says about this card, when it has been read. */
|
|
58
|
+
const record = computed(() =>
|
|
59
|
+
approval.value
|
|
60
|
+
? web?.value.approvals.find(
|
|
61
|
+
(candidate) => candidate.approvalId === approval.value?.approvalId,
|
|
62
|
+
)
|
|
63
|
+
: undefined,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const decision = computed(() => record.value?.decision ?? "pending");
|
|
67
|
+
|
|
68
|
+
const decided = computed(() =>
|
|
69
|
+
decision.value === "approved"
|
|
70
|
+
? "You approved this."
|
|
71
|
+
: decision.value === "denied"
|
|
72
|
+
? "You denied this."
|
|
73
|
+
: decision.value === "expired"
|
|
74
|
+
? "This expired before anyone answered."
|
|
75
|
+
: undefined,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const expiry = computed(() => {
|
|
79
|
+
const at = record.value?.expiresAt;
|
|
80
|
+
if (!at || decision.value !== "pending") return undefined;
|
|
81
|
+
const parsed = new Date(at);
|
|
82
|
+
return Number.isNaN(parsed.getTime())
|
|
83
|
+
? undefined
|
|
84
|
+
: `Expires ${parsed.toLocaleString()}`;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
async function decide(next: "approved" | "denied"): Promise<void> {
|
|
88
|
+
if (!web || !approval.value || deciding.value) return;
|
|
89
|
+
deciding.value = true;
|
|
90
|
+
try {
|
|
91
|
+
await web.value.decideApproval(approval.value.approvalId, next);
|
|
92
|
+
} finally {
|
|
93
|
+
deciding.value = false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const unsupported = computed(
|
|
98
|
+
() => !text.value && !widget.value && !attachment.value && !approval.value,
|
|
99
|
+
);
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<template>
|
|
103
|
+
<div v-if="text" class="send-text"><UiMarkdown :text="text" /></div>
|
|
104
|
+
|
|
105
|
+
<section v-else-if="widget" class="send-widget" aria-label="Question">
|
|
106
|
+
<p class="send-widget-prompt">{{ widget.prompt }}</p>
|
|
107
|
+
<p v-if="widget.helpText" class="send-widget-help">
|
|
108
|
+
{{ widget.helpText }}
|
|
109
|
+
</p>
|
|
110
|
+
<ul class="send-widget-options">
|
|
111
|
+
<li v-for="option in widget.options" :key="option">{{ option }}</li>
|
|
112
|
+
</ul>
|
|
113
|
+
<p v-if="widget.allowCustom" class="send-widget-help">
|
|
114
|
+
Any other answer is accepted too.
|
|
115
|
+
</p>
|
|
116
|
+
</section>
|
|
117
|
+
|
|
118
|
+
<section
|
|
119
|
+
v-else-if="connectCard"
|
|
120
|
+
class="send-connect-card"
|
|
121
|
+
aria-label="Connection request"
|
|
122
|
+
>
|
|
123
|
+
<p class="send-connect-title">{{ connectCard.title }}</p>
|
|
124
|
+
<p v-if="connectCard.body" class="send-connect-body">
|
|
125
|
+
{{ connectCard.body }}
|
|
126
|
+
</p>
|
|
127
|
+
<p class="send-connect-help">
|
|
128
|
+
Open Settings → Plugins to authorize it. Only you can complete this.
|
|
129
|
+
<a :href="connectionsLink">{{ connectionsLink }}</a>
|
|
130
|
+
</p>
|
|
131
|
+
</section>
|
|
132
|
+
|
|
133
|
+
<section
|
|
134
|
+
v-else-if="approval"
|
|
135
|
+
class="send-approval"
|
|
136
|
+
:class="`send-approval--${decision}`"
|
|
137
|
+
aria-label="Approval request"
|
|
138
|
+
>
|
|
139
|
+
<header class="send-approval-head">
|
|
140
|
+
<span class="send-approval-risk" :class="`risk-${approval.risk}`">{{
|
|
141
|
+
approval.risk
|
|
142
|
+
}}</span>
|
|
143
|
+
<span class="send-approval-label">Needs your approval</span>
|
|
144
|
+
</header>
|
|
145
|
+
<p class="send-approval-action">{{ approval.action }}</p>
|
|
146
|
+
<p v-if="approval.rationale" class="send-approval-rationale">
|
|
147
|
+
{{ approval.rationale }}
|
|
148
|
+
</p>
|
|
149
|
+
<p v-if="decided" class="send-approval-decided">{{ decided }}</p>
|
|
150
|
+
<div v-else class="send-approval-actions">
|
|
151
|
+
<UiButton :disabled="deciding || !web" @click="decide('approved')"
|
|
152
|
+
>Approve</UiButton
|
|
153
|
+
>
|
|
154
|
+
<UiButton
|
|
155
|
+
variant="ghost"
|
|
156
|
+
:disabled="deciding || !web"
|
|
157
|
+
@click="decide('denied')"
|
|
158
|
+
>Deny</UiButton
|
|
159
|
+
>
|
|
160
|
+
</div>
|
|
161
|
+
<p v-if="expiry" class="send-approval-expiry">{{ expiry }}</p>
|
|
162
|
+
</section>
|
|
163
|
+
|
|
164
|
+
<p v-else-if="attachment" class="send-attachment">
|
|
165
|
+
<a :href="attachment.url" target="_blank" rel="noopener noreferrer">{{
|
|
166
|
+
attachment.name ?? attachment.url
|
|
167
|
+
}}</a>
|
|
168
|
+
</p>
|
|
169
|
+
|
|
170
|
+
<p v-else-if="unsupported" class="send-unsupported">
|
|
171
|
+
This client cannot display that message.
|
|
172
|
+
</p>
|
|
173
|
+
</template>
|
|
174
|
+
|
|
175
|
+
<style scoped>
|
|
176
|
+
/* An assistant-side bubble, shaped like the one the derived text uses. */
|
|
177
|
+
.send-text {
|
|
178
|
+
width: max-content;
|
|
179
|
+
max-width: 100%;
|
|
180
|
+
border: 1px solid var(--frock-border);
|
|
181
|
+
border-radius: 18px 18px 18px 6px;
|
|
182
|
+
background: var(--frock-surface-subtle);
|
|
183
|
+
padding: 10px 14px;
|
|
184
|
+
color: var(--frock-text);
|
|
185
|
+
font-size: var(--frock-text-md);
|
|
186
|
+
line-height: var(--frock-leading-normal);
|
|
187
|
+
overflow-wrap: anywhere;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.send-widget {
|
|
191
|
+
display: flex;
|
|
192
|
+
flex-direction: column;
|
|
193
|
+
gap: 0.5rem;
|
|
194
|
+
border: 1px solid var(--frock-border);
|
|
195
|
+
border-radius: var(--frock-radius-card);
|
|
196
|
+
background: var(--frock-surface-raised);
|
|
197
|
+
padding: 0.75rem 1rem;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.send-widget-prompt {
|
|
201
|
+
margin: 0;
|
|
202
|
+
color: var(--frock-text);
|
|
203
|
+
font-weight: 600;
|
|
204
|
+
font-size: var(--frock-text-base);
|
|
205
|
+
line-height: var(--frock-leading-snug);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.send-widget-help {
|
|
209
|
+
margin: 0;
|
|
210
|
+
color: var(--frock-text-muted);
|
|
211
|
+
font-size: var(--frock-text-sm);
|
|
212
|
+
line-height: var(--frock-leading-normal);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.send-widget-options {
|
|
216
|
+
display: flex;
|
|
217
|
+
flex-wrap: wrap;
|
|
218
|
+
gap: 0.375rem;
|
|
219
|
+
margin: 0;
|
|
220
|
+
padding: 0;
|
|
221
|
+
list-style: none;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.send-connect-card {
|
|
225
|
+
display: flex;
|
|
226
|
+
flex-direction: column;
|
|
227
|
+
gap: 0.5rem;
|
|
228
|
+
border: 1px solid var(--frock-border);
|
|
229
|
+
border-radius: var(--frock-radius-card);
|
|
230
|
+
background: var(--frock-surface-raised);
|
|
231
|
+
padding: 0.75rem 1rem;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.send-connect-title {
|
|
235
|
+
margin: 0;
|
|
236
|
+
color: var(--frock-text);
|
|
237
|
+
font-weight: 600;
|
|
238
|
+
font-size: var(--frock-text-base);
|
|
239
|
+
line-height: var(--frock-leading-snug);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.send-connect-body {
|
|
243
|
+
margin: 0;
|
|
244
|
+
color: var(--frock-text);
|
|
245
|
+
font-size: var(--frock-text-sm);
|
|
246
|
+
line-height: var(--frock-leading-normal);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.send-connect-help {
|
|
250
|
+
margin: 0;
|
|
251
|
+
color: var(--frock-text-muted);
|
|
252
|
+
font-size: var(--frock-text-sm);
|
|
253
|
+
line-height: var(--frock-leading-normal);
|
|
254
|
+
overflow-wrap: anywhere;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.send-widget-options li {
|
|
258
|
+
border: 1px solid var(--frock-border);
|
|
259
|
+
border-radius: var(--frock-radius-control);
|
|
260
|
+
background: var(--frock-surface);
|
|
261
|
+
padding: 0.25rem 0.625rem;
|
|
262
|
+
color: var(--frock-text);
|
|
263
|
+
font-size: var(--frock-text-sm);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.send-approval {
|
|
267
|
+
display: flex;
|
|
268
|
+
flex-direction: column;
|
|
269
|
+
gap: 0.5rem;
|
|
270
|
+
border: 1px solid var(--frock-border);
|
|
271
|
+
border-radius: var(--frock-radius-card);
|
|
272
|
+
background: var(--frock-surface-raised);
|
|
273
|
+
padding: 0.75rem 1rem;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.send-approval-head {
|
|
277
|
+
display: flex;
|
|
278
|
+
gap: 0.5rem;
|
|
279
|
+
align-items: center;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.send-approval-risk {
|
|
283
|
+
border: 1px solid var(--frock-border);
|
|
284
|
+
border-radius: var(--frock-radius-control);
|
|
285
|
+
padding: 0.125rem 0.5rem;
|
|
286
|
+
color: var(--frock-text-muted);
|
|
287
|
+
font-size: var(--frock-text-xs);
|
|
288
|
+
text-transform: uppercase;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.send-approval-risk.risk-high {
|
|
292
|
+
border-color: var(--frock-danger-border);
|
|
293
|
+
color: var(--frock-danger-text);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.send-approval-label {
|
|
297
|
+
color: var(--frock-text-muted);
|
|
298
|
+
font-size: var(--frock-text-sm);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.send-approval-action {
|
|
302
|
+
margin: 0;
|
|
303
|
+
color: var(--frock-text);
|
|
304
|
+
font-weight: 600;
|
|
305
|
+
font-size: var(--frock-text-base);
|
|
306
|
+
line-height: var(--frock-leading-snug);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.send-approval-rationale,
|
|
310
|
+
.send-approval-decided,
|
|
311
|
+
.send-approval-expiry {
|
|
312
|
+
margin: 0;
|
|
313
|
+
color: var(--frock-text-muted);
|
|
314
|
+
font-size: var(--frock-text-sm);
|
|
315
|
+
line-height: var(--frock-leading-normal);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
.send-approval-actions {
|
|
319
|
+
display: flex;
|
|
320
|
+
gap: 0.5rem;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.send-attachment,
|
|
324
|
+
.send-unsupported {
|
|
325
|
+
margin: 0;
|
|
326
|
+
font-size: var(--frock-text-sm);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
.send-attachment a {
|
|
330
|
+
color: var(--frock-accent-text);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.send-unsupported {
|
|
334
|
+
color: var(--frock-text-muted);
|
|
335
|
+
font-style: italic;
|
|
336
|
+
}
|
|
337
|
+
</style>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { ComposerDraftStore } from "./composer-draft.js";
|
|
3
|
+
|
|
4
|
+
describe("composer draft restoration", () => {
|
|
5
|
+
test("retains a rejected submission in its originating Bot draft", () => {
|
|
6
|
+
const drafts = new ComposerDraftStore();
|
|
7
|
+
const submission = drafts.begin("bot-a", "message for A");
|
|
8
|
+
drafts.setDraft("bot-b", "message for B");
|
|
9
|
+
|
|
10
|
+
expect(drafts.reject(submission)).toBe("message for A");
|
|
11
|
+
expect(drafts.draftFor("bot-a")).toBe("message for A");
|
|
12
|
+
expect(drafts.draftFor("bot-b")).toBe("message for B");
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("does not restore an older submission over a newer one for the same Bot", () => {
|
|
16
|
+
const drafts = new ComposerDraftStore();
|
|
17
|
+
const stale = drafts.begin("bot-a", "older");
|
|
18
|
+
const current = drafts.begin("bot-a", "newer");
|
|
19
|
+
|
|
20
|
+
expect(drafts.reject(stale)).toBeUndefined();
|
|
21
|
+
expect(drafts.reject(current)).toBe("newer");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("preserves text typed after submission alongside rejected text", () => {
|
|
25
|
+
const drafts = new ComposerDraftStore();
|
|
26
|
+
const submission = drafts.begin("bot-a", "rejected");
|
|
27
|
+
drafts.setDraft("bot-a", "new draft");
|
|
28
|
+
|
|
29
|
+
expect(drafts.reject(submission)).toBe("rejected\n\nnew draft");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface ComposerSubmissionToken {
|
|
2
|
+
generation: number;
|
|
3
|
+
context: unknown;
|
|
4
|
+
text: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class ComposerDraftStore {
|
|
8
|
+
readonly #drafts = new Map<unknown, string>();
|
|
9
|
+
readonly #generations = new Map<unknown, number>();
|
|
10
|
+
|
|
11
|
+
draftFor(context: unknown): string {
|
|
12
|
+
return this.#drafts.get(context) ?? "";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
setDraft(context: unknown, draft: string): void {
|
|
16
|
+
this.#drafts.set(context, draft);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
begin(context: unknown, text: string): ComposerSubmissionToken {
|
|
20
|
+
const generation = (this.#generations.get(context) ?? 0) + 1;
|
|
21
|
+
this.#generations.set(context, generation);
|
|
22
|
+
this.#drafts.set(context, "");
|
|
23
|
+
return { generation, context, text };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
reject(token: ComposerSubmissionToken): string | undefined {
|
|
27
|
+
if (this.#generations.get(token.context) !== token.generation) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const existing = this.draftFor(token.context);
|
|
31
|
+
const restored = existing ? `${token.text}\n\n${existing}` : token.text;
|
|
32
|
+
this.#drafts.set(token.context, restored);
|
|
33
|
+
return restored;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Component, Ref } from "vue";
|
|
2
|
+
|
|
3
|
+
export interface Context {
|
|
4
|
+
client: {
|
|
5
|
+
router: {
|
|
6
|
+
slot(options: {
|
|
7
|
+
type: string;
|
|
8
|
+
order?: number;
|
|
9
|
+
component: Component;
|
|
10
|
+
}): void;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export declare function useRpc<T>(): Ref<T>;
|