@peers-app/peers-sdk 0.21.7 → 0.22.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/dist/contracts/__tests__/contract-resolvers.test.js +35 -0
- package/dist/contracts/contract-proxy.d.ts +21 -0
- package/dist/contracts/contract-proxy.js +3 -1
- package/dist/contracts/contract-resolvers.d.ts +10 -0
- package/dist/contracts/contract-resolvers.js +12 -2
- package/dist/contracts/index.d.ts +2 -2
- package/dist/data/index.d.ts +0 -1
- package/dist/data/index.js +0 -1
- package/dist/package-installer/package-creator.d.ts +31 -0
- package/dist/package-installer/package-creator.js +140 -40
- package/dist/package-installer/package-creator.test.js +113 -11
- package/dist/rpc-types.d.ts +0 -17
- package/dist/rpc-types.js +0 -15
- package/package.json +1 -1
- package/dist/data/voice-messages.d.ts +0 -33
- package/dist/data/voice-messages.js +0 -33
|
@@ -239,4 +239,39 @@ describe("local user-context contract routing", () => {
|
|
|
239
239
|
await expect(setup.consumerTransport.emit(index_1.CONTRACT_REQUEST_CHANNEL, makeToolCall(setup.contractId))).rejects.toThrow("contract");
|
|
240
240
|
router.dispose();
|
|
241
241
|
});
|
|
242
|
+
it("appends host-only variable access to local package tool calls", async () => {
|
|
243
|
+
const setup = setupLocalRouter();
|
|
244
|
+
const resolveVariable = jest.fn(async ({ dataContext, variableName, requestingToolId }) => [dataContext.dataContextId, variableName, requestingToolId].join(":"));
|
|
245
|
+
(0, index_1.registerContractResolver)(setup.contractId, 1, () => ({
|
|
246
|
+
contractId: setup.contractId,
|
|
247
|
+
version: 1,
|
|
248
|
+
tools: {
|
|
249
|
+
inspect: {
|
|
250
|
+
tool: {
|
|
251
|
+
toolId: "00localcontractsecrettool",
|
|
252
|
+
name: "inspect",
|
|
253
|
+
usageDescription: "Resolve a local variable.",
|
|
254
|
+
inputSchema: { type: "simple", fields: [] },
|
|
255
|
+
outputSchema: { type: "simple", fields: [] },
|
|
256
|
+
code: "",
|
|
257
|
+
},
|
|
258
|
+
toolFn: async (variableName, context) => context.getVariable(variableName, "00localcontractsecrettool"),
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
}));
|
|
262
|
+
const router = (0, index_1.createLocalUserContextContractProviderRouter)(setup.providerTransport, {
|
|
263
|
+
getUserContext: async () => setup.userContext,
|
|
264
|
+
resolveVariable,
|
|
265
|
+
});
|
|
266
|
+
await expect(setup.consumerTransport.emit(index_1.CONTRACT_REQUEST_CHANNEL, {
|
|
267
|
+
...makeToolCall(setup.contractId),
|
|
268
|
+
args: ["OPENAI_API_KEY"],
|
|
269
|
+
})).resolves.toBe(`${setup.userId}:OPENAI_API_KEY:00localcontractsecrettool`);
|
|
270
|
+
expect(resolveVariable).toHaveBeenCalledWith(expect.objectContaining({
|
|
271
|
+
dataContext: expect.objectContaining({ dataContextId: setup.userId }),
|
|
272
|
+
variableName: "OPENAI_API_KEY",
|
|
273
|
+
requestingToolId: "00localcontractsecrettool",
|
|
274
|
+
}));
|
|
275
|
+
router.dispose();
|
|
276
|
+
});
|
|
242
277
|
});
|
|
@@ -195,6 +195,22 @@ export interface IContractConsumer extends IContractConsumerBase {
|
|
|
195
195
|
subscribe: (handler: (value: any) => void) => ISubscriptionResult;
|
|
196
196
|
}>;
|
|
197
197
|
}
|
|
198
|
+
/**
|
|
199
|
+
* Trusted host capabilities appended to local package-contract tool calls.
|
|
200
|
+
*
|
|
201
|
+
* The context is constructed by the provider host after it authorizes the data-context
|
|
202
|
+
* route. It is never serialized to the consumer and is intentionally absent for remote
|
|
203
|
+
* contract calls unless a host explicitly opts in.
|
|
204
|
+
*/
|
|
205
|
+
export interface IContractToolContext {
|
|
206
|
+
/** Authorized data context in which the tool is executing. */
|
|
207
|
+
dataContextId: string;
|
|
208
|
+
/**
|
|
209
|
+
* Resolve a persistent variable in the authorized context. Secret values are decrypted
|
|
210
|
+
* by the host and returned only to the provider-side tool implementation.
|
|
211
|
+
*/
|
|
212
|
+
getVariable(variableName: string, requestingToolId: string): Promise<string>;
|
|
213
|
+
}
|
|
198
214
|
/**
|
|
199
215
|
* Build the **consumer** side of a contract proxy from a contract definition and a
|
|
200
216
|
* transport. Member names come from the contract shape; table members are backed by a
|
|
@@ -234,6 +250,11 @@ export interface IContractResolution {
|
|
|
234
250
|
version: number;
|
|
235
251
|
tables?: Record<string, Table<any>>;
|
|
236
252
|
tools?: Record<string, IToolInstance>;
|
|
253
|
+
/**
|
|
254
|
+
* Host-only capabilities appended as the final argument to provider tool functions.
|
|
255
|
+
* Omit this for remote or otherwise untrusted provider routes.
|
|
256
|
+
*/
|
|
257
|
+
toolContext?: IContractToolContext;
|
|
237
258
|
observables?: Record<string, Observable<any>>;
|
|
238
259
|
/**
|
|
239
260
|
* Provider-owned write policy for live observables. A `set` is allowed only when the
|
|
@@ -394,7 +394,9 @@ function createContractProvider(resolution, opts = {}) {
|
|
|
394
394
|
if (!tool) {
|
|
395
395
|
throw new Error(`Contract ${call.contractId} does not provide tool '${call.name}'`);
|
|
396
396
|
}
|
|
397
|
-
return
|
|
397
|
+
return resolution.toolContext
|
|
398
|
+
? tool.toolFn(...call.args, resolution.toolContext)
|
|
399
|
+
: tool.toolFn(...call.args);
|
|
398
400
|
}
|
|
399
401
|
case "observable": {
|
|
400
402
|
const obs = resolution.observables?.[call.name];
|
|
@@ -12,6 +12,14 @@ import type { IContractTransport } from "./contract-transport";
|
|
|
12
12
|
export type ContractResolver = (ctx: {
|
|
13
13
|
dataContext: DataContext;
|
|
14
14
|
}) => IContractResolution | Promise<IContractResolution>;
|
|
15
|
+
/** Input supplied to a trusted local host when a package tool resolves a pvar. */
|
|
16
|
+
export interface ILocalContractVariableRequest {
|
|
17
|
+
dataContext: DataContext;
|
|
18
|
+
variableName: string;
|
|
19
|
+
requestingToolId: string;
|
|
20
|
+
}
|
|
21
|
+
/** Host callback that resolves plaintext only inside a local provider process. */
|
|
22
|
+
export type LocalContractVariableResolver = (request: ILocalContractVariableRequest) => Promise<string>;
|
|
15
23
|
/** Connection-authenticated identity fields used to authorize remote contract calls. */
|
|
16
24
|
export type ContractRemoteIdentity = Pick<IDeviceInfo, "deviceId" | "userId" | "publicKey" | "publicBoxKey">;
|
|
17
25
|
/**
|
|
@@ -45,10 +53,12 @@ export declare function isSelfTrustedContractCaller(userContext: UserContext, re
|
|
|
45
53
|
* @param transport Contract transport backed by an authenticated local session.
|
|
46
54
|
* @param opts.getUserContext Lazy accessor for the host's loaded user context.
|
|
47
55
|
* @param opts.allowedContractIds Optional contract allowlist for this session.
|
|
56
|
+
* @param opts.resolveVariable Optional host-only pvar resolver exposed to local package tools.
|
|
48
57
|
*/
|
|
49
58
|
export declare function createLocalUserContextContractProviderRouter(transport: IContractTransport, opts: {
|
|
50
59
|
getUserContext: () => Promise<UserContext>;
|
|
51
60
|
allowedContractIds?: ReadonlySet<string>;
|
|
61
|
+
resolveVariable?: LocalContractVariableResolver;
|
|
52
62
|
}): IContractProviderRouter;
|
|
53
63
|
/**
|
|
54
64
|
* Create the local-object provider router for one verified device connection.
|
|
@@ -100,9 +100,10 @@ function getAuthorizedLocalDataContext(userContext, requestedId) {
|
|
|
100
100
|
* @param transport Contract transport backed by an authenticated local session.
|
|
101
101
|
* @param opts.getUserContext Lazy accessor for the host's loaded user context.
|
|
102
102
|
* @param opts.allowedContractIds Optional contract allowlist for this session.
|
|
103
|
+
* @param opts.resolveVariable Optional host-only pvar resolver exposed to local package tools.
|
|
103
104
|
*/
|
|
104
105
|
function createLocalUserContextContractProviderRouter(transport, opts) {
|
|
105
|
-
const { getUserContext, allowedContractIds } = opts;
|
|
106
|
+
const { getUserContext, allowedContractIds, resolveVariable } = opts;
|
|
106
107
|
return (0, contract_provider_router_1.createContractProviderRouter)(transport, {
|
|
107
108
|
authorizeCall: async (call) => {
|
|
108
109
|
if (allowedContractIds && !allowedContractIds.has(call.contractId)) {
|
|
@@ -127,7 +128,16 @@ function createLocalUserContextContractProviderRouter(transport, opts) {
|
|
|
127
128
|
}
|
|
128
129
|
const dataContext = getAuthorizedLocalDataContext(userContext, dataContextId);
|
|
129
130
|
const resolution = await resolver({ dataContext });
|
|
130
|
-
|
|
131
|
+
const localResolution = resolveVariable
|
|
132
|
+
? {
|
|
133
|
+
...resolution,
|
|
134
|
+
toolContext: {
|
|
135
|
+
dataContextId: dataContext.dataContextId,
|
|
136
|
+
getVariable: (variableName, requestingToolId) => resolveVariable({ dataContext, variableName, requestingToolId }),
|
|
137
|
+
},
|
|
138
|
+
}
|
|
139
|
+
: resolution;
|
|
140
|
+
return (0, contract_proxy_1.createContractProviderEndpoint)(localResolution, { context, notify });
|
|
131
141
|
},
|
|
132
142
|
});
|
|
133
143
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { ContractBuilder, definePackage, PackageBuilder } from "./builder";
|
|
2
2
|
export { type ContractCallAuthorizer, type ContractProviderEndpointResolver, createContractProviderRouter, type IAuthorizedContractRoute, type IContractProviderEndpointRequest, type IContractProviderRouter, type IContractProviderRouterOptions, } from "./contract-provider-router";
|
|
3
3
|
export { ContractProviders, ContractProvidersTable, contractProvidersMetaData, contractProvidersSchema, type IContractProviderRecord, } from "./contract-providers.table";
|
|
4
|
-
export { CONTRACT_CALL_RPC, type ContractMemberType, type ContractNotificationSink, type ContractPermissionCheck, createContractConsumer, createContractProvider, createContractProviderEndpoint, createContractProviderSession, type IContractCall, type IContractCallContext, type IContractConsumer, type IContractConsumerBase, type IContractNotification, type IContractProviderEndpoint, type IContractProviderEndpointOptions, type IContractProviderSession, type IContractProviderSessionOptions, type IContractResolution, type IContractTableProxy, type ITableProxy, sameUserContractPermissionCheck, } from "./contract-proxy";
|
|
5
|
-
export { type ContractRemoteIdentity, type ContractResolver, createLocalUserContextContractProviderRouter, createUserContextContractProviderRouter, getContractResolver, handleContractCall, isSelfTrustedContractCaller, registerContractResolver, } from "./contract-resolvers";
|
|
4
|
+
export { CONTRACT_CALL_RPC, type ContractMemberType, type ContractNotificationSink, type ContractPermissionCheck, createContractConsumer, createContractProvider, createContractProviderEndpoint, createContractProviderSession, type IContractCall, type IContractCallContext, type IContractConsumer, type IContractConsumerBase, type IContractNotification, type IContractProviderEndpoint, type IContractProviderEndpointOptions, type IContractProviderSession, type IContractProviderSessionOptions, type IContractResolution, type IContractTableProxy, type IContractToolContext, type ITableProxy, sameUserContractPermissionCheck, } from "./contract-proxy";
|
|
5
|
+
export { type ContractRemoteIdentity, type ContractResolver, createLocalUserContextContractProviderRouter, createUserContextContractProviderRouter, getContractResolver, handleContractCall, type ILocalContractVariableRequest, isSelfTrustedContractCaller, type LocalContractVariableResolver, registerContractResolver, } from "./contract-resolvers";
|
|
6
6
|
export { CONTRACT_NOTIFY_CHANNEL, CONTRACT_REQUEST_CHANNEL, connectionContractTransport, type IContractTransport, type IDuplexEmitter, inProcessTransportPair, } from "./contract-transport";
|
|
7
7
|
export { Contracts, ContractsTable, contractsMetaData, contractsSchema, type IContractRecord, } from "./contracts.table";
|
|
8
8
|
export { createLocalContractConsumer } from "./create-local-consumer";
|
package/dist/data/index.d.ts
CHANGED
|
@@ -25,7 +25,6 @@ export * from "./tools";
|
|
|
25
25
|
export * from "./user-permissions";
|
|
26
26
|
export * from "./user-trust-levels";
|
|
27
27
|
export * from "./users";
|
|
28
|
-
export * from "./voice-messages";
|
|
29
28
|
export * from "./welcome-modal.pvar";
|
|
30
29
|
export * from "./workflow-logs";
|
|
31
30
|
export * from "./workflow-runs";
|
package/dist/data/index.js
CHANGED
|
@@ -41,7 +41,6 @@ __exportStar(require("./tools"), exports);
|
|
|
41
41
|
__exportStar(require("./user-permissions"), exports);
|
|
42
42
|
__exportStar(require("./user-trust-levels"), exports);
|
|
43
43
|
__exportStar(require("./users"), exports);
|
|
44
|
-
__exportStar(require("./voice-messages"), exports);
|
|
45
44
|
__exportStar(require("./welcome-modal.pvar"), exports);
|
|
46
45
|
__exportStar(require("./workflow-logs"), exports);
|
|
47
46
|
__exportStar(require("./workflow-runs"), exports);
|
|
@@ -12,11 +12,42 @@ export declare function nameToFolderSlug(name: string): string;
|
|
|
12
12
|
* @returns The path to the cached template directory.
|
|
13
13
|
*/
|
|
14
14
|
export declare function ensureTemplateCache(deps: IPackageInstallerDeps, templateRepo: string): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve (or create) the Ed25519 publish public key for a package.
|
|
17
|
+
*
|
|
18
|
+
* Idempotent: never rotates an established key. Reuses `existingPublishPublicKey`
|
|
19
|
+
* when present, derives the public key from an existing secret pvar when possible,
|
|
20
|
+
* and only generates a new keypair when both are missing.
|
|
21
|
+
*
|
|
22
|
+
* @throws When a readable secret key does not match an established `publishPublicKey`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function ensurePackageSigningKey(opts: {
|
|
25
|
+
personalContext?: DataContext;
|
|
26
|
+
packageId: string;
|
|
27
|
+
packageName: string;
|
|
28
|
+
existingPublishPublicKey?: string;
|
|
29
|
+
}): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Import an existing on-disk Peers package directory into the current data context.
|
|
32
|
+
*
|
|
33
|
+
* Validates the manifest packageId, creates/updates the Packages record, persists
|
|
34
|
+
* `packageLocalPath_*`, provisions a signing key when missing, then installs the
|
|
35
|
+
* local dev bundles.
|
|
36
|
+
*/
|
|
37
|
+
export declare function importExistingPackage(dataContext: DataContext, deps: IPackageInstallerDeps, opts: {
|
|
38
|
+
location: string;
|
|
39
|
+
personalContext?: DataContext;
|
|
40
|
+
/** Display name fallback when the manifest name is a slug. */
|
|
41
|
+
displayName?: string;
|
|
42
|
+
}): Promise<ICreatePackageResult>;
|
|
15
43
|
/**
|
|
16
44
|
* Create a new Peers package from the package template.
|
|
17
45
|
*
|
|
18
46
|
* Clones the template repo, patches IDs and metadata, runs npm install + build,
|
|
19
47
|
* initializes a fresh git repo, then delegates to {@link installPackageFromBundles}
|
|
20
48
|
* to register the dev bundle in the database.
|
|
49
|
+
*
|
|
50
|
+
* When `location` already exists and contains a valid Peers package manifest,
|
|
51
|
+
* imports that directory instead (see {@link importExistingPackage}).
|
|
21
52
|
*/
|
|
22
53
|
export declare function createPackage(dataContext: DataContext, deps: IPackageInstallerDeps, opts: ICreatePackageOpts): Promise<ICreatePackageResult>;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.nameToFolderSlug = nameToFolderSlug;
|
|
4
4
|
exports.ensureTemplateCache = ensureTemplateCache;
|
|
5
|
+
exports.ensurePackageSigningKey = ensurePackageSigningKey;
|
|
6
|
+
exports.importExistingPackage = importExistingPackage;
|
|
5
7
|
exports.createPackage = createPackage;
|
|
6
8
|
const packages_1 = require("../data/packages");
|
|
7
9
|
const persistent_vars_1 = require("../data/persistent-vars");
|
|
@@ -42,29 +44,148 @@ async function ensureTemplateCache(deps, templateRepo) {
|
|
|
42
44
|
}
|
|
43
45
|
return cachePath;
|
|
44
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Resolve (or create) the Ed25519 publish public key for a package.
|
|
49
|
+
*
|
|
50
|
+
* Idempotent: never rotates an established key. Reuses `existingPublishPublicKey`
|
|
51
|
+
* when present, derives the public key from an existing secret pvar when possible,
|
|
52
|
+
* and only generates a new keypair when both are missing.
|
|
53
|
+
*
|
|
54
|
+
* @throws When a readable secret key does not match an established `publishPublicKey`.
|
|
55
|
+
*/
|
|
56
|
+
async function ensurePackageSigningKey(opts) {
|
|
57
|
+
const { personalContext, packageId, packageName, existingPublishPublicKey } = opts;
|
|
58
|
+
const establishedKey = existingPublishPublicKey?.trim() || "";
|
|
59
|
+
if (personalContext) {
|
|
60
|
+
const keyName = `${package_author_signing_1.PACKAGE_SIGNING_KEY_PREFIX}${packageId}`;
|
|
61
|
+
const existingKey = await (0, persistent_vars_1.PersistentVars)(personalContext).findOne({ name: keyName });
|
|
62
|
+
const rawSecret = existingKey?.value?.value;
|
|
63
|
+
if (typeof rawSecret === "string" && rawSecret.length > 0) {
|
|
64
|
+
try {
|
|
65
|
+
const derivedPublicKey = (0, keys_1.hydrateKeys)(rawSecret).publicKey;
|
|
66
|
+
if (establishedKey && derivedPublicKey !== establishedKey) {
|
|
67
|
+
throw new Error(`Package signing key mismatch for "${packageName}" (${packageId}): ` +
|
|
68
|
+
"stored secret does not match Packages.publishPublicKey. " +
|
|
69
|
+
"Refusing to rotate the established key.");
|
|
70
|
+
}
|
|
71
|
+
return establishedKey || derivedPublicKey;
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
if (err instanceof Error && err.message.includes("signing key mismatch")) {
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
// Secret is likely encrypted at rest — cannot derive here without the user key.
|
|
78
|
+
if (establishedKey) {
|
|
79
|
+
return establishedKey;
|
|
80
|
+
}
|
|
81
|
+
throw new Error(`Package "${packageName}" (${packageId}) has a signing secret pvar but no ` +
|
|
82
|
+
"publishPublicKey, and the secret could not be hydrated. " +
|
|
83
|
+
"Restore publishPublicKey or replace the unreadable key before importing.");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (establishedKey) {
|
|
88
|
+
return establishedKey;
|
|
89
|
+
}
|
|
90
|
+
if (!personalContext) {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
const keys = (0, keys_1.newKeys)();
|
|
94
|
+
const keyName = `${package_author_signing_1.PACKAGE_SIGNING_KEY_PREFIX}${packageId}`;
|
|
95
|
+
await (0, persistent_vars_1.PersistentVars)(personalContext).save({
|
|
96
|
+
persistentVarId: (0, utils_1.deterministicPvarId)(keyName),
|
|
97
|
+
name: keyName,
|
|
98
|
+
scope: "user",
|
|
99
|
+
value: { value: keys.secretKey },
|
|
100
|
+
isSecret: true,
|
|
101
|
+
description: `Signing key for package "${packageName}"`,
|
|
102
|
+
modifiedAt: Date.now(),
|
|
103
|
+
});
|
|
104
|
+
return keys.publicKey;
|
|
105
|
+
}
|
|
106
|
+
async function registerPackageRecord(dataContext, opts) {
|
|
107
|
+
const { packageId, name, description, publishPublicKey, remoteRepo, existing } = opts;
|
|
108
|
+
await (0, packages_1.Packages)(dataContext).signAndSave({
|
|
109
|
+
packageId,
|
|
110
|
+
name,
|
|
111
|
+
description: description || existing?.description || "",
|
|
112
|
+
createdBy: existing?.createdBy || system_ids_1.peersRootUserId,
|
|
113
|
+
disabled: existing?.disabled ?? false,
|
|
114
|
+
remoteRepo: remoteRepo || existing?.remoteRepo,
|
|
115
|
+
activePackageVersionId: existing?.activePackageVersionId,
|
|
116
|
+
versionFollowRange: existing?.versionFollowRange,
|
|
117
|
+
followVersionTags: existing?.followVersionTags,
|
|
118
|
+
updateUrl: existing?.updateUrl,
|
|
119
|
+
publishPublicKey: publishPublicKey || existing?.publishPublicKey || "",
|
|
120
|
+
signature: existing?.signature || "",
|
|
121
|
+
}, { restoreIfDeleted: true, saveAsSnapshot: true });
|
|
122
|
+
}
|
|
123
|
+
async function persistPackageLocalPath(dataContext, deps, packageId, location) {
|
|
124
|
+
const pvar = (0, persistent_vars_1.groupDeviceVar)(`packageLocalPath_${packageId}`, {
|
|
125
|
+
defaultValue: deps.packagesRootDir,
|
|
126
|
+
dataContext,
|
|
127
|
+
});
|
|
128
|
+
await pvar.loadingPromise;
|
|
129
|
+
pvar(location);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Import an existing on-disk Peers package directory into the current data context.
|
|
133
|
+
*
|
|
134
|
+
* Validates the manifest packageId, creates/updates the Packages record, persists
|
|
135
|
+
* `packageLocalPath_*`, provisions a signing key when missing, then installs the
|
|
136
|
+
* local dev bundles.
|
|
137
|
+
*/
|
|
138
|
+
async function importExistingPackage(dataContext, deps, opts) {
|
|
139
|
+
const info = await (0, package_installer_1.getPackageInfo)(deps, opts.location);
|
|
140
|
+
if (!info.packageId) {
|
|
141
|
+
throw new Error(`Directory already exists at ${opts.location} but does not contain a valid Peers package`);
|
|
142
|
+
}
|
|
143
|
+
const packageId = info.packageId;
|
|
144
|
+
const existing = await (0, packages_1.Packages)(dataContext).get(packageId);
|
|
145
|
+
const packageName = opts.displayName || info.name || packageId;
|
|
146
|
+
const publishPublicKey = await ensurePackageSigningKey({
|
|
147
|
+
personalContext: opts.personalContext,
|
|
148
|
+
packageId,
|
|
149
|
+
packageName,
|
|
150
|
+
existingPublishPublicKey: existing?.publishPublicKey,
|
|
151
|
+
});
|
|
152
|
+
await registerPackageRecord(dataContext, {
|
|
153
|
+
packageId,
|
|
154
|
+
name: packageName,
|
|
155
|
+
description: info.description || "",
|
|
156
|
+
publishPublicKey,
|
|
157
|
+
remoteRepo: info.remoteRepo,
|
|
158
|
+
existing,
|
|
159
|
+
});
|
|
160
|
+
await persistPackageLocalPath(dataContext, deps, packageId, opts.location);
|
|
161
|
+
const result = await (0, package_installer_1.installPackageFromBundles)(dataContext, deps, packageId, {
|
|
162
|
+
localPath: opts.location,
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
packageId,
|
|
166
|
+
localPath: opts.location,
|
|
167
|
+
packageVersion: result.packageVersion,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
45
170
|
/**
|
|
46
171
|
* Create a new Peers package from the package template.
|
|
47
172
|
*
|
|
48
173
|
* Clones the template repo, patches IDs and metadata, runs npm install + build,
|
|
49
174
|
* initializes a fresh git repo, then delegates to {@link installPackageFromBundles}
|
|
50
175
|
* to register the dev bundle in the database.
|
|
176
|
+
*
|
|
177
|
+
* When `location` already exists and contains a valid Peers package manifest,
|
|
178
|
+
* imports that directory instead (see {@link importExistingPackage}).
|
|
51
179
|
*/
|
|
52
180
|
async function createPackage(dataContext, deps, opts) {
|
|
53
181
|
const location = opts.location ?? deps.resolvePath(deps.packagesRootDir, nameToFolderSlug(opts.name));
|
|
54
182
|
// If directory already exists, treat as an import rather than a fresh create
|
|
55
183
|
if (await deps.fs.exists(location)) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return {
|
|
62
|
-
packageId: info.packageId,
|
|
63
|
-
localPath: location,
|
|
64
|
-
packageVersion: result.packageVersion,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
throw new Error(`Directory already exists at ${location} but does not contain a valid Peers package`);
|
|
184
|
+
return importExistingPackage(dataContext, deps, {
|
|
185
|
+
location,
|
|
186
|
+
personalContext: opts.personalContext,
|
|
187
|
+
displayName: opts.name,
|
|
188
|
+
});
|
|
68
189
|
}
|
|
69
190
|
// Generate fresh IDs
|
|
70
191
|
const packageId = (0, utils_1.newid)();
|
|
@@ -110,39 +231,18 @@ async function createPackage(dataContext, deps, opts) {
|
|
|
110
231
|
await deps.shell.exec("git init", { cwd: location });
|
|
111
232
|
await deps.shell.exec("git add .", { cwd: location });
|
|
112
233
|
await deps.shell.exec('git commit -m "Initial commit"', { cwd: location });
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
await (0, persistent_vars_1.PersistentVars)(opts.personalContext).save({
|
|
120
|
-
persistentVarId: (0, utils_1.deterministicPvarId)(keyName),
|
|
121
|
-
name: keyName,
|
|
122
|
-
scope: "user",
|
|
123
|
-
value: { value: keys.secretKey },
|
|
124
|
-
isSecret: true,
|
|
125
|
-
description: `Signing key for package "${opts.name}"`,
|
|
126
|
-
modifiedAt: Date.now(),
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
// Register the package record
|
|
130
|
-
await (0, packages_1.Packages)(dataContext).signAndSave({
|
|
234
|
+
const publishPublicKey = await ensurePackageSigningKey({
|
|
235
|
+
personalContext: opts.personalContext,
|
|
236
|
+
packageId,
|
|
237
|
+
packageName: opts.name,
|
|
238
|
+
});
|
|
239
|
+
await registerPackageRecord(dataContext, {
|
|
131
240
|
packageId,
|
|
132
241
|
name: opts.name,
|
|
133
242
|
description: "",
|
|
134
|
-
createdBy: system_ids_1.peersRootUserId,
|
|
135
|
-
disabled: false,
|
|
136
243
|
publishPublicKey,
|
|
137
|
-
signature: "",
|
|
138
|
-
}, { restoreIfDeleted: true, saveAsSnapshot: true });
|
|
139
|
-
// Set the packageLocalPath device var
|
|
140
|
-
const pvar = (0, persistent_vars_1.groupDeviceVar)(`packageLocalPath_${packageId}`, {
|
|
141
|
-
defaultValue: deps.packagesRootDir,
|
|
142
|
-
dataContext,
|
|
143
244
|
});
|
|
144
|
-
await
|
|
145
|
-
pvar(location);
|
|
245
|
+
await persistPackageLocalPath(dataContext, deps, packageId, location);
|
|
146
246
|
// Install the dev bundle
|
|
147
247
|
const result = await (0, package_installer_1.installPackageFromBundles)(dataContext, deps, packageId, {
|
|
148
248
|
localPath: location,
|
|
@@ -3,17 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
const package_creator_1 = require("./package-creator");
|
|
4
4
|
// --- Mocks for SDK internals ---
|
|
5
5
|
const mockSignAndSavePkg = jest.fn();
|
|
6
|
+
const mockGetPkg = jest.fn();
|
|
6
7
|
jest.mock("../data/packages", () => ({
|
|
7
8
|
Packages: () => ({
|
|
8
9
|
signAndSave: mockSignAndSavePkg,
|
|
10
|
+
get: mockGetPkg,
|
|
9
11
|
}),
|
|
10
12
|
}));
|
|
11
13
|
const mockGroupDeviceVar = jest.fn();
|
|
12
14
|
const mockPersistentVarsSave = jest.fn();
|
|
15
|
+
const mockPersistentVarsFindOne = jest.fn();
|
|
13
16
|
jest.mock("../data/persistent-vars", () => ({
|
|
14
17
|
groupDeviceVar: (...args) => mockGroupDeviceVar(...args),
|
|
15
18
|
PersistentVars: () => ({
|
|
16
19
|
save: mockPersistentVarsSave,
|
|
20
|
+
findOne: mockPersistentVarsFindOne,
|
|
17
21
|
}),
|
|
18
22
|
}));
|
|
19
23
|
const mockNewKeys = jest.fn();
|
|
@@ -70,6 +74,13 @@ function mockDataContext() {
|
|
|
70
74
|
// --- Tests ---
|
|
71
75
|
beforeEach(() => {
|
|
72
76
|
jest.clearAllMocks();
|
|
77
|
+
mockGetPkg.mockResolvedValue(undefined);
|
|
78
|
+
mockPersistentVarsFindOne.mockResolvedValue(undefined);
|
|
79
|
+
mockNewKeys.mockReturnValue({
|
|
80
|
+
publicKey: "test-public-key-abc",
|
|
81
|
+
secretKey: "test-secret-key-xyz",
|
|
82
|
+
publicBoxKey: "test-box-key",
|
|
83
|
+
});
|
|
73
84
|
});
|
|
74
85
|
describe("nameToFolderSlug", () => {
|
|
75
86
|
it("converts a simple name", () => {
|
|
@@ -170,16 +181,18 @@ describe("createPackage", () => {
|
|
|
170
181
|
expect(result.localPath).toBe("/packages/my-widget");
|
|
171
182
|
expect(result.packageVersion).toBe(mockPv);
|
|
172
183
|
});
|
|
173
|
-
it("
|
|
184
|
+
it("registers metadata, path, and signing key when importing an existing package directory", async () => {
|
|
174
185
|
const deps = makeMockDeps({});
|
|
175
186
|
const dc = mockDataContext();
|
|
176
|
-
|
|
187
|
+
const personalCtx = mockDataContext();
|
|
177
188
|
deps.fs.exists.mockResolvedValue(true);
|
|
178
|
-
// getPackageInfo returns a valid packageId
|
|
179
189
|
mockGetPackageInfo.mockResolvedValue({
|
|
180
190
|
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
181
191
|
name: "existing-pkg",
|
|
192
|
+
description: "Imported package",
|
|
182
193
|
});
|
|
194
|
+
const pvarSetter = jest.fn();
|
|
195
|
+
mockGroupDeviceVar.mockReturnValue(pvarSetter);
|
|
183
196
|
const mockPv = { packageVersionId: "pv002", packageId: "00mnkoyo7mizav6tseubjzr3a" };
|
|
184
197
|
mockInstallPackageFromBundles.mockResolvedValue({
|
|
185
198
|
packageVersion: mockPv,
|
|
@@ -187,16 +200,67 @@ describe("createPackage", () => {
|
|
|
187
200
|
unchanged: false,
|
|
188
201
|
});
|
|
189
202
|
const result = await (0, package_creator_1.createPackage)(dc, deps, {
|
|
190
|
-
name: "
|
|
203
|
+
name: "Existing Pkg",
|
|
191
204
|
location: "/custom/path",
|
|
205
|
+
personalContext: personalCtx,
|
|
192
206
|
});
|
|
193
|
-
// Should NOT clone
|
|
194
207
|
expect(deps.shell.exec).not.toHaveBeenCalled();
|
|
195
|
-
|
|
208
|
+
expect(mockGetPkg).toHaveBeenCalledWith("00mnkoyo7mizav6tseubjzr3a");
|
|
209
|
+
expect(mockNewKeys).toHaveBeenCalled();
|
|
210
|
+
expect(mockPersistentVarsSave).toHaveBeenCalledWith(expect.objectContaining({
|
|
211
|
+
name: "packageSigningKey_00mnkoyo7mizav6tseubjzr3a",
|
|
212
|
+
isSecret: true,
|
|
213
|
+
value: { value: "test-secret-key-xyz" },
|
|
214
|
+
}));
|
|
215
|
+
expect(mockSignAndSavePkg).toHaveBeenCalledWith(expect.objectContaining({
|
|
216
|
+
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
217
|
+
name: "Existing Pkg",
|
|
218
|
+
description: "Imported package",
|
|
219
|
+
publishPublicKey: "test-public-key-abc",
|
|
220
|
+
}), { restoreIfDeleted: true, saveAsSnapshot: true });
|
|
221
|
+
expect(mockGroupDeviceVar).toHaveBeenCalledWith("packageLocalPath_00mnkoyo7mizav6tseubjzr3a", expect.any(Object));
|
|
222
|
+
expect(pvarSetter).toHaveBeenCalledWith("/custom/path");
|
|
196
223
|
expect(mockInstallPackageFromBundles).toHaveBeenCalledWith(dc, deps, "00mnkoyo7mizav6tseubjzr3a", { localPath: "/custom/path" });
|
|
197
224
|
expect(result.packageId).toBe("00mnkoyo7mizav6tseubjzr3a");
|
|
198
225
|
expect(result.localPath).toBe("/custom/path");
|
|
199
226
|
});
|
|
227
|
+
it("reuses an existing publishPublicKey and does not rotate the signing key on re-import", async () => {
|
|
228
|
+
const deps = makeMockDeps({});
|
|
229
|
+
const dc = mockDataContext();
|
|
230
|
+
const personalCtx = mockDataContext();
|
|
231
|
+
deps.fs.exists.mockResolvedValue(true);
|
|
232
|
+
mockGetPackageInfo.mockResolvedValue({
|
|
233
|
+
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
234
|
+
name: "existing-pkg",
|
|
235
|
+
});
|
|
236
|
+
mockGetPkg.mockResolvedValue({
|
|
237
|
+
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
238
|
+
name: "existing-pkg",
|
|
239
|
+
description: "Old description",
|
|
240
|
+
createdBy: "00m87fbwu96jzf8qnwllly0fd",
|
|
241
|
+
publishPublicKey: "already-established-public-key",
|
|
242
|
+
signature: "sig",
|
|
243
|
+
disabled: false,
|
|
244
|
+
});
|
|
245
|
+
const pvarSetter = jest.fn();
|
|
246
|
+
mockGroupDeviceVar.mockReturnValue(pvarSetter);
|
|
247
|
+
mockInstallPackageFromBundles.mockResolvedValue({
|
|
248
|
+
packageVersion: { packageVersionId: "pv002", packageId: "00mnkoyo7mizav6tseubjzr3a" },
|
|
249
|
+
loaded: undefined,
|
|
250
|
+
unchanged: false,
|
|
251
|
+
});
|
|
252
|
+
await (0, package_creator_1.createPackage)(dc, deps, {
|
|
253
|
+
name: "existing-pkg",
|
|
254
|
+
location: "/custom/path",
|
|
255
|
+
personalContext: personalCtx,
|
|
256
|
+
});
|
|
257
|
+
expect(mockNewKeys).not.toHaveBeenCalled();
|
|
258
|
+
expect(mockPersistentVarsSave).not.toHaveBeenCalled();
|
|
259
|
+
expect(mockSignAndSavePkg).toHaveBeenCalledWith(expect.objectContaining({
|
|
260
|
+
publishPublicKey: "already-established-public-key",
|
|
261
|
+
createdBy: "00m87fbwu96jzf8qnwllly0fd",
|
|
262
|
+
}), expect.any(Object));
|
|
263
|
+
});
|
|
200
264
|
it("throws when directory exists but has no valid package", async () => {
|
|
201
265
|
const deps = makeMockDeps({});
|
|
202
266
|
const dc = mockDataContext();
|
|
@@ -268,11 +332,6 @@ describe("createPackage", () => {
|
|
|
268
332
|
loaded: undefined,
|
|
269
333
|
unchanged: false,
|
|
270
334
|
});
|
|
271
|
-
mockNewKeys.mockReturnValue({
|
|
272
|
-
publicKey: "test-public-key-abc",
|
|
273
|
-
secretKey: "test-secret-key-xyz",
|
|
274
|
-
publicBoxKey: "test-box-key",
|
|
275
|
-
});
|
|
276
335
|
await (0, package_creator_1.createPackage)(dc, deps, {
|
|
277
336
|
name: "Signed Pkg",
|
|
278
337
|
personalContext: personalCtx,
|
|
@@ -319,6 +378,49 @@ describe("createPackage", () => {
|
|
|
319
378
|
}), expect.any(Object));
|
|
320
379
|
});
|
|
321
380
|
});
|
|
381
|
+
describe("ensurePackageSigningKey", () => {
|
|
382
|
+
it("generates a key once when none exists", async () => {
|
|
383
|
+
const personalCtx = mockDataContext();
|
|
384
|
+
const publicKey = await (0, package_creator_1.ensurePackageSigningKey)({
|
|
385
|
+
personalContext: personalCtx,
|
|
386
|
+
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
387
|
+
packageName: "Test Pkg",
|
|
388
|
+
});
|
|
389
|
+
expect(publicKey).toBe("test-public-key-abc");
|
|
390
|
+
expect(mockNewKeys).toHaveBeenCalledTimes(1);
|
|
391
|
+
expect(mockPersistentVarsSave).toHaveBeenCalledTimes(1);
|
|
392
|
+
});
|
|
393
|
+
it("reuses an established publishPublicKey without generating a new key", async () => {
|
|
394
|
+
const personalCtx = mockDataContext();
|
|
395
|
+
const publicKey = await (0, package_creator_1.ensurePackageSigningKey)({
|
|
396
|
+
personalContext: personalCtx,
|
|
397
|
+
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
398
|
+
packageName: "Test Pkg",
|
|
399
|
+
existingPublishPublicKey: "established-key",
|
|
400
|
+
});
|
|
401
|
+
expect(publicKey).toBe("established-key");
|
|
402
|
+
expect(mockNewKeys).not.toHaveBeenCalled();
|
|
403
|
+
expect(mockPersistentVarsSave).not.toHaveBeenCalled();
|
|
404
|
+
});
|
|
405
|
+
it("throws on an unrecoverable public/private key mismatch", async () => {
|
|
406
|
+
const { newKeys: realNewKeys } = jest.requireActual("../keys");
|
|
407
|
+
const realKeys = realNewKeys();
|
|
408
|
+
const otherKeys = realNewKeys();
|
|
409
|
+
const personalCtx = mockDataContext();
|
|
410
|
+
mockPersistentVarsFindOne.mockResolvedValue({
|
|
411
|
+
name: "packageSigningKey_00mnkoyo7mizav6tseubjzr3a",
|
|
412
|
+
value: { value: realKeys.secretKey },
|
|
413
|
+
isSecret: true,
|
|
414
|
+
});
|
|
415
|
+
await expect((0, package_creator_1.ensurePackageSigningKey)({
|
|
416
|
+
personalContext: personalCtx,
|
|
417
|
+
packageId: "00mnkoyo7mizav6tseubjzr3a",
|
|
418
|
+
packageName: "Test Pkg",
|
|
419
|
+
existingPublishPublicKey: otherKeys.publicKey,
|
|
420
|
+
})).rejects.toThrow("signing key mismatch");
|
|
421
|
+
expect(mockNewKeys).not.toHaveBeenCalled();
|
|
422
|
+
});
|
|
423
|
+
});
|
|
322
424
|
describe("ensureTemplateCache", () => {
|
|
323
425
|
it("clones template when cache directory does not exist", async () => {
|
|
324
426
|
const deps = makeMockDeps({});
|
package/dist/rpc-types.d.ts
CHANGED
|
@@ -174,23 +174,6 @@ export declare const rpcServerCalls: {
|
|
|
174
174
|
success: boolean;
|
|
175
175
|
error?: string;
|
|
176
176
|
}>;
|
|
177
|
-
voiceGetState: () => Promise<{
|
|
178
|
-
state: "disabled" | "idle" | "listening" | "recording" | "processing" | "speaking";
|
|
179
|
-
keyError?: "refused" | "invalid" | "limit" | "throttled";
|
|
180
|
-
}>;
|
|
181
|
-
voiceStartRecording: () => Promise<void>;
|
|
182
|
-
voiceStopRecording: () => Promise<void>;
|
|
183
|
-
voiceCancelRecording: () => Promise<void>;
|
|
184
|
-
voiceSetTargetChannel: (channelId: string) => Promise<void>;
|
|
185
|
-
voiceGetAudioDevices: () => Promise<string[]>;
|
|
186
|
-
voiceTestTTS: (text: string) => Promise<void>;
|
|
187
|
-
voiceNotifyPlaybackComplete: () => Promise<void>;
|
|
188
|
-
voiceStopPlayback: () => Promise<void>;
|
|
189
|
-
voiceGetThreadId: () => Promise<string | null>;
|
|
190
|
-
voiceSetThreadId: (threadId: string | null) => Promise<void>;
|
|
191
|
-
voiceNotifyTextActivity: () => Promise<void>;
|
|
192
|
-
voiceDisable: () => Promise<void>;
|
|
193
|
-
voiceEnable: () => Promise<void>;
|
|
194
177
|
flushDatabases: () => Promise<void>;
|
|
195
178
|
subscribeEvents: (names: string[]) => Promise<void>;
|
|
196
179
|
unsubscribeEvents: (names: string[]) => Promise<void>;
|
package/dist/rpc-types.js
CHANGED
|
@@ -84,21 +84,6 @@ exports.rpcServerCalls = {
|
|
|
84
84
|
uiClick: rpcStub("uiClick"),
|
|
85
85
|
uiSet: rpcStub("uiSet"),
|
|
86
86
|
uiScroll: rpcStub("uiScroll"),
|
|
87
|
-
// Voice control (settings managed via voiceSettings pvar, keys via secret pvars)
|
|
88
|
-
voiceGetState: rpcStub("voiceGetState"),
|
|
89
|
-
voiceStartRecording: rpcStub("voiceStartRecording"),
|
|
90
|
-
voiceStopRecording: rpcStub("voiceStopRecording"),
|
|
91
|
-
voiceCancelRecording: rpcStub("voiceCancelRecording"),
|
|
92
|
-
voiceSetTargetChannel: rpcStub("voiceSetTargetChannel"),
|
|
93
|
-
voiceGetAudioDevices: rpcStub("voiceGetAudioDevices"),
|
|
94
|
-
voiceTestTTS: rpcStub("voiceTestTTS"),
|
|
95
|
-
voiceNotifyPlaybackComplete: rpcStub("voiceNotifyPlaybackComplete"),
|
|
96
|
-
voiceStopPlayback: rpcStub("voiceStopPlayback"),
|
|
97
|
-
voiceGetThreadId: rpcStub("voiceGetThreadId"),
|
|
98
|
-
voiceSetThreadId: rpcStub("voiceSetThreadId"),
|
|
99
|
-
voiceNotifyTextActivity: rpcStub("voiceNotifyTextActivity"),
|
|
100
|
-
voiceDisable: rpcStub("voiceDisable"),
|
|
101
|
-
voiceEnable: rpcStub("voiceEnable"),
|
|
102
87
|
// Flush all in-memory database snapshots to durable storage (IndexedDB in PWA).
|
|
103
88
|
// No-op on Electron where better-sqlite3 writes are synchronous to disk.
|
|
104
89
|
flushDatabases: (async () => { }),
|
package/package.json
CHANGED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import type { DataContext } from "../context/data-context";
|
|
3
|
-
export declare const voiceMessageSchema: z.ZodObject<{
|
|
4
|
-
voiceMessageId: z.ZodDefault<z.ZodEffects<z.ZodString, string, string>>;
|
|
5
|
-
sessionId: z.ZodString;
|
|
6
|
-
role: z.ZodEnum<["user", "assistant"]>;
|
|
7
|
-
content: z.ZodString;
|
|
8
|
-
threadId: z.ZodOptional<z.ZodString>;
|
|
9
|
-
createdAt: z.ZodDefault<z.ZodDate>;
|
|
10
|
-
}, "strip", z.ZodTypeAny, {
|
|
11
|
-
content: string;
|
|
12
|
-
role: "user" | "assistant";
|
|
13
|
-
sessionId: string;
|
|
14
|
-
createdAt: Date;
|
|
15
|
-
voiceMessageId: string;
|
|
16
|
-
threadId?: string | undefined;
|
|
17
|
-
}, {
|
|
18
|
-
content: string;
|
|
19
|
-
role: "user" | "assistant";
|
|
20
|
-
sessionId: string;
|
|
21
|
-
createdAt?: Date | undefined;
|
|
22
|
-
voiceMessageId?: string | undefined;
|
|
23
|
-
threadId?: string | undefined;
|
|
24
|
-
}>;
|
|
25
|
-
export type IVoiceMessage = z.infer<typeof voiceMessageSchema>;
|
|
26
|
-
export declare function VoiceMessages(dataContext?: DataContext): import("..").Table<{
|
|
27
|
-
content: string;
|
|
28
|
-
role: "user" | "assistant";
|
|
29
|
-
sessionId: string;
|
|
30
|
-
createdAt: Date;
|
|
31
|
-
voiceMessageId: string;
|
|
32
|
-
threadId?: string | undefined;
|
|
33
|
-
}>;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.voiceMessageSchema = void 0;
|
|
4
|
-
exports.VoiceMessages = VoiceMessages;
|
|
5
|
-
const zod_1 = require("zod");
|
|
6
|
-
const user_context_singleton_1 = require("../context/user-context-singleton");
|
|
7
|
-
const zod_types_1 = require("../types/zod-types");
|
|
8
|
-
const utils_1 = require("../utils");
|
|
9
|
-
const table_definitions_system_1 = require("./orm/table-definitions.system");
|
|
10
|
-
const types_1 = require("./orm/types");
|
|
11
|
-
exports.voiceMessageSchema = zod_1.z.object({
|
|
12
|
-
voiceMessageId: zod_types_1.zodPeerId.default(() => (0, utils_1.newid)()),
|
|
13
|
-
sessionId: zod_1.z.string().describe("Groups messages belonging to one voice session"),
|
|
14
|
-
role: zod_1.z.enum(["user", "assistant"]),
|
|
15
|
-
content: zod_1.z.string(),
|
|
16
|
-
threadId: zod_1.z
|
|
17
|
-
.string()
|
|
18
|
-
.optional()
|
|
19
|
-
.describe("messageId of the channel thread created when an action was dispatched to the background agent"),
|
|
20
|
-
createdAt: zod_1.z.date().default(() => new Date()),
|
|
21
|
-
});
|
|
22
|
-
const metaData = {
|
|
23
|
-
name: "VoiceMessages",
|
|
24
|
-
description: "Voice conversation history for the self-contained voice assistant",
|
|
25
|
-
primaryKeyName: "voiceMessageId",
|
|
26
|
-
fields: (0, types_1.schemaToFields)(exports.voiceMessageSchema),
|
|
27
|
-
localOnly: true,
|
|
28
|
-
indexes: [{ fields: ["sessionId"] }, { fields: ["sessionId", "createdAt"] }],
|
|
29
|
-
};
|
|
30
|
-
(0, table_definitions_system_1.registerSystemTableDefinition)(metaData, exports.voiceMessageSchema);
|
|
31
|
-
function VoiceMessages(dataContext) {
|
|
32
|
-
return (0, user_context_singleton_1.getTableContainer)(dataContext).getTable(metaData, exports.voiceMessageSchema);
|
|
33
|
-
}
|