@openacp/cli 2026.410.2 → 2026.413.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/dist/{channel-CFMUPzvH.d.ts → channel-Dg1nGCYa.d.ts} +29 -1
- package/dist/cli.js +1324 -135
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +250 -22
- package/dist/index.js +322 -34
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1244,11 +1244,11 @@ mockServices.context(overrides?) // buildContext, registerProvider
|
|
|
1244
1244
|
ctx.registerCommand({
|
|
1245
1245
|
name: 'mycommand',
|
|
1246
1246
|
description: 'Does something useful',
|
|
1247
|
-
usage: '
|
|
1247
|
+
usage: '[arg]',
|
|
1248
1248
|
category: 'plugin',
|
|
1249
1249
|
async handler(args) {
|
|
1250
1250
|
const input = args.raw.trim()
|
|
1251
|
-
if (!input) return { type: 'error', message: 'Usage: /mycommand
|
|
1251
|
+
if (!input) return { type: 'error', message: 'Usage: /mycommand [arg]' }
|
|
1252
1252
|
return { type: 'text', text: \\\`Result: \\\${input}\\\` }
|
|
1253
1253
|
},
|
|
1254
1254
|
})
|
|
@@ -1811,6 +1811,8 @@ var init_events = __esm({
|
|
|
1811
1811
|
TURN_START: "turn:start",
|
|
1812
1812
|
/** Turn ended (always fires, even on error) — read-only, fire-and-forget. */
|
|
1813
1813
|
TURN_END: "turn:end",
|
|
1814
|
+
/** After a turn completes — full assembled agent text, read-only, fire-and-forget. */
|
|
1815
|
+
AGENT_AFTER_TURN: "agent:afterTurn",
|
|
1814
1816
|
// --- Session lifecycle ---
|
|
1815
1817
|
/** Before a new session is created — modifiable, can block. */
|
|
1816
1818
|
SESSION_BEFORE_CREATE: "session:beforeCreate",
|
|
@@ -1888,7 +1890,22 @@ var init_events = __esm({
|
|
|
1888
1890
|
PLUGIN_UNLOADED: "plugin:unloaded",
|
|
1889
1891
|
// --- Usage ---
|
|
1890
1892
|
/** Fired when a token usage record is captured (consumed by usage plugin). */
|
|
1891
|
-
USAGE_RECORDED: "usage:recorded"
|
|
1893
|
+
USAGE_RECORDED: "usage:recorded",
|
|
1894
|
+
// --- Identity lifecycle ---
|
|
1895
|
+
/** Fired when a new user+identity record is created. */
|
|
1896
|
+
IDENTITY_CREATED: "identity:created",
|
|
1897
|
+
/** Fired when user profile fields change. */
|
|
1898
|
+
IDENTITY_UPDATED: "identity:updated",
|
|
1899
|
+
/** Fired when two identities are linked (same person). */
|
|
1900
|
+
IDENTITY_LINKED: "identity:linked",
|
|
1901
|
+
/** Fired when an identity is unlinked into a new user. */
|
|
1902
|
+
IDENTITY_UNLINKED: "identity:unlinked",
|
|
1903
|
+
/** Fired when two user records are merged during a link operation. */
|
|
1904
|
+
IDENTITY_USER_MERGED: "identity:userMerged",
|
|
1905
|
+
/** Fired when a user's role changes. */
|
|
1906
|
+
IDENTITY_ROLE_CHANGED: "identity:roleChanged",
|
|
1907
|
+
/** Fired when a user is seen (throttled). */
|
|
1908
|
+
IDENTITY_SEEN: "identity:seen"
|
|
1892
1909
|
};
|
|
1893
1910
|
SessionEv = {
|
|
1894
1911
|
/** Agent produced an event (text, tool_call, etc.) during a turn. */
|
|
@@ -2035,6 +2052,704 @@ var init_security = __esm({
|
|
|
2035
2052
|
}
|
|
2036
2053
|
});
|
|
2037
2054
|
|
|
2055
|
+
// src/plugins/identity/types.ts
|
|
2056
|
+
function formatIdentityId(source, platformId) {
|
|
2057
|
+
return `${source}:${platformId}`;
|
|
2058
|
+
}
|
|
2059
|
+
var init_types = __esm({
|
|
2060
|
+
"src/plugins/identity/types.ts"() {
|
|
2061
|
+
"use strict";
|
|
2062
|
+
}
|
|
2063
|
+
});
|
|
2064
|
+
|
|
2065
|
+
// src/plugins/identity/identity-service.ts
|
|
2066
|
+
import { nanoid } from "nanoid";
|
|
2067
|
+
var IdentityServiceImpl;
|
|
2068
|
+
var init_identity_service = __esm({
|
|
2069
|
+
"src/plugins/identity/identity-service.ts"() {
|
|
2070
|
+
"use strict";
|
|
2071
|
+
init_types();
|
|
2072
|
+
IdentityServiceImpl = class {
|
|
2073
|
+
/**
|
|
2074
|
+
* @param store - Persistence layer for user/identity records and indexes.
|
|
2075
|
+
* @param emitEvent - Callback to publish events on the EventBus.
|
|
2076
|
+
* @param getSessionsForUser - Optional function to look up sessions for a userId.
|
|
2077
|
+
*/
|
|
2078
|
+
constructor(store, emitEvent, getSessionsForUser) {
|
|
2079
|
+
this.store = store;
|
|
2080
|
+
this.emitEvent = emitEvent;
|
|
2081
|
+
this.getSessionsForUser = getSessionsForUser;
|
|
2082
|
+
}
|
|
2083
|
+
registeredSources = /* @__PURE__ */ new Set();
|
|
2084
|
+
// ─── Lookups ───
|
|
2085
|
+
async getUser(userId) {
|
|
2086
|
+
return this.store.getUser(userId);
|
|
2087
|
+
}
|
|
2088
|
+
async getUserByUsername(username) {
|
|
2089
|
+
const userId = await this.store.getUserIdByUsername(username);
|
|
2090
|
+
if (!userId) return void 0;
|
|
2091
|
+
return this.store.getUser(userId);
|
|
2092
|
+
}
|
|
2093
|
+
async getIdentity(identityId) {
|
|
2094
|
+
return this.store.getIdentity(identityId);
|
|
2095
|
+
}
|
|
2096
|
+
async getUserByIdentity(identityId) {
|
|
2097
|
+
const identity = await this.store.getIdentity(identityId);
|
|
2098
|
+
if (!identity) return void 0;
|
|
2099
|
+
return this.store.getUser(identity.userId);
|
|
2100
|
+
}
|
|
2101
|
+
async getIdentitiesFor(userId) {
|
|
2102
|
+
return this.store.getIdentitiesForUser(userId);
|
|
2103
|
+
}
|
|
2104
|
+
async listUsers(filter) {
|
|
2105
|
+
return this.store.listUsers(filter);
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Case-insensitive substring search across displayName, username, and platform
|
|
2109
|
+
* usernames. Designed for admin tooling, not high-frequency user-facing paths.
|
|
2110
|
+
*/
|
|
2111
|
+
async searchUsers(query) {
|
|
2112
|
+
const all = await this.store.listUsers();
|
|
2113
|
+
const q = query.toLowerCase();
|
|
2114
|
+
const matched = [];
|
|
2115
|
+
for (const user of all) {
|
|
2116
|
+
const nameMatch = user.displayName.toLowerCase().includes(q) || user.username && user.username.toLowerCase().includes(q);
|
|
2117
|
+
if (nameMatch) {
|
|
2118
|
+
matched.push(user);
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
const identities = await this.store.getIdentitiesForUser(user.userId);
|
|
2122
|
+
const platformMatch = identities.some(
|
|
2123
|
+
(id) => id.platformUsername && id.platformUsername.toLowerCase().includes(q)
|
|
2124
|
+
);
|
|
2125
|
+
if (platformMatch) matched.push(user);
|
|
2126
|
+
}
|
|
2127
|
+
return matched;
|
|
2128
|
+
}
|
|
2129
|
+
async getSessionsFor(userId) {
|
|
2130
|
+
if (!this.getSessionsForUser) return [];
|
|
2131
|
+
return this.getSessionsForUser(userId);
|
|
2132
|
+
}
|
|
2133
|
+
// ─── Mutations ───
|
|
2134
|
+
/**
|
|
2135
|
+
* Creates a user + identity pair atomically.
|
|
2136
|
+
* The first ever user in the system is auto-promoted to admin — this ensures
|
|
2137
|
+
* there is always at least one admin when bootstrapping a fresh instance.
|
|
2138
|
+
*/
|
|
2139
|
+
async createUserWithIdentity(data) {
|
|
2140
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2141
|
+
const userId = `u_${nanoid(12)}`;
|
|
2142
|
+
const identityId = formatIdentityId(data.source, data.platformId);
|
|
2143
|
+
const count = await this.store.getUserCount();
|
|
2144
|
+
const role = count === 0 ? "admin" : data.role ?? "member";
|
|
2145
|
+
const user = {
|
|
2146
|
+
userId,
|
|
2147
|
+
displayName: data.displayName,
|
|
2148
|
+
username: data.username,
|
|
2149
|
+
role,
|
|
2150
|
+
identities: [identityId],
|
|
2151
|
+
pluginData: {},
|
|
2152
|
+
createdAt: now,
|
|
2153
|
+
updatedAt: now,
|
|
2154
|
+
lastSeenAt: now
|
|
2155
|
+
};
|
|
2156
|
+
const identity = {
|
|
2157
|
+
identityId,
|
|
2158
|
+
userId,
|
|
2159
|
+
source: data.source,
|
|
2160
|
+
platformId: data.platformId,
|
|
2161
|
+
platformUsername: data.platformUsername,
|
|
2162
|
+
platformDisplayName: data.platformDisplayName,
|
|
2163
|
+
createdAt: now,
|
|
2164
|
+
updatedAt: now
|
|
2165
|
+
};
|
|
2166
|
+
await this.store.putUser(user);
|
|
2167
|
+
await this.store.putIdentity(identity);
|
|
2168
|
+
await this.store.setSourceIndex(data.source, data.platformId, identityId);
|
|
2169
|
+
if (data.username) {
|
|
2170
|
+
await this.store.setUsernameIndex(data.username, userId);
|
|
2171
|
+
}
|
|
2172
|
+
this.emitEvent("identity:created", { userId, identityId, source: data.source, displayName: data.displayName });
|
|
2173
|
+
return { user, identity };
|
|
2174
|
+
}
|
|
2175
|
+
async updateUser(userId, changes) {
|
|
2176
|
+
const user = await this.store.getUser(userId);
|
|
2177
|
+
if (!user) throw new Error(`User not found: ${userId}`);
|
|
2178
|
+
if (changes.username !== void 0 && changes.username !== user.username) {
|
|
2179
|
+
if (changes.username) {
|
|
2180
|
+
const existingId = await this.store.getUserIdByUsername(changes.username);
|
|
2181
|
+
if (existingId && existingId !== userId) {
|
|
2182
|
+
throw new Error(`Username already taken: ${changes.username}`);
|
|
2183
|
+
}
|
|
2184
|
+
await this.store.setUsernameIndex(changes.username, userId);
|
|
2185
|
+
}
|
|
2186
|
+
if (user.username) {
|
|
2187
|
+
await this.store.deleteUsernameIndex(user.username);
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
const updated = {
|
|
2191
|
+
...user,
|
|
2192
|
+
...changes,
|
|
2193
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2194
|
+
};
|
|
2195
|
+
await this.store.putUser(updated);
|
|
2196
|
+
this.emitEvent("identity:updated", { userId, changes: Object.keys(changes) });
|
|
2197
|
+
return updated;
|
|
2198
|
+
}
|
|
2199
|
+
async setRole(userId, role) {
|
|
2200
|
+
const user = await this.store.getUser(userId);
|
|
2201
|
+
if (!user) throw new Error(`User not found: ${userId}`);
|
|
2202
|
+
const oldRole = user.role;
|
|
2203
|
+
await this.store.putUser({ ...user, role, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2204
|
+
this.emitEvent("identity:roleChanged", { userId, oldRole, newRole: role });
|
|
2205
|
+
}
|
|
2206
|
+
async createIdentity(userId, identity) {
|
|
2207
|
+
const user = await this.store.getUser(userId);
|
|
2208
|
+
if (!user) throw new Error(`User not found: ${userId}`);
|
|
2209
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2210
|
+
const identityId = formatIdentityId(identity.source, identity.platformId);
|
|
2211
|
+
const record = {
|
|
2212
|
+
identityId,
|
|
2213
|
+
userId,
|
|
2214
|
+
source: identity.source,
|
|
2215
|
+
platformId: identity.platformId,
|
|
2216
|
+
platformUsername: identity.platformUsername,
|
|
2217
|
+
platformDisplayName: identity.platformDisplayName,
|
|
2218
|
+
createdAt: now,
|
|
2219
|
+
updatedAt: now
|
|
2220
|
+
};
|
|
2221
|
+
await this.store.putIdentity(record);
|
|
2222
|
+
await this.store.setSourceIndex(identity.source, identity.platformId, identityId);
|
|
2223
|
+
const updatedUser = {
|
|
2224
|
+
...user,
|
|
2225
|
+
identities: [...user.identities, identityId],
|
|
2226
|
+
updatedAt: now
|
|
2227
|
+
};
|
|
2228
|
+
await this.store.putUser(updatedUser);
|
|
2229
|
+
return record;
|
|
2230
|
+
}
|
|
2231
|
+
/**
|
|
2232
|
+
* Links two identities into a single user.
|
|
2233
|
+
*
|
|
2234
|
+
* When identities belong to different users, the younger (more recently created)
|
|
2235
|
+
* user is merged into the older one. We keep the older user as the canonical
|
|
2236
|
+
* record because it likely has more history, sessions, and plugin data.
|
|
2237
|
+
*
|
|
2238
|
+
* Merge strategy for pluginData: per-namespace, the winning user's data takes
|
|
2239
|
+
* precedence. The younger user's data only fills in missing namespaces.
|
|
2240
|
+
*/
|
|
2241
|
+
async link(identityIdA, identityIdB) {
|
|
2242
|
+
const identityA = await this.store.getIdentity(identityIdA);
|
|
2243
|
+
const identityB = await this.store.getIdentity(identityIdB);
|
|
2244
|
+
if (!identityA) throw new Error(`Identity not found: ${identityIdA}`);
|
|
2245
|
+
if (!identityB) throw new Error(`Identity not found: ${identityIdB}`);
|
|
2246
|
+
if (identityA.userId === identityB.userId) return;
|
|
2247
|
+
const userA = await this.store.getUser(identityA.userId);
|
|
2248
|
+
const userB = await this.store.getUser(identityB.userId);
|
|
2249
|
+
if (!userA) throw new Error(`User not found: ${identityA.userId}`);
|
|
2250
|
+
if (!userB) throw new Error(`User not found: ${identityB.userId}`);
|
|
2251
|
+
const [keep, merge] = userA.createdAt <= userB.createdAt ? [userA, userB] : [userB, userA];
|
|
2252
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2253
|
+
for (const identityId of merge.identities) {
|
|
2254
|
+
const identity = await this.store.getIdentity(identityId);
|
|
2255
|
+
if (!identity) continue;
|
|
2256
|
+
const updated = { ...identity, userId: keep.userId, updatedAt: now };
|
|
2257
|
+
await this.store.putIdentity(updated);
|
|
2258
|
+
}
|
|
2259
|
+
const mergedPluginData = { ...merge.pluginData };
|
|
2260
|
+
for (const [ns, nsData] of Object.entries(keep.pluginData)) {
|
|
2261
|
+
mergedPluginData[ns] = nsData;
|
|
2262
|
+
}
|
|
2263
|
+
if (merge.username) {
|
|
2264
|
+
await this.store.deleteUsernameIndex(merge.username);
|
|
2265
|
+
}
|
|
2266
|
+
const updatedKeep = {
|
|
2267
|
+
...keep,
|
|
2268
|
+
identities: [.../* @__PURE__ */ new Set([...keep.identities, ...merge.identities])],
|
|
2269
|
+
pluginData: mergedPluginData,
|
|
2270
|
+
updatedAt: now
|
|
2271
|
+
};
|
|
2272
|
+
await this.store.putUser(updatedKeep);
|
|
2273
|
+
await this.store.deleteUser(merge.userId);
|
|
2274
|
+
const linkedIdentityId = identityA.userId === merge.userId ? identityIdA : identityIdB;
|
|
2275
|
+
this.emitEvent("identity:linked", { userId: keep.userId, identityId: linkedIdentityId, linkedFrom: merge.userId });
|
|
2276
|
+
this.emitEvent("identity:userMerged", {
|
|
2277
|
+
keptUserId: keep.userId,
|
|
2278
|
+
mergedUserId: merge.userId,
|
|
2279
|
+
movedIdentities: merge.identities
|
|
2280
|
+
});
|
|
2281
|
+
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Separates an identity from its user into a new standalone account.
|
|
2284
|
+
* Throws if it's the user's last identity — unlinking would produce a
|
|
2285
|
+
* ghost user with no way to authenticate.
|
|
2286
|
+
*/
|
|
2287
|
+
async unlink(identityId) {
|
|
2288
|
+
const identity = await this.store.getIdentity(identityId);
|
|
2289
|
+
if (!identity) throw new Error(`Identity not found: ${identityId}`);
|
|
2290
|
+
const user = await this.store.getUser(identity.userId);
|
|
2291
|
+
if (!user) throw new Error(`User not found: ${identity.userId}`);
|
|
2292
|
+
if (user.identities.length <= 1) {
|
|
2293
|
+
throw new Error(`Cannot unlink the last identity from user ${identity.userId}`);
|
|
2294
|
+
}
|
|
2295
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2296
|
+
const newUserId = `u_${nanoid(12)}`;
|
|
2297
|
+
const newUser = {
|
|
2298
|
+
userId: newUserId,
|
|
2299
|
+
displayName: identity.platformDisplayName ?? identity.platformUsername ?? "User",
|
|
2300
|
+
role: "member",
|
|
2301
|
+
identities: [identityId],
|
|
2302
|
+
pluginData: {},
|
|
2303
|
+
createdAt: now,
|
|
2304
|
+
updatedAt: now,
|
|
2305
|
+
lastSeenAt: now
|
|
2306
|
+
};
|
|
2307
|
+
await this.store.putUser(newUser);
|
|
2308
|
+
await this.store.putIdentity({ ...identity, userId: newUserId, updatedAt: now });
|
|
2309
|
+
const updatedUser = {
|
|
2310
|
+
...user,
|
|
2311
|
+
identities: user.identities.filter((id) => id !== identityId),
|
|
2312
|
+
updatedAt: now
|
|
2313
|
+
};
|
|
2314
|
+
await this.store.putUser(updatedUser);
|
|
2315
|
+
this.emitEvent("identity:unlinked", {
|
|
2316
|
+
userId: user.userId,
|
|
2317
|
+
identityId,
|
|
2318
|
+
newUserId
|
|
2319
|
+
});
|
|
2320
|
+
}
|
|
2321
|
+
// ─── Plugin data ───
|
|
2322
|
+
async setPluginData(userId, pluginName, key, value) {
|
|
2323
|
+
const user = await this.store.getUser(userId);
|
|
2324
|
+
if (!user) throw new Error(`User not found: ${userId}`);
|
|
2325
|
+
const pluginData = { ...user.pluginData };
|
|
2326
|
+
pluginData[pluginName] = { ...pluginData[pluginName] ?? {}, [key]: value };
|
|
2327
|
+
await this.store.putUser({ ...user, pluginData, updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2328
|
+
}
|
|
2329
|
+
async getPluginData(userId, pluginName, key) {
|
|
2330
|
+
const user = await this.store.getUser(userId);
|
|
2331
|
+
if (!user) return void 0;
|
|
2332
|
+
return user.pluginData[pluginName]?.[key];
|
|
2333
|
+
}
|
|
2334
|
+
// ─── Source registry ───
|
|
2335
|
+
registerSource(source) {
|
|
2336
|
+
this.registeredSources.add(source);
|
|
2337
|
+
}
|
|
2338
|
+
/**
|
|
2339
|
+
* Resolves a username mention to platform-specific info for the given source.
|
|
2340
|
+
* Finds the user by username, then scans their identities for the matching source.
|
|
2341
|
+
* Returns found=false when no user or no identity for that source exists.
|
|
2342
|
+
*/
|
|
2343
|
+
async resolveCanonicalMention(username, source) {
|
|
2344
|
+
const user = await this.getUserByUsername(username);
|
|
2345
|
+
if (!user) return { found: false };
|
|
2346
|
+
const identities = await this.store.getIdentitiesForUser(user.userId);
|
|
2347
|
+
const sourceIdentity = identities.find((id) => id.source === source);
|
|
2348
|
+
if (!sourceIdentity) return { found: false };
|
|
2349
|
+
return {
|
|
2350
|
+
found: true,
|
|
2351
|
+
platformId: sourceIdentity.platformId,
|
|
2352
|
+
platformUsername: sourceIdentity.platformUsername
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
async getUserCount() {
|
|
2356
|
+
return this.store.getUserCount();
|
|
2357
|
+
}
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
});
|
|
2361
|
+
|
|
2362
|
+
// src/plugins/identity/store/kv-identity-store.ts
|
|
2363
|
+
var KvIdentityStore;
|
|
2364
|
+
var init_kv_identity_store = __esm({
|
|
2365
|
+
"src/plugins/identity/store/kv-identity-store.ts"() {
|
|
2366
|
+
"use strict";
|
|
2367
|
+
KvIdentityStore = class {
|
|
2368
|
+
constructor(storage) {
|
|
2369
|
+
this.storage = storage;
|
|
2370
|
+
}
|
|
2371
|
+
// === User CRUD ===
|
|
2372
|
+
async getUser(userId) {
|
|
2373
|
+
return this.storage.get(`users/${userId}`);
|
|
2374
|
+
}
|
|
2375
|
+
async putUser(record) {
|
|
2376
|
+
await this.storage.set(`users/${record.userId}`, record);
|
|
2377
|
+
}
|
|
2378
|
+
async deleteUser(userId) {
|
|
2379
|
+
await this.storage.delete(`users/${userId}`);
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Lists all users, optionally filtered by role or source.
|
|
2383
|
+
* Filtering by source requires scanning all identity records for the user,
|
|
2384
|
+
* which is acceptable given the expected user count (hundreds, not millions).
|
|
2385
|
+
*/
|
|
2386
|
+
async listUsers(filter) {
|
|
2387
|
+
const keys = await this.storage.keys("users/");
|
|
2388
|
+
const users = [];
|
|
2389
|
+
for (const key of keys) {
|
|
2390
|
+
const user = await this.storage.get(key);
|
|
2391
|
+
if (!user) continue;
|
|
2392
|
+
if (filter?.role && user.role !== filter.role) continue;
|
|
2393
|
+
if (filter?.source) {
|
|
2394
|
+
const hasSource = user.identities.some((id) => id.startsWith(`${filter.source}:`));
|
|
2395
|
+
if (!hasSource) continue;
|
|
2396
|
+
}
|
|
2397
|
+
users.push(user);
|
|
2398
|
+
}
|
|
2399
|
+
return users;
|
|
2400
|
+
}
|
|
2401
|
+
// === Identity CRUD ===
|
|
2402
|
+
async getIdentity(identityId) {
|
|
2403
|
+
return this.storage.get(`identities/${identityId}`);
|
|
2404
|
+
}
|
|
2405
|
+
async putIdentity(record) {
|
|
2406
|
+
await this.storage.set(`identities/${record.identityId}`, record);
|
|
2407
|
+
}
|
|
2408
|
+
async deleteIdentity(identityId) {
|
|
2409
|
+
await this.storage.delete(`identities/${identityId}`);
|
|
2410
|
+
}
|
|
2411
|
+
/**
|
|
2412
|
+
* Fetches all identity records for a user by scanning their identities array.
|
|
2413
|
+
* Avoids a full table scan by leveraging the user record as a secondary index.
|
|
2414
|
+
*/
|
|
2415
|
+
async getIdentitiesForUser(userId) {
|
|
2416
|
+
const user = await this.getUser(userId);
|
|
2417
|
+
if (!user) return [];
|
|
2418
|
+
const records = [];
|
|
2419
|
+
for (const identityId of user.identities) {
|
|
2420
|
+
const record = await this.getIdentity(identityId);
|
|
2421
|
+
if (record) records.push(record);
|
|
2422
|
+
}
|
|
2423
|
+
return records;
|
|
2424
|
+
}
|
|
2425
|
+
// === Secondary indexes ===
|
|
2426
|
+
async getUserIdByUsername(username) {
|
|
2427
|
+
return this.storage.get(`idx/usernames/${username.toLowerCase()}`);
|
|
2428
|
+
}
|
|
2429
|
+
async getIdentityIdBySource(source, platformId) {
|
|
2430
|
+
return this.storage.get(`idx/sources/${source}/${platformId}`);
|
|
2431
|
+
}
|
|
2432
|
+
// === Index mutations ===
|
|
2433
|
+
async setUsernameIndex(username, userId) {
|
|
2434
|
+
await this.storage.set(`idx/usernames/${username.toLowerCase()}`, userId);
|
|
2435
|
+
}
|
|
2436
|
+
async deleteUsernameIndex(username) {
|
|
2437
|
+
await this.storage.delete(`idx/usernames/${username.toLowerCase()}`);
|
|
2438
|
+
}
|
|
2439
|
+
async setSourceIndex(source, platformId, identityId) {
|
|
2440
|
+
await this.storage.set(`idx/sources/${source}/${platformId}`, identityId);
|
|
2441
|
+
}
|
|
2442
|
+
async deleteSourceIndex(source, platformId) {
|
|
2443
|
+
await this.storage.delete(`idx/sources/${source}/${platformId}`);
|
|
2444
|
+
}
|
|
2445
|
+
async getUserCount() {
|
|
2446
|
+
const keys = await this.storage.keys("users/");
|
|
2447
|
+
return keys.length;
|
|
2448
|
+
}
|
|
2449
|
+
};
|
|
2450
|
+
}
|
|
2451
|
+
});
|
|
2452
|
+
|
|
2453
|
+
// src/plugins/identity/middleware/auto-register.ts
|
|
2454
|
+
function createAutoRegisterHandler(service, store) {
|
|
2455
|
+
const lastSeenThrottle = /* @__PURE__ */ new Map();
|
|
2456
|
+
return async (payload, next) => {
|
|
2457
|
+
const { channelId, userId, meta } = payload;
|
|
2458
|
+
const identityId = formatIdentityId(channelId, userId);
|
|
2459
|
+
const channelUser = meta?.channelUser;
|
|
2460
|
+
let identity = await store.getIdentity(identityId);
|
|
2461
|
+
let user;
|
|
2462
|
+
if (!identity) {
|
|
2463
|
+
const result = await service.createUserWithIdentity({
|
|
2464
|
+
displayName: channelUser?.displayName ?? userId,
|
|
2465
|
+
username: channelUser?.username,
|
|
2466
|
+
source: channelId,
|
|
2467
|
+
platformId: userId,
|
|
2468
|
+
platformUsername: channelUser?.username,
|
|
2469
|
+
platformDisplayName: channelUser?.displayName
|
|
2470
|
+
});
|
|
2471
|
+
user = result.user;
|
|
2472
|
+
identity = result.identity;
|
|
2473
|
+
} else {
|
|
2474
|
+
user = await service.getUser(identity.userId);
|
|
2475
|
+
if (!user) return next();
|
|
2476
|
+
const now = Date.now();
|
|
2477
|
+
const lastSeen = lastSeenThrottle.get(user.userId);
|
|
2478
|
+
if (!lastSeen || now - lastSeen > LAST_SEEN_THROTTLE_MS) {
|
|
2479
|
+
lastSeenThrottle.set(user.userId, now);
|
|
2480
|
+
await store.putUser({ ...user, lastSeenAt: new Date(now).toISOString() });
|
|
2481
|
+
}
|
|
2482
|
+
if (channelUser) {
|
|
2483
|
+
const needsUpdate = channelUser.displayName !== void 0 && channelUser.displayName !== identity.platformDisplayName || channelUser.username !== void 0 && channelUser.username !== identity.platformUsername;
|
|
2484
|
+
if (needsUpdate) {
|
|
2485
|
+
await store.putIdentity({
|
|
2486
|
+
...identity,
|
|
2487
|
+
platformDisplayName: channelUser.displayName ?? identity.platformDisplayName,
|
|
2488
|
+
platformUsername: channelUser.username ?? identity.platformUsername,
|
|
2489
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2490
|
+
});
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
if (meta) {
|
|
2495
|
+
meta.identity = {
|
|
2496
|
+
userId: user.userId,
|
|
2497
|
+
identityId: identity.identityId,
|
|
2498
|
+
displayName: user.displayName,
|
|
2499
|
+
username: user.username,
|
|
2500
|
+
role: user.role
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
return next();
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
var LAST_SEEN_THROTTLE_MS;
|
|
2507
|
+
var init_auto_register = __esm({
|
|
2508
|
+
"src/plugins/identity/middleware/auto-register.ts"() {
|
|
2509
|
+
"use strict";
|
|
2510
|
+
init_types();
|
|
2511
|
+
LAST_SEEN_THROTTLE_MS = 5 * 60 * 1e3;
|
|
2512
|
+
}
|
|
2513
|
+
});
|
|
2514
|
+
|
|
2515
|
+
// src/plugins/identity/routes/users.ts
|
|
2516
|
+
var users_exports = {};
|
|
2517
|
+
__export(users_exports, {
|
|
2518
|
+
registerIdentityRoutes: () => registerIdentityRoutes
|
|
2519
|
+
});
|
|
2520
|
+
function registerIdentityRoutes(app, deps) {
|
|
2521
|
+
const { service, tokenStore } = deps;
|
|
2522
|
+
function resolveUserId(request) {
|
|
2523
|
+
return tokenStore?.getUserId?.(request.auth?.tokenId);
|
|
2524
|
+
}
|
|
2525
|
+
app.get("/users", async (request) => {
|
|
2526
|
+
const { source, role, q } = request.query;
|
|
2527
|
+
if (q) return service.searchUsers(q);
|
|
2528
|
+
return service.listUsers({ source, role });
|
|
2529
|
+
});
|
|
2530
|
+
app.get("/users/me", async (request, reply) => {
|
|
2531
|
+
const userId = resolveUserId(request);
|
|
2532
|
+
if (!userId) return reply.status(403).send({ error: "Identity not set up" });
|
|
2533
|
+
const user = await service.getUser(userId);
|
|
2534
|
+
if (!user) return reply.status(404).send({ error: "User not found" });
|
|
2535
|
+
return user;
|
|
2536
|
+
});
|
|
2537
|
+
app.put("/users/me", async (request, reply) => {
|
|
2538
|
+
const userId = resolveUserId(request);
|
|
2539
|
+
if (!userId) {
|
|
2540
|
+
return reply.status(403).send({ error: "Identity not set up. Call POST /identity/setup first." });
|
|
2541
|
+
}
|
|
2542
|
+
const body = request.body;
|
|
2543
|
+
return service.updateUser(userId, {
|
|
2544
|
+
displayName: body.displayName,
|
|
2545
|
+
username: body.username,
|
|
2546
|
+
avatarUrl: body.avatarUrl,
|
|
2547
|
+
timezone: body.timezone,
|
|
2548
|
+
locale: body.locale
|
|
2549
|
+
});
|
|
2550
|
+
});
|
|
2551
|
+
app.get("/users/:userId", async (request, reply) => {
|
|
2552
|
+
const { userId } = request.params;
|
|
2553
|
+
const user = await service.getUser(userId);
|
|
2554
|
+
if (!user) return reply.status(404).send({ error: "User not found" });
|
|
2555
|
+
return user;
|
|
2556
|
+
});
|
|
2557
|
+
app.put("/users/:userId/role", async (request, reply) => {
|
|
2558
|
+
const callerUserId = resolveUserId(request);
|
|
2559
|
+
if (!callerUserId) return reply.status(403).send({ error: "Identity not set up" });
|
|
2560
|
+
const caller = await service.getUser(callerUserId);
|
|
2561
|
+
if (!caller || caller.role !== "admin") return reply.status(403).send({ error: "Admin only" });
|
|
2562
|
+
const { userId } = request.params;
|
|
2563
|
+
const { role } = request.body;
|
|
2564
|
+
await service.setRole(userId, role);
|
|
2565
|
+
return { ok: true };
|
|
2566
|
+
});
|
|
2567
|
+
app.get("/users/:userId/identities", async (request) => {
|
|
2568
|
+
const { userId } = request.params;
|
|
2569
|
+
return service.getIdentitiesFor(userId);
|
|
2570
|
+
});
|
|
2571
|
+
app.get("/resolve/:identityId", async (request, reply) => {
|
|
2572
|
+
const { identityId } = request.params;
|
|
2573
|
+
const user = await service.getUserByIdentity(identityId);
|
|
2574
|
+
if (!user) return reply.status(404).send({ error: "Identity not found" });
|
|
2575
|
+
const identity = await service.getIdentity(identityId);
|
|
2576
|
+
return { user, identity };
|
|
2577
|
+
});
|
|
2578
|
+
app.post("/link", async (request, reply) => {
|
|
2579
|
+
const callerUserId = resolveUserId(request);
|
|
2580
|
+
if (!callerUserId) return reply.status(403).send({ error: "Identity not set up" });
|
|
2581
|
+
const caller = await service.getUser(callerUserId);
|
|
2582
|
+
if (!caller || caller.role !== "admin") return reply.status(403).send({ error: "Admin only" });
|
|
2583
|
+
const { identityIdA, identityIdB } = request.body;
|
|
2584
|
+
await service.link(identityIdA, identityIdB);
|
|
2585
|
+
return { ok: true };
|
|
2586
|
+
});
|
|
2587
|
+
app.post("/unlink", async (request, reply) => {
|
|
2588
|
+
const callerUserId = resolveUserId(request);
|
|
2589
|
+
if (!callerUserId) return reply.status(403).send({ error: "Identity not set up" });
|
|
2590
|
+
const caller = await service.getUser(callerUserId);
|
|
2591
|
+
if (!caller || caller.role !== "admin") return reply.status(403).send({ error: "Admin only" });
|
|
2592
|
+
const { identityId } = request.body;
|
|
2593
|
+
await service.unlink(identityId);
|
|
2594
|
+
return { ok: true };
|
|
2595
|
+
});
|
|
2596
|
+
app.get("/search", async (request) => {
|
|
2597
|
+
const { q } = request.query;
|
|
2598
|
+
if (!q) return [];
|
|
2599
|
+
return service.searchUsers(q);
|
|
2600
|
+
});
|
|
2601
|
+
}
|
|
2602
|
+
var init_users = __esm({
|
|
2603
|
+
"src/plugins/identity/routes/users.ts"() {
|
|
2604
|
+
"use strict";
|
|
2605
|
+
}
|
|
2606
|
+
});
|
|
2607
|
+
|
|
2608
|
+
// src/plugins/identity/routes/setup.ts
|
|
2609
|
+
var setup_exports = {};
|
|
2610
|
+
__export(setup_exports, {
|
|
2611
|
+
registerSetupRoutes: () => registerSetupRoutes
|
|
2612
|
+
});
|
|
2613
|
+
import { randomBytes } from "crypto";
|
|
2614
|
+
function registerSetupRoutes(app, deps) {
|
|
2615
|
+
const { service, tokenStore } = deps;
|
|
2616
|
+
app.post("/setup", async (request, reply) => {
|
|
2617
|
+
const auth = request.auth;
|
|
2618
|
+
if (!auth?.tokenId) return reply.status(401).send({ error: "JWT required" });
|
|
2619
|
+
const existingUserId = tokenStore?.getUserId?.(auth.tokenId);
|
|
2620
|
+
if (existingUserId) {
|
|
2621
|
+
const user2 = await service.getUser(existingUserId);
|
|
2622
|
+
if (user2) return user2;
|
|
2623
|
+
}
|
|
2624
|
+
const body = request.body;
|
|
2625
|
+
if (body?.linkCode) {
|
|
2626
|
+
const entry = linkCodes.get(body.linkCode);
|
|
2627
|
+
if (!entry || entry.expiresAt < Date.now()) {
|
|
2628
|
+
return reply.status(401).send({ error: "Invalid or expired link code" });
|
|
2629
|
+
}
|
|
2630
|
+
linkCodes.delete(body.linkCode);
|
|
2631
|
+
await service.createIdentity(entry.userId, {
|
|
2632
|
+
source: "api",
|
|
2633
|
+
platformId: auth.tokenId
|
|
2634
|
+
});
|
|
2635
|
+
tokenStore?.setUserId?.(auth.tokenId, entry.userId);
|
|
2636
|
+
return service.getUser(entry.userId);
|
|
2637
|
+
}
|
|
2638
|
+
if (!body?.displayName) return reply.status(400).send({ error: "displayName is required" });
|
|
2639
|
+
const { user } = await service.createUserWithIdentity({
|
|
2640
|
+
displayName: body.displayName,
|
|
2641
|
+
username: body.username,
|
|
2642
|
+
source: "api",
|
|
2643
|
+
platformId: auth.tokenId
|
|
2644
|
+
});
|
|
2645
|
+
tokenStore?.setUserId?.(auth.tokenId, user.userId);
|
|
2646
|
+
return user;
|
|
2647
|
+
});
|
|
2648
|
+
app.post("/link-code", async (request, reply) => {
|
|
2649
|
+
const auth = request.auth;
|
|
2650
|
+
if (!auth?.tokenId) return reply.status(401).send({ error: "JWT required" });
|
|
2651
|
+
const userId = tokenStore?.getUserId?.(auth.tokenId);
|
|
2652
|
+
if (!userId) return reply.status(403).send({ error: "Identity not set up" });
|
|
2653
|
+
const code = randomBytes(16).toString("hex");
|
|
2654
|
+
const expiresAt = Date.now() + 5 * 60 * 1e3;
|
|
2655
|
+
for (const [k, v] of linkCodes) {
|
|
2656
|
+
if (v.expiresAt < Date.now()) linkCodes.delete(k);
|
|
2657
|
+
}
|
|
2658
|
+
linkCodes.set(code, { userId, expiresAt });
|
|
2659
|
+
return { linkCode: code, expiresAt: new Date(expiresAt).toISOString() };
|
|
2660
|
+
});
|
|
2661
|
+
}
|
|
2662
|
+
var linkCodes;
|
|
2663
|
+
var init_setup = __esm({
|
|
2664
|
+
"src/plugins/identity/routes/setup.ts"() {
|
|
2665
|
+
"use strict";
|
|
2666
|
+
linkCodes = /* @__PURE__ */ new Map();
|
|
2667
|
+
}
|
|
2668
|
+
});
|
|
2669
|
+
|
|
2670
|
+
// src/plugins/identity/index.ts
|
|
2671
|
+
function createIdentityPlugin() {
|
|
2672
|
+
return {
|
|
2673
|
+
name: "@openacp/identity",
|
|
2674
|
+
version: "1.0.0",
|
|
2675
|
+
description: "User identity, cross-platform linking, and role-based access",
|
|
2676
|
+
essential: false,
|
|
2677
|
+
permissions: [
|
|
2678
|
+
"storage:read",
|
|
2679
|
+
"storage:write",
|
|
2680
|
+
"middleware:register",
|
|
2681
|
+
"services:register",
|
|
2682
|
+
"services:use",
|
|
2683
|
+
"events:emit",
|
|
2684
|
+
"events:read",
|
|
2685
|
+
"commands:register",
|
|
2686
|
+
"kernel:access"
|
|
2687
|
+
],
|
|
2688
|
+
optionalPluginDependencies: {
|
|
2689
|
+
"@openacp/api-server": ">=1.0.0"
|
|
2690
|
+
},
|
|
2691
|
+
async setup(ctx) {
|
|
2692
|
+
const store = new KvIdentityStore(ctx.storage);
|
|
2693
|
+
const service = new IdentityServiceImpl(store, (event, data) => {
|
|
2694
|
+
ctx.emit(event, data);
|
|
2695
|
+
});
|
|
2696
|
+
ctx.registerService("identity", service);
|
|
2697
|
+
ctx.registerMiddleware(Hook.MESSAGE_INCOMING, {
|
|
2698
|
+
priority: 110,
|
|
2699
|
+
handler: createAutoRegisterHandler(service, store)
|
|
2700
|
+
});
|
|
2701
|
+
ctx.registerCommand({
|
|
2702
|
+
name: "whoami",
|
|
2703
|
+
description: "Set your display name and username",
|
|
2704
|
+
usage: "[name]",
|
|
2705
|
+
category: "plugin",
|
|
2706
|
+
async handler(args2) {
|
|
2707
|
+
const name = args2.raw.trim();
|
|
2708
|
+
if (!name) {
|
|
2709
|
+
return { type: "text", text: "Usage: /whoami <name>" };
|
|
2710
|
+
}
|
|
2711
|
+
const identityId = formatIdentityId(args2.channelId, args2.userId);
|
|
2712
|
+
const user = await service.getUserByIdentity(identityId);
|
|
2713
|
+
if (!user) {
|
|
2714
|
+
return { type: "error", message: "User not found \u2014 send a message first." };
|
|
2715
|
+
}
|
|
2716
|
+
try {
|
|
2717
|
+
const username = name.toLowerCase().replace(/[^a-z0-9_]/g, "");
|
|
2718
|
+
await service.updateUser(user.userId, { displayName: name, username });
|
|
2719
|
+
return { type: "text", text: `Display name set to "${name}", username: @${username}` };
|
|
2720
|
+
} catch (err) {
|
|
2721
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2722
|
+
return { type: "error", message };
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
});
|
|
2726
|
+
const apiServer = ctx.getService("api-server");
|
|
2727
|
+
if (apiServer) {
|
|
2728
|
+
const tokenStore = ctx.getService("token-store");
|
|
2729
|
+
const { registerIdentityRoutes: registerIdentityRoutes2 } = await Promise.resolve().then(() => (init_users(), users_exports));
|
|
2730
|
+
const { registerSetupRoutes: registerSetupRoutes2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
2731
|
+
apiServer.registerPlugin("/api/v1/identity", async (app) => {
|
|
2732
|
+
registerIdentityRoutes2(app, { service, tokenStore: tokenStore ?? void 0 });
|
|
2733
|
+
registerSetupRoutes2(app, { service, tokenStore: tokenStore ?? void 0 });
|
|
2734
|
+
}, { auth: true });
|
|
2735
|
+
}
|
|
2736
|
+
ctx.log.info(`Identity service ready (${await service.getUserCount()} users)`);
|
|
2737
|
+
}
|
|
2738
|
+
};
|
|
2739
|
+
}
|
|
2740
|
+
var identity_default;
|
|
2741
|
+
var init_identity = __esm({
|
|
2742
|
+
"src/plugins/identity/index.ts"() {
|
|
2743
|
+
"use strict";
|
|
2744
|
+
init_identity_service();
|
|
2745
|
+
init_kv_identity_store();
|
|
2746
|
+
init_auto_register();
|
|
2747
|
+
init_types();
|
|
2748
|
+
init_events();
|
|
2749
|
+
identity_default = createIdentityPlugin();
|
|
2750
|
+
}
|
|
2751
|
+
});
|
|
2752
|
+
|
|
2038
2753
|
// src/core/utils/read-text-file.ts
|
|
2039
2754
|
var read_text_file_exports = {};
|
|
2040
2755
|
__export(read_text_file_exports, {
|
|
@@ -3631,7 +4346,7 @@ var init_history_recorder = __esm({
|
|
|
3631
4346
|
}
|
|
3632
4347
|
states = /* @__PURE__ */ new Map();
|
|
3633
4348
|
debounceTimers = /* @__PURE__ */ new Map();
|
|
3634
|
-
onBeforePrompt(sessionId, text5, attachments, sourceAdapterId) {
|
|
4349
|
+
onBeforePrompt(sessionId, text5, attachments, sourceAdapterId, meta) {
|
|
3635
4350
|
let state = this.states.get(sessionId);
|
|
3636
4351
|
if (!state) {
|
|
3637
4352
|
state = {
|
|
@@ -3652,6 +4367,9 @@ var init_history_recorder = __esm({
|
|
|
3652
4367
|
if (sourceAdapterId) {
|
|
3653
4368
|
userTurn.sourceAdapterId = sourceAdapterId;
|
|
3654
4369
|
}
|
|
4370
|
+
if (meta && Object.keys(meta).length > 0) {
|
|
4371
|
+
userTurn.meta = meta;
|
|
4372
|
+
}
|
|
3655
4373
|
state.history.turns.push(userTurn);
|
|
3656
4374
|
const assistantTurn = {
|
|
3657
4375
|
index: state.history.turns.length,
|
|
@@ -3986,7 +4704,7 @@ var init_context = __esm({
|
|
|
3986
4704
|
ctx.registerMiddleware(Hook.AGENT_BEFORE_PROMPT, {
|
|
3987
4705
|
priority: 200,
|
|
3988
4706
|
handler: async (payload, next) => {
|
|
3989
|
-
recorder.onBeforePrompt(payload.sessionId, payload.text, payload.attachments, payload.sourceAdapterId);
|
|
4707
|
+
recorder.onBeforePrompt(payload.sessionId, payload.text, payload.attachments, payload.sourceAdapterId, payload.meta);
|
|
3990
4708
|
return next();
|
|
3991
4709
|
}
|
|
3992
4710
|
});
|
|
@@ -4485,14 +5203,20 @@ var init_speech = __esm({
|
|
|
4485
5203
|
});
|
|
4486
5204
|
|
|
4487
5205
|
// src/plugins/notifications/notification.ts
|
|
4488
|
-
var
|
|
5206
|
+
var NotificationService;
|
|
4489
5207
|
var init_notification = __esm({
|
|
4490
5208
|
"src/plugins/notifications/notification.ts"() {
|
|
4491
5209
|
"use strict";
|
|
4492
|
-
|
|
5210
|
+
NotificationService = class {
|
|
4493
5211
|
constructor(adapters) {
|
|
4494
5212
|
this.adapters = adapters;
|
|
4495
5213
|
}
|
|
5214
|
+
identityResolver;
|
|
5215
|
+
/** Inject identity resolver for user-targeted notifications. */
|
|
5216
|
+
setIdentityResolver(resolver) {
|
|
5217
|
+
this.identityResolver = resolver;
|
|
5218
|
+
}
|
|
5219
|
+
// --- Legacy API (backward compat with NotificationManager) ---
|
|
4496
5220
|
/**
|
|
4497
5221
|
* Send a notification to a specific channel adapter.
|
|
4498
5222
|
*
|
|
@@ -4521,6 +5245,62 @@ var init_notification = __esm({
|
|
|
4521
5245
|
}
|
|
4522
5246
|
}
|
|
4523
5247
|
}
|
|
5248
|
+
// --- New user-targeted API ---
|
|
5249
|
+
/**
|
|
5250
|
+
* Send a notification to a user across all their linked platforms.
|
|
5251
|
+
* Fire-and-forget — never throws, swallows all errors.
|
|
5252
|
+
*/
|
|
5253
|
+
async notifyUser(target, message, options) {
|
|
5254
|
+
try {
|
|
5255
|
+
await this._resolveAndDeliver(target, message, options);
|
|
5256
|
+
} catch {
|
|
5257
|
+
}
|
|
5258
|
+
}
|
|
5259
|
+
async _resolveAndDeliver(target, message, options) {
|
|
5260
|
+
if ("channelId" in target && "platformId" in target) {
|
|
5261
|
+
const adapter = this.adapters.get(target.channelId);
|
|
5262
|
+
if (!adapter?.sendUserNotification) return;
|
|
5263
|
+
await adapter.sendUserNotification(target.platformId, message, {
|
|
5264
|
+
via: options?.via,
|
|
5265
|
+
topicId: options?.topicId,
|
|
5266
|
+
sessionId: options?.sessionId
|
|
5267
|
+
});
|
|
5268
|
+
return;
|
|
5269
|
+
}
|
|
5270
|
+
if (!this.identityResolver) return;
|
|
5271
|
+
let identities = [];
|
|
5272
|
+
if ("identityId" in target) {
|
|
5273
|
+
const identity = await this.identityResolver.getIdentity(target.identityId);
|
|
5274
|
+
if (!identity) return;
|
|
5275
|
+
const user = await this.identityResolver.getUser(identity.userId);
|
|
5276
|
+
if (!user) return;
|
|
5277
|
+
identities = await this.identityResolver.getIdentitiesFor(user.userId);
|
|
5278
|
+
} else if ("userId" in target) {
|
|
5279
|
+
identities = await this.identityResolver.getIdentitiesFor(target.userId);
|
|
5280
|
+
}
|
|
5281
|
+
if (options?.onlyPlatforms) {
|
|
5282
|
+
identities = identities.filter((i) => options.onlyPlatforms.includes(i.source));
|
|
5283
|
+
}
|
|
5284
|
+
if (options?.excludePlatforms) {
|
|
5285
|
+
identities = identities.filter((i) => !options.excludePlatforms.includes(i.source));
|
|
5286
|
+
}
|
|
5287
|
+
for (const identity of identities) {
|
|
5288
|
+
const adapter = this.adapters.get(identity.source);
|
|
5289
|
+
if (!adapter?.sendUserNotification) continue;
|
|
5290
|
+
try {
|
|
5291
|
+
await adapter.sendUserNotification(identity.platformId, message, {
|
|
5292
|
+
via: options?.via,
|
|
5293
|
+
topicId: options?.topicId,
|
|
5294
|
+
sessionId: options?.sessionId,
|
|
5295
|
+
platformMention: {
|
|
5296
|
+
platformUsername: identity.platformUsername,
|
|
5297
|
+
platformId: identity.platformId
|
|
5298
|
+
}
|
|
5299
|
+
});
|
|
5300
|
+
} catch {
|
|
5301
|
+
}
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
4524
5304
|
};
|
|
4525
5305
|
}
|
|
4526
5306
|
});
|
|
@@ -4538,11 +5318,10 @@ function createNotificationsPlugin() {
|
|
|
4538
5318
|
essential: false,
|
|
4539
5319
|
// Depends on security so the notification service is only active for authorized sessions
|
|
4540
5320
|
pluginDependencies: { "@openacp/security": "^1.0.0" },
|
|
4541
|
-
permissions: ["services:register", "kernel:access"],
|
|
5321
|
+
permissions: ["services:register", "services:use", "kernel:access", "events:read"],
|
|
4542
5322
|
async install(ctx) {
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
terminal.log.success("Notifications defaults saved");
|
|
5323
|
+
await ctx.settings.setAll({ enabled: true });
|
|
5324
|
+
ctx.terminal.log.success("Notifications defaults saved");
|
|
4546
5325
|
},
|
|
4547
5326
|
async configure(ctx) {
|
|
4548
5327
|
const { terminal, settings } = ctx;
|
|
@@ -4565,8 +5344,16 @@ function createNotificationsPlugin() {
|
|
|
4565
5344
|
},
|
|
4566
5345
|
async setup(ctx) {
|
|
4567
5346
|
const core = ctx.core;
|
|
4568
|
-
const
|
|
4569
|
-
ctx.
|
|
5347
|
+
const service = new NotificationService(core.adapters);
|
|
5348
|
+
const identity = ctx.getService("identity");
|
|
5349
|
+
if (identity) service.setIdentityResolver(identity);
|
|
5350
|
+
ctx.on("plugin:loaded", (data) => {
|
|
5351
|
+
if (data?.name === "@openacp/identity") {
|
|
5352
|
+
const id = ctx.getService("identity");
|
|
5353
|
+
if (id) service.setIdentityResolver(id);
|
|
5354
|
+
}
|
|
5355
|
+
});
|
|
5356
|
+
ctx.registerService("notifications", service);
|
|
4570
5357
|
ctx.log.info("Notifications service ready");
|
|
4571
5358
|
}
|
|
4572
5359
|
};
|
|
@@ -6866,7 +7653,7 @@ var init_viewer_routes = __esm({
|
|
|
6866
7653
|
// src/plugins/tunnel/viewer-store.ts
|
|
6867
7654
|
import * as fs17 from "fs";
|
|
6868
7655
|
import * as path20 from "path";
|
|
6869
|
-
import { nanoid } from "nanoid";
|
|
7656
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
6870
7657
|
var log10, MAX_CONTENT_SIZE, EXTENSION_LANGUAGE, ViewerStore;
|
|
6871
7658
|
var init_viewer_store = __esm({
|
|
6872
7659
|
"src/plugins/tunnel/viewer-store.ts"() {
|
|
@@ -6927,7 +7714,7 @@ var init_viewer_store = __esm({
|
|
|
6927
7714
|
log10.debug({ filePath, size: content.length }, "File too large for viewer");
|
|
6928
7715
|
return null;
|
|
6929
7716
|
}
|
|
6930
|
-
const id =
|
|
7717
|
+
const id = nanoid2(12);
|
|
6931
7718
|
const now = Date.now();
|
|
6932
7719
|
this.entries.set(id, {
|
|
6933
7720
|
id,
|
|
@@ -6953,7 +7740,7 @@ var init_viewer_store = __esm({
|
|
|
6953
7740
|
log10.debug({ filePath, size: combined }, "Diff content too large for viewer");
|
|
6954
7741
|
return null;
|
|
6955
7742
|
}
|
|
6956
|
-
const id =
|
|
7743
|
+
const id = nanoid2(12);
|
|
6957
7744
|
const now = Date.now();
|
|
6958
7745
|
this.entries.set(id, {
|
|
6959
7746
|
id,
|
|
@@ -6975,7 +7762,7 @@ var init_viewer_store = __esm({
|
|
|
6975
7762
|
log10.debug({ label, size: output.length }, "Output too large for viewer");
|
|
6976
7763
|
return null;
|
|
6977
7764
|
}
|
|
6978
|
-
const id =
|
|
7765
|
+
const id = nanoid2(12);
|
|
6979
7766
|
const now = Date.now();
|
|
6980
7767
|
this.entries.set(id, {
|
|
6981
7768
|
id,
|
|
@@ -7424,9 +8211,9 @@ __export(token_store_exports, {
|
|
|
7424
8211
|
parseDuration: () => parseDuration
|
|
7425
8212
|
});
|
|
7426
8213
|
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
7427
|
-
import { randomBytes } from "crypto";
|
|
8214
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
7428
8215
|
function generateTokenId() {
|
|
7429
|
-
return `tok_${
|
|
8216
|
+
return `tok_${randomBytes2(12).toString("hex")}`;
|
|
7430
8217
|
}
|
|
7431
8218
|
function parseDuration(duration) {
|
|
7432
8219
|
const match = duration.match(/^(\d+)(h|d|m)$/);
|
|
@@ -7573,6 +8360,18 @@ var init_token_store = __esm({
|
|
|
7573
8360
|
this.lastUsedSaveTimer = null;
|
|
7574
8361
|
}
|
|
7575
8362
|
}
|
|
8363
|
+
/** Associate a user ID with a token. Called by identity plugin after /identity/setup. */
|
|
8364
|
+
setUserId(tokenId, userId) {
|
|
8365
|
+
const token = this.tokens.get(tokenId);
|
|
8366
|
+
if (token) {
|
|
8367
|
+
token.userId = userId;
|
|
8368
|
+
this.scheduleSave();
|
|
8369
|
+
}
|
|
8370
|
+
}
|
|
8371
|
+
/** Get the user ID associated with a token. */
|
|
8372
|
+
getUserId(tokenId) {
|
|
8373
|
+
return this.tokens.get(tokenId)?.userId;
|
|
8374
|
+
}
|
|
7576
8375
|
/**
|
|
7577
8376
|
* Generates a one-time authorization code that can be exchanged for a JWT.
|
|
7578
8377
|
*
|
|
@@ -7580,7 +8379,7 @@ var init_token_store = __esm({
|
|
|
7580
8379
|
* the App, which exchanges it for a proper JWT without ever exposing the raw API secret.
|
|
7581
8380
|
*/
|
|
7582
8381
|
createCode(opts) {
|
|
7583
|
-
const code =
|
|
8382
|
+
const code = randomBytes2(16).toString("hex");
|
|
7584
8383
|
const now = /* @__PURE__ */ new Date();
|
|
7585
8384
|
const ttl = opts.codeTtlMs ?? 30 * 60 * 1e3;
|
|
7586
8385
|
const stored = {
|
|
@@ -8100,9 +8899,16 @@ async function createApiServer(options) {
|
|
|
8100
8899
|
});
|
|
8101
8900
|
const authPreHandler = createAuthPreHandler(options.getSecret, options.getJwtSecret, options.tokenStore);
|
|
8102
8901
|
app.decorateRequest("auth", null, []);
|
|
8902
|
+
let booted = false;
|
|
8903
|
+
app.addHook("onReady", async () => {
|
|
8904
|
+
booted = true;
|
|
8905
|
+
});
|
|
8103
8906
|
return {
|
|
8104
8907
|
app,
|
|
8105
8908
|
registerPlugin(prefix, plugin2, opts) {
|
|
8909
|
+
if (booted) {
|
|
8910
|
+
return;
|
|
8911
|
+
}
|
|
8106
8912
|
const wrappedPlugin = async (pluginApp, pluginOpts) => {
|
|
8107
8913
|
if (opts?.auth !== false) {
|
|
8108
8914
|
pluginApp.addHook("onRequest", authPreHandler);
|
|
@@ -8533,7 +9339,7 @@ var sessions_exports = {};
|
|
|
8533
9339
|
__export(sessions_exports, {
|
|
8534
9340
|
sessionRoutes: () => sessionRoutes
|
|
8535
9341
|
});
|
|
8536
|
-
import { nanoid as
|
|
9342
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
8537
9343
|
async function sessionRoutes(app, deps) {
|
|
8538
9344
|
app.get("/", { preHandler: requireScopes("sessions:read") }, async () => {
|
|
8539
9345
|
const summaries = deps.core.sessionManager.listAllSessions();
|
|
@@ -8561,32 +9367,48 @@ async function sessionRoutes(app, deps) {
|
|
|
8561
9367
|
{ preHandler: requireScopes("sessions:read") },
|
|
8562
9368
|
async (request) => {
|
|
8563
9369
|
const { sessionId } = SessionIdParamSchema.parse(request.params);
|
|
8564
|
-
const
|
|
8565
|
-
|
|
8566
|
-
)
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
9370
|
+
const id = decodeURIComponent(sessionId);
|
|
9371
|
+
const session = deps.core.sessionManager.getSession(id);
|
|
9372
|
+
if (session) {
|
|
9373
|
+
return {
|
|
9374
|
+
session: {
|
|
9375
|
+
id: session.id,
|
|
9376
|
+
agent: session.agentName,
|
|
9377
|
+
status: session.status,
|
|
9378
|
+
name: session.name ?? null,
|
|
9379
|
+
workspace: session.workingDirectory,
|
|
9380
|
+
createdAt: session.createdAt.toISOString(),
|
|
9381
|
+
dangerousMode: session.clientOverrides.bypassPermissions ?? false,
|
|
9382
|
+
queueDepth: session.queueDepth,
|
|
9383
|
+
promptRunning: session.promptRunning,
|
|
9384
|
+
threadId: session.threadId,
|
|
9385
|
+
channelId: session.channelId,
|
|
9386
|
+
agentSessionId: session.agentSessionId,
|
|
9387
|
+
configOptions: session.configOptions?.length ? session.configOptions : void 0,
|
|
9388
|
+
capabilities: session.agentCapabilities ?? null
|
|
9389
|
+
}
|
|
9390
|
+
};
|
|
9391
|
+
}
|
|
9392
|
+
const record = deps.core.sessionManager.getSessionRecord(id);
|
|
9393
|
+
if (!record) {
|
|
9394
|
+
throw new NotFoundError("SESSION_NOT_FOUND", `Session "${id}" not found`);
|
|
8572
9395
|
}
|
|
8573
9396
|
return {
|
|
8574
9397
|
session: {
|
|
8575
|
-
id:
|
|
8576
|
-
agent:
|
|
8577
|
-
status:
|
|
8578
|
-
name:
|
|
8579
|
-
workspace:
|
|
8580
|
-
createdAt:
|
|
8581
|
-
dangerousMode:
|
|
8582
|
-
queueDepth:
|
|
8583
|
-
promptRunning:
|
|
8584
|
-
threadId:
|
|
8585
|
-
channelId:
|
|
8586
|
-
agentSessionId:
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
capabilities: session.agentCapabilities ?? null
|
|
9398
|
+
id: record.sessionId,
|
|
9399
|
+
agent: record.agentName,
|
|
9400
|
+
status: record.status,
|
|
9401
|
+
name: record.name ?? null,
|
|
9402
|
+
workspace: record.workingDir,
|
|
9403
|
+
createdAt: record.createdAt,
|
|
9404
|
+
dangerousMode: record.clientOverrides?.bypassPermissions ?? false,
|
|
9405
|
+
queueDepth: 0,
|
|
9406
|
+
promptRunning: false,
|
|
9407
|
+
threadId: null,
|
|
9408
|
+
channelId: record.channelId,
|
|
9409
|
+
agentSessionId: record.agentSessionId,
|
|
9410
|
+
configOptions: record.acpState?.configOptions?.length ? record.acpState.configOptions : void 0,
|
|
9411
|
+
capabilities: record.acpState?.agentCapabilities ?? null
|
|
8590
9412
|
}
|
|
8591
9413
|
};
|
|
8592
9414
|
}
|
|
@@ -8692,8 +9514,17 @@ async function sessionRoutes(app, deps) {
|
|
|
8692
9514
|
}
|
|
8693
9515
|
attachments = await resolveAttachments(fileService, sessionId, body.attachments);
|
|
8694
9516
|
}
|
|
8695
|
-
const sourceAdapterId = body.sourceAdapterId ?? "
|
|
8696
|
-
const turnId = body.turnId ??
|
|
9517
|
+
const sourceAdapterId = body.sourceAdapterId ?? "sse";
|
|
9518
|
+
const turnId = body.turnId ?? nanoid3(8);
|
|
9519
|
+
const userId = request.auth?.tokenId ?? "api";
|
|
9520
|
+
const meta = { turnId, channelUser: { channelId: "sse", userId } };
|
|
9521
|
+
if (deps.lifecycleManager?.middlewareChain) {
|
|
9522
|
+
await deps.lifecycleManager.middlewareChain.execute(
|
|
9523
|
+
Hook.MESSAGE_INCOMING,
|
|
9524
|
+
{ channelId: sourceAdapterId, threadId: session.id, userId, text: body.prompt, attachments, meta },
|
|
9525
|
+
async (p2) => p2
|
|
9526
|
+
);
|
|
9527
|
+
}
|
|
8697
9528
|
deps.core.eventBus.emit(BusEvent.MESSAGE_QUEUED, {
|
|
8698
9529
|
sessionId,
|
|
8699
9530
|
turnId,
|
|
@@ -8706,7 +9537,7 @@ async function sessionRoutes(app, deps) {
|
|
|
8706
9537
|
await session.enqueuePrompt(body.prompt, attachments, {
|
|
8707
9538
|
sourceAdapterId,
|
|
8708
9539
|
responseAdapterId: body.responseAdapterId
|
|
8709
|
-
}, turnId);
|
|
9540
|
+
}, turnId, meta);
|
|
8710
9541
|
return {
|
|
8711
9542
|
ok: true,
|
|
8712
9543
|
sessionId,
|
|
@@ -8721,7 +9552,7 @@ async function sessionRoutes(app, deps) {
|
|
|
8721
9552
|
async (request, reply) => {
|
|
8722
9553
|
const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
|
|
8723
9554
|
const sessionId = decodeURIComponent(rawId);
|
|
8724
|
-
const session = deps.core.
|
|
9555
|
+
const session = await deps.core.getOrResumeSessionById(sessionId);
|
|
8725
9556
|
if (!session) {
|
|
8726
9557
|
throw new NotFoundError(
|
|
8727
9558
|
"SESSION_NOT_FOUND",
|
|
@@ -8750,7 +9581,7 @@ async function sessionRoutes(app, deps) {
|
|
|
8750
9581
|
async (request) => {
|
|
8751
9582
|
const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
|
|
8752
9583
|
const sessionId = decodeURIComponent(rawId);
|
|
8753
|
-
const session = deps.core.
|
|
9584
|
+
const session = await deps.core.getOrResumeSessionById(sessionId);
|
|
8754
9585
|
if (!session) {
|
|
8755
9586
|
throw new NotFoundError(
|
|
8756
9587
|
"SESSION_NOT_FOUND",
|
|
@@ -8787,7 +9618,7 @@ async function sessionRoutes(app, deps) {
|
|
|
8787
9618
|
async (request) => {
|
|
8788
9619
|
const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
|
|
8789
9620
|
const sessionId = decodeURIComponent(rawId);
|
|
8790
|
-
const session = deps.core.
|
|
9621
|
+
const session = await deps.core.getOrResumeSessionById(sessionId);
|
|
8791
9622
|
if (!session) {
|
|
8792
9623
|
throw new NotFoundError(
|
|
8793
9624
|
"SESSION_NOT_FOUND",
|
|
@@ -8809,15 +9640,19 @@ async function sessionRoutes(app, deps) {
|
|
|
8809
9640
|
const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
|
|
8810
9641
|
const sessionId = decodeURIComponent(rawId);
|
|
8811
9642
|
const session = deps.core.sessionManager.getSession(sessionId);
|
|
8812
|
-
if (
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
9643
|
+
if (session) {
|
|
9644
|
+
return {
|
|
9645
|
+
configOptions: session.configOptions,
|
|
9646
|
+
clientOverrides: session.clientOverrides
|
|
9647
|
+
};
|
|
9648
|
+
}
|
|
9649
|
+
const record = deps.core.sessionManager.getSessionRecord(sessionId);
|
|
9650
|
+
if (!record) {
|
|
9651
|
+
throw new NotFoundError("SESSION_NOT_FOUND", `Session "${sessionId}" not found`);
|
|
8817
9652
|
}
|
|
8818
9653
|
return {
|
|
8819
|
-
configOptions:
|
|
8820
|
-
clientOverrides:
|
|
9654
|
+
configOptions: record.acpState?.configOptions,
|
|
9655
|
+
clientOverrides: record.clientOverrides ?? {}
|
|
8821
9656
|
};
|
|
8822
9657
|
}
|
|
8823
9658
|
);
|
|
@@ -8827,7 +9662,7 @@ async function sessionRoutes(app, deps) {
|
|
|
8827
9662
|
async (request) => {
|
|
8828
9663
|
const { sessionId: rawId, configId } = ConfigIdParamSchema.parse(request.params);
|
|
8829
9664
|
const sessionId = decodeURIComponent(rawId);
|
|
8830
|
-
const session = deps.core.
|
|
9665
|
+
const session = await deps.core.getOrResumeSessionById(sessionId);
|
|
8831
9666
|
if (!session) {
|
|
8832
9667
|
throw new NotFoundError(
|
|
8833
9668
|
"SESSION_NOT_FOUND",
|
|
@@ -8852,13 +9687,14 @@ async function sessionRoutes(app, deps) {
|
|
|
8852
9687
|
const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
|
|
8853
9688
|
const sessionId = decodeURIComponent(rawId);
|
|
8854
9689
|
const session = deps.core.sessionManager.getSession(sessionId);
|
|
8855
|
-
if (
|
|
8856
|
-
|
|
8857
|
-
"SESSION_NOT_FOUND",
|
|
8858
|
-
`Session "${sessionId}" not found`
|
|
8859
|
-
);
|
|
9690
|
+
if (session) {
|
|
9691
|
+
return { clientOverrides: session.clientOverrides };
|
|
8860
9692
|
}
|
|
8861
|
-
|
|
9693
|
+
const record = deps.core.sessionManager.getSessionRecord(sessionId);
|
|
9694
|
+
if (!record) {
|
|
9695
|
+
throw new NotFoundError("SESSION_NOT_FOUND", `Session "${sessionId}" not found`);
|
|
9696
|
+
}
|
|
9697
|
+
return { clientOverrides: record.clientOverrides ?? {} };
|
|
8862
9698
|
}
|
|
8863
9699
|
);
|
|
8864
9700
|
app.put(
|
|
@@ -8867,7 +9703,7 @@ async function sessionRoutes(app, deps) {
|
|
|
8867
9703
|
async (request) => {
|
|
8868
9704
|
const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
|
|
8869
9705
|
const sessionId = decodeURIComponent(rawId);
|
|
8870
|
-
const session = deps.core.
|
|
9706
|
+
const session = await deps.core.getOrResumeSessionById(sessionId);
|
|
8871
9707
|
if (!session) {
|
|
8872
9708
|
throw new NotFoundError(
|
|
8873
9709
|
"SESSION_NOT_FOUND",
|
|
@@ -8933,8 +9769,8 @@ async function sessionRoutes(app, deps) {
|
|
|
8933
9769
|
{ preHandler: requireScopes("sessions:read") },
|
|
8934
9770
|
async (request, reply) => {
|
|
8935
9771
|
const { sessionId } = SessionIdParamSchema.parse(request.params);
|
|
8936
|
-
const
|
|
8937
|
-
if (!
|
|
9772
|
+
const isKnown = deps.core.sessionManager.getSession(sessionId) ?? deps.core.sessionManager.getSessionRecord(sessionId);
|
|
9773
|
+
if (!isKnown) {
|
|
8938
9774
|
throw new NotFoundError(
|
|
8939
9775
|
"SESSION_NOT_FOUND",
|
|
8940
9776
|
`Session "${sessionId}" not found`
|
|
@@ -8956,8 +9792,8 @@ async function sessionRoutes(app, deps) {
|
|
|
8956
9792
|
async (request) => {
|
|
8957
9793
|
const { sessionId: rawId } = SessionIdParamSchema.parse(request.params);
|
|
8958
9794
|
const sessionId = decodeURIComponent(rawId);
|
|
8959
|
-
const
|
|
8960
|
-
if (!
|
|
9795
|
+
const isKnown = deps.core.sessionManager.getSession(sessionId) ?? deps.core.sessionManager.getSessionRecord(sessionId);
|
|
9796
|
+
if (!isKnown) {
|
|
8961
9797
|
throw new NotFoundError(
|
|
8962
9798
|
"SESSION_NOT_FOUND",
|
|
8963
9799
|
`Session "${sessionId}" not found`
|
|
@@ -9159,7 +9995,7 @@ import { z as z4 } from "zod";
|
|
|
9159
9995
|
import * as fs21 from "fs";
|
|
9160
9996
|
import * as path24 from "path";
|
|
9161
9997
|
import * as os5 from "os";
|
|
9162
|
-
import { randomBytes as
|
|
9998
|
+
import { randomBytes as randomBytes3 } from "crypto";
|
|
9163
9999
|
import { EventEmitter } from "events";
|
|
9164
10000
|
function expandHome2(p2) {
|
|
9165
10001
|
if (p2.startsWith("~")) {
|
|
@@ -9289,7 +10125,7 @@ var init_config2 = __esm({
|
|
|
9289
10125
|
log14.error({ errors: result.error.issues }, "Config validation failed, not saving");
|
|
9290
10126
|
return;
|
|
9291
10127
|
}
|
|
9292
|
-
const tmpPath = this.configPath + `.tmp.${
|
|
10128
|
+
const tmpPath = this.configPath + `.tmp.${randomBytes3(4).toString("hex")}`;
|
|
9293
10129
|
fs21.writeFileSync(tmpPath, JSON.stringify(raw, null, 2), "utf-8");
|
|
9294
10130
|
fs21.renameSync(tmpPath, this.configPath);
|
|
9295
10131
|
this.config = result.data;
|
|
@@ -9858,8 +10694,8 @@ async function commandRoutes(app, deps) {
|
|
|
9858
10694
|
const result = await deps.commandRegistry.execute(commandString, {
|
|
9859
10695
|
raw: "",
|
|
9860
10696
|
sessionId: body.sessionId ?? null,
|
|
9861
|
-
channelId: "
|
|
9862
|
-
userId: "api",
|
|
10697
|
+
channelId: "sse",
|
|
10698
|
+
userId: request.auth?.tokenId ?? "api",
|
|
9863
10699
|
reply: async () => {
|
|
9864
10700
|
}
|
|
9865
10701
|
});
|
|
@@ -10019,11 +10855,23 @@ async function authRoutes(app, deps) {
|
|
|
10019
10855
|
});
|
|
10020
10856
|
app.get("/me", async (request) => {
|
|
10021
10857
|
const { auth } = request;
|
|
10858
|
+
const userId = auth.tokenId ? deps.tokenStore.getUserId(auth.tokenId) : void 0;
|
|
10859
|
+
let displayName = null;
|
|
10860
|
+
if (userId && deps.getIdentityService) {
|
|
10861
|
+
const identityService = deps.getIdentityService();
|
|
10862
|
+
if (identityService) {
|
|
10863
|
+
const user = await identityService.getUser(userId);
|
|
10864
|
+
displayName = user?.displayName ?? null;
|
|
10865
|
+
}
|
|
10866
|
+
}
|
|
10022
10867
|
return {
|
|
10023
10868
|
type: auth.type,
|
|
10024
10869
|
tokenId: auth.tokenId,
|
|
10025
10870
|
role: auth.role,
|
|
10026
|
-
scopes: auth.scopes
|
|
10871
|
+
scopes: auth.scopes,
|
|
10872
|
+
userId: userId ?? null,
|
|
10873
|
+
displayName,
|
|
10874
|
+
claimed: !!userId
|
|
10027
10875
|
};
|
|
10028
10876
|
});
|
|
10029
10877
|
app.post("/codes", {
|
|
@@ -10603,6 +11451,7 @@ function createApiServerPlugin() {
|
|
|
10603
11451
|
const tokenStore = new TokenStore2(tokensFilePath);
|
|
10604
11452
|
await tokenStore.load();
|
|
10605
11453
|
tokenStoreRef = tokenStore;
|
|
11454
|
+
ctx.registerService("token-store", tokenStore);
|
|
10606
11455
|
const { createApiServer: createApiServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
10607
11456
|
const { SSEManager: SSEManager2 } = await Promise.resolve().then(() => (init_sse_manager(), sse_manager_exports));
|
|
10608
11457
|
const { StaticServer: StaticServer2 } = await Promise.resolve().then(() => (init_static_server(), static_server_exports));
|
|
@@ -10656,7 +11505,12 @@ function createApiServerPlugin() {
|
|
|
10656
11505
|
server.registerPlugin("/api/v1/tunnel", async (app) => tunnelRoutes2(app, deps));
|
|
10657
11506
|
server.registerPlugin("/api/v1/notify", async (app) => notifyRoutes2(app, deps));
|
|
10658
11507
|
server.registerPlugin("/api/v1/commands", async (app) => commandRoutes2(app, deps));
|
|
10659
|
-
server.registerPlugin("/api/v1/auth", async (app) => authRoutes2(app, {
|
|
11508
|
+
server.registerPlugin("/api/v1/auth", async (app) => authRoutes2(app, {
|
|
11509
|
+
tokenStore,
|
|
11510
|
+
getJwtSecret: () => jwtSecret,
|
|
11511
|
+
// Lazy resolver: identity plugin may not be loaded, so we fetch it on demand
|
|
11512
|
+
getIdentityService: () => ctx.getService("identity") ?? void 0
|
|
11513
|
+
}));
|
|
10660
11514
|
server.registerPlugin("/api/v1/plugins", async (app) => pluginRoutes2(app, deps));
|
|
10661
11515
|
const appConfig = core.configManager.get();
|
|
10662
11516
|
const workspaceName = appConfig.instanceName ?? "Main";
|
|
@@ -10805,7 +11659,7 @@ var init_api_server = __esm({
|
|
|
10805
11659
|
});
|
|
10806
11660
|
|
|
10807
11661
|
// src/plugins/sse-adapter/connection-manager.ts
|
|
10808
|
-
import { randomBytes as
|
|
11662
|
+
import { randomBytes as randomBytes5 } from "crypto";
|
|
10809
11663
|
var ConnectionManager;
|
|
10810
11664
|
var init_connection_manager = __esm({
|
|
10811
11665
|
"src/plugins/sse-adapter/connection-manager.ts"() {
|
|
@@ -10814,6 +11668,8 @@ var init_connection_manager = __esm({
|
|
|
10814
11668
|
connections = /* @__PURE__ */ new Map();
|
|
10815
11669
|
// Secondary index: sessionId → Set of connection IDs for O(1) broadcast targeting
|
|
10816
11670
|
sessionIndex = /* @__PURE__ */ new Map();
|
|
11671
|
+
// Secondary index: userId → Set of connection IDs for user-level event delivery
|
|
11672
|
+
userIndex = /* @__PURE__ */ new Map();
|
|
10817
11673
|
maxConnectionsPerSession;
|
|
10818
11674
|
maxTotalConnections;
|
|
10819
11675
|
constructor(opts) {
|
|
@@ -10836,7 +11692,7 @@ var init_connection_manager = __esm({
|
|
|
10836
11692
|
if (sessionConns && sessionConns.size >= this.maxConnectionsPerSession) {
|
|
10837
11693
|
throw new Error("Maximum connections per session reached");
|
|
10838
11694
|
}
|
|
10839
|
-
const id = `conn_${
|
|
11695
|
+
const id = `conn_${randomBytes5(8).toString("hex")}`;
|
|
10840
11696
|
const connection = { id, sessionId, tokenId, response, connectedAt: /* @__PURE__ */ new Date() };
|
|
10841
11697
|
this.connections.set(id, connection);
|
|
10842
11698
|
let sessionConnsSet = this.sessionIndex.get(sessionId);
|
|
@@ -10848,7 +11704,66 @@ var init_connection_manager = __esm({
|
|
|
10848
11704
|
response.on("close", () => this.removeConnection(id));
|
|
10849
11705
|
return connection;
|
|
10850
11706
|
}
|
|
10851
|
-
/**
|
|
11707
|
+
/**
|
|
11708
|
+
* Registers a user-level SSE connection (not tied to a specific session).
|
|
11709
|
+
* Used for notifications and system events delivered to a user.
|
|
11710
|
+
*
|
|
11711
|
+
* @throws if the global connection limit is reached.
|
|
11712
|
+
*/
|
|
11713
|
+
addUserConnection(userId, tokenId, response) {
|
|
11714
|
+
if (this.connections.size >= this.maxTotalConnections) {
|
|
11715
|
+
throw new Error("Maximum total connections reached");
|
|
11716
|
+
}
|
|
11717
|
+
const id = `conn_${randomBytes5(8).toString("hex")}`;
|
|
11718
|
+
const connection = {
|
|
11719
|
+
id,
|
|
11720
|
+
sessionId: "",
|
|
11721
|
+
tokenId,
|
|
11722
|
+
userId,
|
|
11723
|
+
response,
|
|
11724
|
+
connectedAt: /* @__PURE__ */ new Date()
|
|
11725
|
+
};
|
|
11726
|
+
this.connections.set(id, connection);
|
|
11727
|
+
let userConns = this.userIndex.get(userId);
|
|
11728
|
+
if (!userConns) {
|
|
11729
|
+
userConns = /* @__PURE__ */ new Set();
|
|
11730
|
+
this.userIndex.set(userId, userConns);
|
|
11731
|
+
}
|
|
11732
|
+
userConns.add(id);
|
|
11733
|
+
response.on("close", () => this.removeConnection(id));
|
|
11734
|
+
return connection;
|
|
11735
|
+
}
|
|
11736
|
+
/**
|
|
11737
|
+
* Writes a serialized SSE event to all connections for a given user.
|
|
11738
|
+
*
|
|
11739
|
+
* Uses the same backpressure strategy as `broadcast`: flag on first overflow,
|
|
11740
|
+
* forcibly close if still backpressured on the next write.
|
|
11741
|
+
*/
|
|
11742
|
+
pushToUser(userId, serializedEvent) {
|
|
11743
|
+
const connIds = this.userIndex.get(userId);
|
|
11744
|
+
if (!connIds) return;
|
|
11745
|
+
for (const connId of connIds) {
|
|
11746
|
+
const conn = this.connections.get(connId);
|
|
11747
|
+
if (!conn || conn.response.writableEnded) continue;
|
|
11748
|
+
try {
|
|
11749
|
+
const ok3 = conn.response.write(serializedEvent);
|
|
11750
|
+
if (!ok3) {
|
|
11751
|
+
if (conn.backpressured) {
|
|
11752
|
+
conn.response.end();
|
|
11753
|
+
this.removeConnection(conn.id);
|
|
11754
|
+
} else {
|
|
11755
|
+
conn.backpressured = true;
|
|
11756
|
+
conn.response.once("drain", () => {
|
|
11757
|
+
conn.backpressured = false;
|
|
11758
|
+
});
|
|
11759
|
+
}
|
|
11760
|
+
}
|
|
11761
|
+
} catch {
|
|
11762
|
+
this.removeConnection(conn.id);
|
|
11763
|
+
}
|
|
11764
|
+
}
|
|
11765
|
+
}
|
|
11766
|
+
/** Remove a connection from all indexes. Called automatically on client disconnect. */
|
|
10852
11767
|
removeConnection(connectionId) {
|
|
10853
11768
|
const conn = this.connections.get(connectionId);
|
|
10854
11769
|
if (!conn) return;
|
|
@@ -10858,6 +11773,13 @@ var init_connection_manager = __esm({
|
|
|
10858
11773
|
sessionConns.delete(connectionId);
|
|
10859
11774
|
if (sessionConns.size === 0) this.sessionIndex.delete(conn.sessionId);
|
|
10860
11775
|
}
|
|
11776
|
+
if (conn.userId) {
|
|
11777
|
+
const userConns = this.userIndex.get(conn.userId);
|
|
11778
|
+
if (userConns) {
|
|
11779
|
+
userConns.delete(connectionId);
|
|
11780
|
+
if (userConns.size === 0) this.userIndex.delete(conn.userId);
|
|
11781
|
+
}
|
|
11782
|
+
}
|
|
10861
11783
|
}
|
|
10862
11784
|
/** Returns all active connections for a session. */
|
|
10863
11785
|
getConnectionsBySession(sessionId) {
|
|
@@ -10917,6 +11839,7 @@ var init_connection_manager = __esm({
|
|
|
10917
11839
|
}
|
|
10918
11840
|
this.connections.clear();
|
|
10919
11841
|
this.sessionIndex.clear();
|
|
11842
|
+
this.userIndex.clear();
|
|
10920
11843
|
}
|
|
10921
11844
|
};
|
|
10922
11845
|
}
|
|
@@ -11110,6 +12033,22 @@ var init_adapter = __esm({
|
|
|
11110
12033
|
this.connectionManager.broadcast(notification.sessionId, serialized);
|
|
11111
12034
|
}
|
|
11112
12035
|
}
|
|
12036
|
+
/**
|
|
12037
|
+
* Delivers a push notification to a specific user's SSE connections.
|
|
12038
|
+
*
|
|
12039
|
+
* `platformId` is the userId for the SSE adapter — SSE has no concept of
|
|
12040
|
+
* platform-specific user handles, so we use the internal userId directly.
|
|
12041
|
+
*/
|
|
12042
|
+
async sendUserNotification(platformId, message, options) {
|
|
12043
|
+
const serialized = `event: notification:text
|
|
12044
|
+
data: ${JSON.stringify({
|
|
12045
|
+
text: message.text ?? message.summary ?? "",
|
|
12046
|
+
...options ?? {}
|
|
12047
|
+
})}
|
|
12048
|
+
|
|
12049
|
+
`;
|
|
12050
|
+
this.connectionManager.pushToUser(platformId, serialized);
|
|
12051
|
+
}
|
|
11113
12052
|
/** SSE has no concept of threads — return sessionId as the threadId */
|
|
11114
12053
|
async createSessionThread(sessionId, _name) {
|
|
11115
12054
|
return sessionId;
|
|
@@ -11200,8 +12139,14 @@ async function sseRoutes(app, deps) {
|
|
|
11200
12139
|
}
|
|
11201
12140
|
attachments = await resolveAttachments(fileService, sessionId, body.attachments);
|
|
11202
12141
|
}
|
|
11203
|
-
|
|
11204
|
-
|
|
12142
|
+
const queueDepth = session.queueDepth + 1;
|
|
12143
|
+
const userId = request.auth?.tokenId ?? "api";
|
|
12144
|
+
await deps.core.handleMessageInSession(
|
|
12145
|
+
session,
|
|
12146
|
+
{ channelId: "sse", userId, text: body.prompt, attachments },
|
|
12147
|
+
{ channelUser: { channelId: "sse", userId } }
|
|
12148
|
+
);
|
|
12149
|
+
return { ok: true, sessionId, queueDepth };
|
|
11205
12150
|
}
|
|
11206
12151
|
);
|
|
11207
12152
|
app.post(
|
|
@@ -11279,6 +12224,45 @@ async function sseRoutes(app, deps) {
|
|
|
11279
12224
|
total: connections.length
|
|
11280
12225
|
};
|
|
11281
12226
|
});
|
|
12227
|
+
app.get("/events", async (request, reply) => {
|
|
12228
|
+
const auth = request.auth;
|
|
12229
|
+
if (!auth?.tokenId) {
|
|
12230
|
+
return reply.status(401).send({ error: "Unauthorized" });
|
|
12231
|
+
}
|
|
12232
|
+
const userId = deps.getUserId?.(auth.tokenId);
|
|
12233
|
+
if (!userId) {
|
|
12234
|
+
return reply.status(403).send({ error: "Identity not set up. Complete /identity/setup first." });
|
|
12235
|
+
}
|
|
12236
|
+
try {
|
|
12237
|
+
deps.connectionManager.addUserConnection(userId, auth.tokenId, reply.raw);
|
|
12238
|
+
} catch (err) {
|
|
12239
|
+
return reply.status(503).send({ error: err.message });
|
|
12240
|
+
}
|
|
12241
|
+
reply.hijack();
|
|
12242
|
+
const raw = reply.raw;
|
|
12243
|
+
raw.writeHead(200, {
|
|
12244
|
+
"Content-Type": "text/event-stream",
|
|
12245
|
+
"Cache-Control": "no-cache",
|
|
12246
|
+
"Connection": "keep-alive",
|
|
12247
|
+
// Disable buffering in Nginx/Cloudflare so events arrive without delay
|
|
12248
|
+
"X-Accel-Buffering": "no"
|
|
12249
|
+
});
|
|
12250
|
+
raw.write(`event: heartbeat
|
|
12251
|
+
data: ${JSON.stringify({ ts: Date.now() })}
|
|
12252
|
+
|
|
12253
|
+
`);
|
|
12254
|
+
const heartbeat = setInterval(() => {
|
|
12255
|
+
if (raw.writableEnded) {
|
|
12256
|
+
clearInterval(heartbeat);
|
|
12257
|
+
return;
|
|
12258
|
+
}
|
|
12259
|
+
raw.write(`event: heartbeat
|
|
12260
|
+
data: ${JSON.stringify({ ts: Date.now() })}
|
|
12261
|
+
|
|
12262
|
+
`);
|
|
12263
|
+
}, 3e4);
|
|
12264
|
+
raw.on("close", () => clearInterval(heartbeat));
|
|
12265
|
+
});
|
|
11282
12266
|
}
|
|
11283
12267
|
var init_routes = __esm({
|
|
11284
12268
|
"src/plugins/sse-adapter/routes.ts"() {
|
|
@@ -11332,6 +12316,7 @@ var init_sse_adapter = __esm({
|
|
|
11332
12316
|
_connectionManager = connectionManager;
|
|
11333
12317
|
ctx.registerService("adapter:sse", adapter);
|
|
11334
12318
|
const commandRegistry = ctx.getService("command-registry");
|
|
12319
|
+
const tokenStore = ctx.getService("token-store");
|
|
11335
12320
|
ctx.on(BusEvent.SESSION_DELETED, (data) => {
|
|
11336
12321
|
const { sessionId } = data;
|
|
11337
12322
|
eventBuffer.cleanup(sessionId);
|
|
@@ -11345,7 +12330,8 @@ var init_sse_adapter = __esm({
|
|
|
11345
12330
|
core,
|
|
11346
12331
|
connectionManager,
|
|
11347
12332
|
eventBuffer,
|
|
11348
|
-
commandRegistry: commandRegistry ?? void 0
|
|
12333
|
+
commandRegistry: commandRegistry ?? void 0,
|
|
12334
|
+
getUserId: tokenStore ? (id) => tokenStore.getUserId(id) : void 0
|
|
11349
12335
|
});
|
|
11350
12336
|
}, { auth: true });
|
|
11351
12337
|
ctx.log.info("SSE adapter registered");
|
|
@@ -15377,7 +16363,7 @@ var init_commands3 = __esm({
|
|
|
15377
16363
|
|
|
15378
16364
|
// src/plugins/telegram/permissions.ts
|
|
15379
16365
|
import { InlineKeyboard as InlineKeyboard11 } from "grammy";
|
|
15380
|
-
import { nanoid as
|
|
16366
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
15381
16367
|
var log26, PermissionHandler;
|
|
15382
16368
|
var init_permissions = __esm({
|
|
15383
16369
|
"src/plugins/telegram/permissions.ts"() {
|
|
@@ -15404,7 +16390,7 @@ var init_permissions = __esm({
|
|
|
15404
16390
|
*/
|
|
15405
16391
|
async sendPermissionRequest(session, request) {
|
|
15406
16392
|
const threadId = Number(session.threadId);
|
|
15407
|
-
const callbackKey =
|
|
16393
|
+
const callbackKey = nanoid4(8);
|
|
15408
16394
|
this.pending.set(callbackKey, {
|
|
15409
16395
|
sessionId: session.id,
|
|
15410
16396
|
requestId: request.id,
|
|
@@ -17539,10 +18525,19 @@ var init_adapter2 = __esm({
|
|
|
17539
18525
|
});
|
|
17540
18526
|
} catch {
|
|
17541
18527
|
}
|
|
17542
|
-
} else if (response.type === "text" || response.type === "error") {
|
|
17543
|
-
|
|
18528
|
+
} else if (response.type === "text" || response.type === "error" || response.type === "adaptive") {
|
|
18529
|
+
let text5;
|
|
18530
|
+
let parseMode;
|
|
18531
|
+
if (response.type === "adaptive") {
|
|
18532
|
+
const variant = response.variants?.["telegram"];
|
|
18533
|
+
text5 = variant?.text ?? response.fallback;
|
|
18534
|
+
parseMode = variant?.parse_mode;
|
|
18535
|
+
} else {
|
|
18536
|
+
text5 = response.type === "text" ? response.text : `\u274C ${response.message}`;
|
|
18537
|
+
parseMode = "Markdown";
|
|
18538
|
+
}
|
|
17544
18539
|
try {
|
|
17545
|
-
await ctx.editMessageText(text5, { parse_mode:
|
|
18540
|
+
await ctx.editMessageText(text5, { ...parseMode && { parse_mode: parseMode } });
|
|
17546
18541
|
} catch {
|
|
17547
18542
|
}
|
|
17548
18543
|
}
|
|
@@ -17824,6 +18819,15 @@ OpenACP will automatically retry until this is resolved.`;
|
|
|
17824
18819
|
message_thread_id: topicId
|
|
17825
18820
|
});
|
|
17826
18821
|
break;
|
|
18822
|
+
case "adaptive": {
|
|
18823
|
+
const variant = response.variants?.["telegram"];
|
|
18824
|
+
const text5 = variant?.text ?? response.fallback;
|
|
18825
|
+
await this.bot.api.sendMessage(chatId, text5, {
|
|
18826
|
+
message_thread_id: topicId,
|
|
18827
|
+
...variant?.parse_mode && { parse_mode: variant.parse_mode }
|
|
18828
|
+
});
|
|
18829
|
+
break;
|
|
18830
|
+
}
|
|
17827
18831
|
case "error":
|
|
17828
18832
|
await this.bot.api.sendMessage(
|
|
17829
18833
|
chatId,
|
|
@@ -17932,12 +18936,25 @@ ${lines.join("\n")}`;
|
|
|
17932
18936
|
}
|
|
17933
18937
|
ctx.replyWithChatAction("typing").catch(() => {
|
|
17934
18938
|
});
|
|
17935
|
-
|
|
17936
|
-
|
|
17937
|
-
|
|
17938
|
-
|
|
17939
|
-
|
|
17940
|
-
|
|
18939
|
+
const fromName = [ctx.from.first_name, ctx.from.last_name].filter(Boolean).join(" ") || void 0;
|
|
18940
|
+
this.core.handleMessage(
|
|
18941
|
+
{
|
|
18942
|
+
channelId: "telegram",
|
|
18943
|
+
threadId: String(threadId),
|
|
18944
|
+
userId: String(ctx.from.id),
|
|
18945
|
+
text: forwardText
|
|
18946
|
+
},
|
|
18947
|
+
// Inject structured channel user info into TurnMeta so plugins can identify
|
|
18948
|
+
// the sender by name without adapter-specific fields on IncomingMessage.
|
|
18949
|
+
{
|
|
18950
|
+
channelUser: {
|
|
18951
|
+
channelId: "telegram",
|
|
18952
|
+
userId: String(ctx.from.id),
|
|
18953
|
+
displayName: fromName,
|
|
18954
|
+
username: ctx.from.username
|
|
18955
|
+
}
|
|
18956
|
+
}
|
|
18957
|
+
).catch((err) => log29.error({ err }, "handleMessage error"));
|
|
17941
18958
|
});
|
|
17942
18959
|
this.bot.on("message:photo", async (ctx) => {
|
|
17943
18960
|
const threadId = ctx.message.message_thread_id;
|
|
@@ -18901,6 +19918,7 @@ var init_core_plugins = __esm({
|
|
|
18901
19918
|
"src/plugins/core-plugins.ts"() {
|
|
18902
19919
|
"use strict";
|
|
18903
19920
|
init_security();
|
|
19921
|
+
init_identity();
|
|
18904
19922
|
init_file_service2();
|
|
18905
19923
|
init_context();
|
|
18906
19924
|
init_speech();
|
|
@@ -18912,6 +19930,8 @@ var init_core_plugins = __esm({
|
|
|
18912
19930
|
corePlugins = [
|
|
18913
19931
|
// Service plugins (no adapter dependencies)
|
|
18914
19932
|
security_default,
|
|
19933
|
+
identity_default,
|
|
19934
|
+
// Must boot after security (blocked users rejected before identity records are created)
|
|
18915
19935
|
file_service_default,
|
|
18916
19936
|
context_default,
|
|
18917
19937
|
speech_default,
|
|
@@ -21141,16 +22161,16 @@ var init_prompt_queue = __esm({
|
|
|
21141
22161
|
* immediately. Otherwise, it's buffered and the returned promise resolves
|
|
21142
22162
|
* only after the prompt finishes processing.
|
|
21143
22163
|
*/
|
|
21144
|
-
async enqueue(text5, attachments, routing, turnId) {
|
|
22164
|
+
async enqueue(text5, attachments, routing, turnId, meta) {
|
|
21145
22165
|
if (this.processing) {
|
|
21146
22166
|
return new Promise((resolve9) => {
|
|
21147
|
-
this.queue.push({ text: text5, attachments, routing, turnId, resolve: resolve9 });
|
|
22167
|
+
this.queue.push({ text: text5, attachments, routing, turnId, meta, resolve: resolve9 });
|
|
21148
22168
|
});
|
|
21149
22169
|
}
|
|
21150
|
-
await this.process(text5, attachments, routing, turnId);
|
|
22170
|
+
await this.process(text5, attachments, routing, turnId, meta);
|
|
21151
22171
|
}
|
|
21152
22172
|
/** Run a single prompt through the processor, then drain the next queued item. */
|
|
21153
|
-
async process(text5, attachments, routing, turnId) {
|
|
22173
|
+
async process(text5, attachments, routing, turnId, meta) {
|
|
21154
22174
|
this.processing = true;
|
|
21155
22175
|
this.abortController = new AbortController();
|
|
21156
22176
|
const { signal } = this.abortController;
|
|
@@ -21160,7 +22180,7 @@ var init_prompt_queue = __esm({
|
|
|
21160
22180
|
});
|
|
21161
22181
|
try {
|
|
21162
22182
|
await Promise.race([
|
|
21163
|
-
this.processor(text5, attachments, routing, turnId),
|
|
22183
|
+
this.processor(text5, attachments, routing, turnId, meta),
|
|
21164
22184
|
new Promise((_, reject) => {
|
|
21165
22185
|
signal.addEventListener("abort", () => reject(new Error("Prompt aborted")), { once: true });
|
|
21166
22186
|
})
|
|
@@ -21181,7 +22201,7 @@ var init_prompt_queue = __esm({
|
|
|
21181
22201
|
drainNext() {
|
|
21182
22202
|
const next = this.queue.shift();
|
|
21183
22203
|
if (next) {
|
|
21184
|
-
this.process(next.text, next.attachments, next.routing, next.turnId).then(next.resolve);
|
|
22204
|
+
this.process(next.text, next.attachments, next.routing, next.turnId, next.meta).then(next.resolve);
|
|
21185
22205
|
}
|
|
21186
22206
|
}
|
|
21187
22207
|
/**
|
|
@@ -21287,10 +22307,10 @@ var init_permission_gate = __esm({
|
|
|
21287
22307
|
});
|
|
21288
22308
|
|
|
21289
22309
|
// src/core/sessions/turn-context.ts
|
|
21290
|
-
import { nanoid as
|
|
22310
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
21291
22311
|
function createTurnContext(sourceAdapterId, responseAdapterId, turnId) {
|
|
21292
22312
|
return {
|
|
21293
|
-
turnId: turnId ??
|
|
22313
|
+
turnId: turnId ?? nanoid5(8),
|
|
21294
22314
|
sourceAdapterId,
|
|
21295
22315
|
responseAdapterId
|
|
21296
22316
|
};
|
|
@@ -21318,7 +22338,7 @@ var init_turn_context = __esm({
|
|
|
21318
22338
|
});
|
|
21319
22339
|
|
|
21320
22340
|
// src/core/sessions/session.ts
|
|
21321
|
-
import { nanoid as
|
|
22341
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
21322
22342
|
import * as fs41 from "fs";
|
|
21323
22343
|
var moduleLog, TTS_PROMPT_INSTRUCTION, TTS_BLOCK_REGEX, TTS_MAX_LENGTH, TTS_TIMEOUT_MS, VALID_TRANSITIONS, Session;
|
|
21324
22344
|
var init_session2 = __esm({
|
|
@@ -21398,7 +22418,7 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
21398
22418
|
pendingContext = null;
|
|
21399
22419
|
constructor(opts) {
|
|
21400
22420
|
super();
|
|
21401
|
-
this.id = opts.id ||
|
|
22421
|
+
this.id = opts.id || nanoid6(12);
|
|
21402
22422
|
this.channelId = opts.channelId;
|
|
21403
22423
|
this.attachedAdapters = [opts.channelId];
|
|
21404
22424
|
this.agentName = opts.agentName;
|
|
@@ -21410,7 +22430,7 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
21410
22430
|
this.log = createSessionLogger(this.id, moduleLog);
|
|
21411
22431
|
this.log.info({ agentName: this.agentName }, "Session created");
|
|
21412
22432
|
this.queue = new PromptQueue(
|
|
21413
|
-
(text5, attachments, routing, turnId) => this.processPrompt(text5, attachments, routing, turnId),
|
|
22433
|
+
(text5, attachments, routing, turnId, meta) => this.processPrompt(text5, attachments, routing, turnId, meta),
|
|
21414
22434
|
(err) => {
|
|
21415
22435
|
this.log.error({ err }, "Prompt execution failed");
|
|
21416
22436
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -21509,19 +22529,20 @@ Additionally, include a [TTS]...[/TTS] block with a spoken-friendly summary of y
|
|
|
21509
22529
|
* then adds it to the PromptQueue. Returns a turnId that callers can use to correlate
|
|
21510
22530
|
* queued/processing events before the prompt actually runs.
|
|
21511
22531
|
*/
|
|
21512
|
-
async enqueuePrompt(text5, attachments, routing, externalTurnId) {
|
|
21513
|
-
const turnId = externalTurnId ??
|
|
22532
|
+
async enqueuePrompt(text5, attachments, routing, externalTurnId, meta) {
|
|
22533
|
+
const turnId = externalTurnId ?? nanoid6(8);
|
|
22534
|
+
const turnMeta = meta ?? { turnId };
|
|
21514
22535
|
if (this.middlewareChain) {
|
|
21515
|
-
const payload = { text: text5, attachments, sessionId: this.id, sourceAdapterId: routing?.sourceAdapterId };
|
|
22536
|
+
const payload = { text: text5, attachments, sessionId: this.id, sourceAdapterId: routing?.sourceAdapterId, meta: turnMeta };
|
|
21516
22537
|
const result = await this.middlewareChain.execute(Hook.AGENT_BEFORE_PROMPT, payload, async (p2) => p2);
|
|
21517
22538
|
if (!result) return turnId;
|
|
21518
22539
|
text5 = result.text;
|
|
21519
22540
|
attachments = result.attachments;
|
|
21520
22541
|
}
|
|
21521
|
-
await this.queue.enqueue(text5, attachments, routing, turnId);
|
|
22542
|
+
await this.queue.enqueue(text5, attachments, routing, turnId, turnMeta);
|
|
21522
22543
|
return turnId;
|
|
21523
22544
|
}
|
|
21524
|
-
async processPrompt(text5, attachments, routing, turnId) {
|
|
22545
|
+
async processPrompt(text5, attachments, routing, turnId, meta) {
|
|
21525
22546
|
if (this._status === "finished") return;
|
|
21526
22547
|
this.activeTurnContext = createTurnContext(
|
|
21527
22548
|
routing?.sourceAdapterId ?? this.channelId,
|
|
@@ -21561,6 +22582,13 @@ ${text5}`;
|
|
|
21561
22582
|
if (accumulatorListener) {
|
|
21562
22583
|
this.on(SessionEv.AGENT_EVENT, accumulatorListener);
|
|
21563
22584
|
}
|
|
22585
|
+
const turnTextBuffer = [];
|
|
22586
|
+
const turnTextListener = (event) => {
|
|
22587
|
+
if (event.type === "text" && typeof event.content === "string") {
|
|
22588
|
+
turnTextBuffer.push(event.content);
|
|
22589
|
+
}
|
|
22590
|
+
};
|
|
22591
|
+
this.on(SessionEv.AGENT_EVENT, turnTextListener);
|
|
21564
22592
|
const mw = this.middlewareChain;
|
|
21565
22593
|
const afterEventListener = mw ? (event) => {
|
|
21566
22594
|
mw.execute(Hook.AGENT_AFTER_EVENT, { sessionId: this.id, event, outgoingMessage: { type: "text", text: "" } }, async (e) => e).catch(() => {
|
|
@@ -21570,7 +22598,7 @@ ${text5}`;
|
|
|
21570
22598
|
this.agentInstance.on(SessionEv.AGENT_EVENT, afterEventListener);
|
|
21571
22599
|
}
|
|
21572
22600
|
if (this.middlewareChain) {
|
|
21573
|
-
this.middlewareChain.execute(Hook.TURN_START, { sessionId: this.id, promptText: processed.text, promptNumber: this.promptCount }, async (p2) => p2).catch(() => {
|
|
22601
|
+
this.middlewareChain.execute(Hook.TURN_START, { sessionId: this.id, promptText: processed.text, promptNumber: this.promptCount, turnId: this.activeTurnContext?.turnId ?? turnId ?? "", meta }, async (p2) => p2).catch(() => {
|
|
21574
22602
|
});
|
|
21575
22603
|
}
|
|
21576
22604
|
let stopReason = "end_turn";
|
|
@@ -21596,8 +22624,20 @@ ${text5}`;
|
|
|
21596
22624
|
if (afterEventListener) {
|
|
21597
22625
|
this.agentInstance.off(SessionEv.AGENT_EVENT, afterEventListener);
|
|
21598
22626
|
}
|
|
22627
|
+
this.off(SessionEv.AGENT_EVENT, turnTextListener);
|
|
22628
|
+
const finalTurnId = this.activeTurnContext?.turnId ?? turnId ?? "";
|
|
21599
22629
|
if (this.middlewareChain) {
|
|
21600
|
-
this.middlewareChain.execute(Hook.TURN_END, { sessionId: this.id, stopReason, durationMs: Date.now() - promptStart }, async (p2) => p2).catch(() => {
|
|
22630
|
+
this.middlewareChain.execute(Hook.TURN_END, { sessionId: this.id, stopReason, durationMs: Date.now() - promptStart, turnId: finalTurnId, meta }, async (p2) => p2).catch(() => {
|
|
22631
|
+
});
|
|
22632
|
+
}
|
|
22633
|
+
if (this.middlewareChain) {
|
|
22634
|
+
this.middlewareChain.execute(Hook.AGENT_AFTER_TURN, {
|
|
22635
|
+
sessionId: this.id,
|
|
22636
|
+
turnId: finalTurnId,
|
|
22637
|
+
fullText: turnTextBuffer.join(""),
|
|
22638
|
+
stopReason,
|
|
22639
|
+
meta
|
|
22640
|
+
}, async (p2) => p2).catch(() => {
|
|
21601
22641
|
});
|
|
21602
22642
|
}
|
|
21603
22643
|
this.activeTurnContext = null;
|
|
@@ -23294,8 +24334,7 @@ var init_session_factory = __esm({
|
|
|
23294
24334
|
const payload = {
|
|
23295
24335
|
agentName: params.agentName,
|
|
23296
24336
|
workingDir: params.workingDirectory,
|
|
23297
|
-
userId: "",
|
|
23298
|
-
// userId is not part of SessionCreateParams — resolved upstream
|
|
24337
|
+
userId: params.userId ?? "",
|
|
23299
24338
|
channelId: params.channelId,
|
|
23300
24339
|
threadId: ""
|
|
23301
24340
|
// threadId is assigned after session creation
|
|
@@ -23397,7 +24436,8 @@ var init_session_factory = __esm({
|
|
|
23397
24436
|
this.eventBus.emit(BusEvent.SESSION_CREATED, {
|
|
23398
24437
|
sessionId: session.id,
|
|
23399
24438
|
agent: session.agentName,
|
|
23400
|
-
status: session.status
|
|
24439
|
+
status: session.status,
|
|
24440
|
+
userId: createParams.userId
|
|
23401
24441
|
});
|
|
23402
24442
|
}
|
|
23403
24443
|
return session;
|
|
@@ -24642,11 +25682,55 @@ var init_plugin_storage = __esm({
|
|
|
24642
25682
|
async list() {
|
|
24643
25683
|
return Object.keys(this.readKv());
|
|
24644
25684
|
}
|
|
25685
|
+
async keys(prefix) {
|
|
25686
|
+
const all = Object.keys(this.readKv());
|
|
25687
|
+
return prefix ? all.filter((k) => k.startsWith(prefix)) : all;
|
|
25688
|
+
}
|
|
25689
|
+
async clear() {
|
|
25690
|
+
this.writeChain = this.writeChain.then(() => {
|
|
25691
|
+
this.writeKv({});
|
|
25692
|
+
});
|
|
25693
|
+
return this.writeChain;
|
|
25694
|
+
}
|
|
24645
25695
|
/** Returns the plugin's data directory, creating it lazily on first access. */
|
|
24646
25696
|
getDataDir() {
|
|
24647
25697
|
fs45.mkdirSync(this.dataDir, { recursive: true });
|
|
24648
25698
|
return this.dataDir;
|
|
24649
25699
|
}
|
|
25700
|
+
/**
|
|
25701
|
+
* Creates a namespaced storage instance scoped to a session.
|
|
25702
|
+
* Keys are prefixed with `session:{sessionId}:` to isolate session data
|
|
25703
|
+
* from global plugin storage in the same backing file.
|
|
25704
|
+
*/
|
|
25705
|
+
forSession(sessionId) {
|
|
25706
|
+
const prefix = `session:${sessionId}:`;
|
|
25707
|
+
return {
|
|
25708
|
+
get: (key) => this.get(`${prefix}${key}`),
|
|
25709
|
+
set: (key, value) => this.set(`${prefix}${key}`, value),
|
|
25710
|
+
delete: (key) => this.delete(`${prefix}${key}`),
|
|
25711
|
+
list: async () => {
|
|
25712
|
+
const all = await this.keys(prefix);
|
|
25713
|
+
return all.map((k) => k.slice(prefix.length));
|
|
25714
|
+
},
|
|
25715
|
+
keys: async (p2) => {
|
|
25716
|
+
const full = p2 ? `${prefix}${p2}` : prefix;
|
|
25717
|
+
const all = await this.keys(full);
|
|
25718
|
+
return all.map((k) => k.slice(prefix.length));
|
|
25719
|
+
},
|
|
25720
|
+
clear: async () => {
|
|
25721
|
+
this.writeChain = this.writeChain.then(() => {
|
|
25722
|
+
const data = this.readKv();
|
|
25723
|
+
for (const key of Object.keys(data)) {
|
|
25724
|
+
if (key.startsWith(prefix)) delete data[key];
|
|
25725
|
+
}
|
|
25726
|
+
this.writeKv(data);
|
|
25727
|
+
});
|
|
25728
|
+
return this.writeChain;
|
|
25729
|
+
},
|
|
25730
|
+
getDataDir: () => this.getDataDir(),
|
|
25731
|
+
forSession: (nestedId) => this.forSession(`${sessionId}:${nestedId}`)
|
|
25732
|
+
};
|
|
25733
|
+
}
|
|
24650
25734
|
};
|
|
24651
25735
|
}
|
|
24652
25736
|
});
|
|
@@ -24712,9 +25796,52 @@ function createPluginContext(opts) {
|
|
|
24712
25796
|
requirePermission(permissions, "storage:read", "storage.list");
|
|
24713
25797
|
return storageImpl.list();
|
|
24714
25798
|
},
|
|
25799
|
+
async keys(prefix) {
|
|
25800
|
+
requirePermission(permissions, "storage:read", "storage.keys");
|
|
25801
|
+
return storageImpl.keys(prefix);
|
|
25802
|
+
},
|
|
25803
|
+
async clear() {
|
|
25804
|
+
requirePermission(permissions, "storage:write", "storage.clear");
|
|
25805
|
+
return storageImpl.clear();
|
|
25806
|
+
},
|
|
24715
25807
|
getDataDir() {
|
|
24716
25808
|
requirePermission(permissions, "storage:read", "storage.getDataDir");
|
|
24717
25809
|
return storageImpl.getDataDir();
|
|
25810
|
+
},
|
|
25811
|
+
forSession(sessionId) {
|
|
25812
|
+
requirePermission(permissions, "storage:read", "storage.forSession");
|
|
25813
|
+
const scoped = storageImpl.forSession(sessionId);
|
|
25814
|
+
return {
|
|
25815
|
+
get: (key) => {
|
|
25816
|
+
requirePermission(permissions, "storage:read", "storage.get");
|
|
25817
|
+
return scoped.get(key);
|
|
25818
|
+
},
|
|
25819
|
+
set: (key, value) => {
|
|
25820
|
+
requirePermission(permissions, "storage:write", "storage.set");
|
|
25821
|
+
return scoped.set(key, value);
|
|
25822
|
+
},
|
|
25823
|
+
delete: (key) => {
|
|
25824
|
+
requirePermission(permissions, "storage:write", "storage.delete");
|
|
25825
|
+
return scoped.delete(key);
|
|
25826
|
+
},
|
|
25827
|
+
list: () => {
|
|
25828
|
+
requirePermission(permissions, "storage:read", "storage.list");
|
|
25829
|
+
return scoped.list();
|
|
25830
|
+
},
|
|
25831
|
+
keys: (prefix) => {
|
|
25832
|
+
requirePermission(permissions, "storage:read", "storage.keys");
|
|
25833
|
+
return scoped.keys(prefix);
|
|
25834
|
+
},
|
|
25835
|
+
clear: () => {
|
|
25836
|
+
requirePermission(permissions, "storage:write", "storage.clear");
|
|
25837
|
+
return scoped.clear();
|
|
25838
|
+
},
|
|
25839
|
+
getDataDir: () => {
|
|
25840
|
+
requirePermission(permissions, "storage:read", "storage.getDataDir");
|
|
25841
|
+
return scoped.getDataDir();
|
|
25842
|
+
},
|
|
25843
|
+
forSession: (nestedId) => storage.forSession(`${sessionId}:${nestedId}`)
|
|
25844
|
+
};
|
|
24718
25845
|
}
|
|
24719
25846
|
};
|
|
24720
25847
|
const ctx = {
|
|
@@ -24765,6 +25892,26 @@ function createPluginContext(opts) {
|
|
|
24765
25892
|
await router.send(_sessionId, _content);
|
|
24766
25893
|
}
|
|
24767
25894
|
},
|
|
25895
|
+
notify(target, message, options) {
|
|
25896
|
+
requirePermission(permissions, "notifications:send", "notify()");
|
|
25897
|
+
const svc = serviceRegistry.get("notifications");
|
|
25898
|
+
if (svc?.notifyUser) {
|
|
25899
|
+
svc.notifyUser(target, message, options).catch(() => {
|
|
25900
|
+
});
|
|
25901
|
+
}
|
|
25902
|
+
},
|
|
25903
|
+
defineHook(_name) {
|
|
25904
|
+
},
|
|
25905
|
+
async emitHook(name, payload) {
|
|
25906
|
+
const qualifiedName = `plugin:${pluginName}:${name}`;
|
|
25907
|
+
return middlewareChain.execute(qualifiedName, payload, (p2) => p2);
|
|
25908
|
+
},
|
|
25909
|
+
async getSessionInfo(sessionId) {
|
|
25910
|
+
requirePermission(permissions, "sessions:read", "getSessionInfo()");
|
|
25911
|
+
const sessionMgr = serviceRegistry.get("session-info");
|
|
25912
|
+
if (!sessionMgr) return void 0;
|
|
25913
|
+
return sessionMgr.getSessionInfo(sessionId);
|
|
25914
|
+
},
|
|
24768
25915
|
registerMenuItem(item) {
|
|
24769
25916
|
requirePermission(permissions, "commands:register", "registerMenuItem()");
|
|
24770
25917
|
const menuRegistry = serviceRegistry.get("menu-registry");
|
|
@@ -25622,7 +26769,7 @@ var init_core_items = __esm({
|
|
|
25622
26769
|
|
|
25623
26770
|
// src/core/core.ts
|
|
25624
26771
|
import path51 from "path";
|
|
25625
|
-
import { nanoid as
|
|
26772
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
25626
26773
|
var log44, OpenACPCore;
|
|
25627
26774
|
var init_core = __esm({
|
|
25628
26775
|
"src/core/core.ts"() {
|
|
@@ -25924,7 +27071,7 @@ var init_core = __esm({
|
|
|
25924
27071
|
*
|
|
25925
27072
|
* If no session is found, the user is told to start one with /new.
|
|
25926
27073
|
*/
|
|
25927
|
-
async handleMessage(message) {
|
|
27074
|
+
async handleMessage(message, initialMeta) {
|
|
25928
27075
|
log44.debug(
|
|
25929
27076
|
{
|
|
25930
27077
|
channelId: message.channelId,
|
|
@@ -25933,10 +27080,12 @@ var init_core = __esm({
|
|
|
25933
27080
|
},
|
|
25934
27081
|
"Incoming message"
|
|
25935
27082
|
);
|
|
27083
|
+
const turnId = nanoid7(8);
|
|
27084
|
+
const meta = { turnId, ...initialMeta };
|
|
25936
27085
|
if (this.lifecycleManager?.middlewareChain) {
|
|
25937
27086
|
const result = await this.lifecycleManager.middlewareChain.execute(
|
|
25938
27087
|
Hook.MESSAGE_INCOMING,
|
|
25939
|
-
message,
|
|
27088
|
+
{ ...message, meta },
|
|
25940
27089
|
async (msg) => msg
|
|
25941
27090
|
);
|
|
25942
27091
|
if (!result) return;
|
|
@@ -25988,8 +27137,8 @@ ${text5}`;
|
|
|
25988
27137
|
}
|
|
25989
27138
|
const sourceAdapterId = message.routing?.sourceAdapterId ?? message.channelId;
|
|
25990
27139
|
const routing = sourceAdapterId !== message.routing?.sourceAdapterId ? { ...message.routing, sourceAdapterId } : message.routing;
|
|
27140
|
+
const enrichedMeta = message.meta ?? meta;
|
|
25991
27141
|
if (sourceAdapterId && sourceAdapterId !== "sse" && sourceAdapterId !== "api") {
|
|
25992
|
-
const turnId = nanoid6(8);
|
|
25993
27142
|
this.eventBus.emit(BusEvent.MESSAGE_QUEUED, {
|
|
25994
27143
|
sessionId: session.id,
|
|
25995
27144
|
turnId,
|
|
@@ -25999,11 +27148,51 @@ ${text5}`;
|
|
|
25999
27148
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26000
27149
|
queueDepth: session.queueDepth
|
|
26001
27150
|
});
|
|
26002
|
-
await session.enqueuePrompt(text5, message.attachments, routing, turnId);
|
|
27151
|
+
await session.enqueuePrompt(text5, message.attachments, routing, turnId, enrichedMeta);
|
|
26003
27152
|
} else {
|
|
26004
|
-
await session.enqueuePrompt(text5, message.attachments, routing);
|
|
27153
|
+
await session.enqueuePrompt(text5, message.attachments, routing, turnId, enrichedMeta);
|
|
26005
27154
|
}
|
|
26006
27155
|
}
|
|
27156
|
+
/**
|
|
27157
|
+
* Send a message to a known session, running the full message:incoming → agent:beforePrompt
|
|
27158
|
+
* middleware chain (same as handleMessage) but without the threadId-based session lookup.
|
|
27159
|
+
*
|
|
27160
|
+
* Used by channels that already hold a direct session reference (e.g. SSE adapter), where
|
|
27161
|
+
* looking up by channelId+threadId is unreliable (API sessions may have no threadId).
|
|
27162
|
+
*
|
|
27163
|
+
* @param session The target session — caller is responsible for validating its status.
|
|
27164
|
+
* @param message Sender context and message content.
|
|
27165
|
+
* @param initialMeta Optional adapter-specific context to seed the TurnMeta bag
|
|
27166
|
+
* (e.g. channelUser with display name/username).
|
|
27167
|
+
*/
|
|
27168
|
+
async handleMessageInSession(session, message, initialMeta) {
|
|
27169
|
+
const turnId = nanoid7(8);
|
|
27170
|
+
const meta = { turnId, ...initialMeta };
|
|
27171
|
+
let text5 = message.text;
|
|
27172
|
+
let { attachments } = message;
|
|
27173
|
+
let enrichedMeta = meta;
|
|
27174
|
+
if (this.lifecycleManager?.middlewareChain) {
|
|
27175
|
+
const payload = {
|
|
27176
|
+
channelId: message.channelId,
|
|
27177
|
+
threadId: session.id,
|
|
27178
|
+
userId: message.userId,
|
|
27179
|
+
text: text5,
|
|
27180
|
+
attachments,
|
|
27181
|
+
meta
|
|
27182
|
+
};
|
|
27183
|
+
const result = await this.lifecycleManager.middlewareChain.execute(
|
|
27184
|
+
Hook.MESSAGE_INCOMING,
|
|
27185
|
+
payload,
|
|
27186
|
+
async (p2) => p2
|
|
27187
|
+
);
|
|
27188
|
+
if (!result) return;
|
|
27189
|
+
text5 = result.text;
|
|
27190
|
+
attachments = result.attachments;
|
|
27191
|
+
enrichedMeta = result.meta ?? meta;
|
|
27192
|
+
}
|
|
27193
|
+
const routing = { sourceAdapterId: message.channelId };
|
|
27194
|
+
await session.enqueuePrompt(text5, attachments, routing, turnId, enrichedMeta);
|
|
27195
|
+
}
|
|
26007
27196
|
// --- Unified Session Creation Pipeline ---
|
|
26008
27197
|
/**
|
|
26009
27198
|
* Create (or resume) a session with full wiring: agent, adapter thread, bridge, persistence.
|
|
@@ -26563,7 +27752,7 @@ function registerSessionCommands(registry, _core) {
|
|
|
26563
27752
|
await assistant.enqueuePrompt(prompt);
|
|
26564
27753
|
return { type: "delegated" };
|
|
26565
27754
|
}
|
|
26566
|
-
return { type: "text", text: "Usage: /new
|
|
27755
|
+
return { type: "text", text: "Usage: /new [agent] [workspace]\nOr use the Assistant topic for guided setup." };
|
|
26567
27756
|
}
|
|
26568
27757
|
});
|
|
26569
27758
|
registry.register({
|
|
@@ -26635,7 +27824,7 @@ Prompts: ${session.promptCount}` };
|
|
|
26635
27824
|
registry.register({
|
|
26636
27825
|
name: "resume",
|
|
26637
27826
|
description: "Resume a previous session",
|
|
26638
|
-
usage: "
|
|
27827
|
+
usage: "[session-number]",
|
|
26639
27828
|
category: "system",
|
|
26640
27829
|
handler: async (args2) => {
|
|
26641
27830
|
const assistant = core.assistantManager?.get(args2.channelId);
|
|
@@ -26649,7 +27838,7 @@ Prompts: ${session.promptCount}` };
|
|
|
26649
27838
|
registry.register({
|
|
26650
27839
|
name: "handoff",
|
|
26651
27840
|
description: "Hand off session to another agent",
|
|
26652
|
-
usage: "
|
|
27841
|
+
usage: "[agent-name]",
|
|
26653
27842
|
category: "system",
|
|
26654
27843
|
handler: async (args2) => {
|
|
26655
27844
|
if (!args2.sessionId) return { type: "text", text: "Use /handoff inside a session topic." };
|
|
@@ -26835,7 +28024,7 @@ function registerAgentCommands(registry, _core) {
|
|
|
26835
28024
|
registry.register({
|
|
26836
28025
|
name: "install",
|
|
26837
28026
|
description: "Install an agent",
|
|
26838
|
-
usage: "
|
|
28027
|
+
usage: "[agent-name]",
|
|
26839
28028
|
category: "system",
|
|
26840
28029
|
handler: async (args2) => {
|
|
26841
28030
|
const agentName = args2.raw.trim();
|
|
@@ -26910,7 +28099,7 @@ function registerAdminCommands(registry, _core) {
|
|
|
26910
28099
|
registry.register({
|
|
26911
28100
|
name: "integrate",
|
|
26912
28101
|
description: "Set up a new channel integration",
|
|
26913
|
-
usage: "
|
|
28102
|
+
usage: "[channel]",
|
|
26914
28103
|
category: "system",
|
|
26915
28104
|
handler: async (args2) => {
|
|
26916
28105
|
const channel = args2.raw.trim();
|
|
@@ -27313,7 +28502,7 @@ var init_plugin_field_registry = __esm({
|
|
|
27313
28502
|
|
|
27314
28503
|
// src/core/setup/types.ts
|
|
27315
28504
|
var ONBOARD_SECTION_OPTIONS, CHANNEL_META;
|
|
27316
|
-
var
|
|
28505
|
+
var init_types2 = __esm({
|
|
27317
28506
|
"src/core/setup/types.ts"() {
|
|
27318
28507
|
"use strict";
|
|
27319
28508
|
ONBOARD_SECTION_OPTIONS = [
|
|
@@ -27822,7 +29011,7 @@ var CHANNEL_PLUGIN_NAME;
|
|
|
27822
29011
|
var init_setup_channels = __esm({
|
|
27823
29012
|
"src/core/setup/setup-channels.ts"() {
|
|
27824
29013
|
"use strict";
|
|
27825
|
-
|
|
29014
|
+
init_types2();
|
|
27826
29015
|
init_helpers2();
|
|
27827
29016
|
CHANNEL_PLUGIN_NAME = {
|
|
27828
29017
|
discord: "@openacp/discord-adapter"
|
|
@@ -28480,7 +29669,7 @@ async function runReconfigure(configManager, settingsManager) {
|
|
|
28480
29669
|
var init_wizard = __esm({
|
|
28481
29670
|
"src/core/setup/wizard.ts"() {
|
|
28482
29671
|
"use strict";
|
|
28483
|
-
|
|
29672
|
+
init_types2();
|
|
28484
29673
|
init_helpers2();
|
|
28485
29674
|
init_setup_agents();
|
|
28486
29675
|
init_setup_run_mode();
|
|
@@ -28495,8 +29684,8 @@ var init_wizard = __esm({
|
|
|
28495
29684
|
});
|
|
28496
29685
|
|
|
28497
29686
|
// src/core/setup/index.ts
|
|
28498
|
-
var
|
|
28499
|
-
__export(
|
|
29687
|
+
var setup_exports2 = {};
|
|
29688
|
+
__export(setup_exports2, {
|
|
28500
29689
|
detectAgents: () => detectAgents,
|
|
28501
29690
|
printStartBanner: () => printStartBanner,
|
|
28502
29691
|
runReconfigure: () => runReconfigure,
|
|
@@ -28508,7 +29697,7 @@ __export(setup_exports, {
|
|
|
28508
29697
|
validateBotToken: () => validateBotToken,
|
|
28509
29698
|
validateChatId: () => validateChatId
|
|
28510
29699
|
});
|
|
28511
|
-
var
|
|
29700
|
+
var init_setup2 = __esm({
|
|
28512
29701
|
"src/core/setup/index.ts"() {
|
|
28513
29702
|
"use strict";
|
|
28514
29703
|
init_wizard();
|
|
@@ -28757,7 +29946,7 @@ async function startServer(opts) {
|
|
|
28757
29946
|
const configManager = new ConfigManager(ctx.paths.config);
|
|
28758
29947
|
const configExists = await configManager.exists();
|
|
28759
29948
|
if (!configExists) {
|
|
28760
|
-
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (
|
|
29949
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup2(), setup_exports2));
|
|
28761
29950
|
const shouldStart = await runSetup2(configManager, { settingsManager, pluginRegistry });
|
|
28762
29951
|
if (!shouldStart) process.exit(0);
|
|
28763
29952
|
}
|
|
@@ -28771,7 +29960,7 @@ async function startServer(opts) {
|
|
|
28771
29960
|
}
|
|
28772
29961
|
const isForegroundTTY = !!(process.stdout.isTTY && !process.env.NO_COLOR && config.runMode !== "daemon");
|
|
28773
29962
|
if (isForegroundTTY) {
|
|
28774
|
-
const { printStartBanner: printStartBanner2 } = await Promise.resolve().then(() => (
|
|
29963
|
+
const { printStartBanner: printStartBanner2 } = await Promise.resolve().then(() => (init_setup2(), setup_exports2));
|
|
28775
29964
|
await printStartBanner2();
|
|
28776
29965
|
}
|
|
28777
29966
|
let spinner4;
|
|
@@ -33286,10 +34475,10 @@ async function cmdOnboard(instanceRoot) {
|
|
|
33286
34475
|
const pluginRegistry = new PluginRegistry2(REGISTRY_PATH);
|
|
33287
34476
|
await pluginRegistry.load();
|
|
33288
34477
|
if (await cm.exists()) {
|
|
33289
|
-
const { runReconfigure: runReconfigure2 } = await Promise.resolve().then(() => (
|
|
34478
|
+
const { runReconfigure: runReconfigure2 } = await Promise.resolve().then(() => (init_setup2(), setup_exports2));
|
|
33290
34479
|
await runReconfigure2(cm, settingsManager);
|
|
33291
34480
|
} else {
|
|
33292
|
-
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (
|
|
34481
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup2(), setup_exports2));
|
|
33293
34482
|
await runSetup2(cm, { skipRunMode: true, settingsManager, pluginRegistry, instanceRoot: OPENACP_DIR });
|
|
33294
34483
|
}
|
|
33295
34484
|
}
|
|
@@ -33349,7 +34538,7 @@ async function cmdDefault(command2, instanceRoot) {
|
|
|
33349
34538
|
const settingsManager = new SettingsManager2(pluginsDataDir);
|
|
33350
34539
|
const pluginRegistry = new PluginRegistry2(registryPath);
|
|
33351
34540
|
await pluginRegistry.load();
|
|
33352
|
-
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (
|
|
34541
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_setup2(), setup_exports2));
|
|
33353
34542
|
const shouldStart = await runSetup2(cm, { settingsManager, pluginRegistry, instanceRoot: root });
|
|
33354
34543
|
if (!shouldStart) process.exit(0);
|
|
33355
34544
|
}
|