@primusclaw/personal-agent-runtime 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +145 -20
- package/dist/sqlite/schema.sql +4 -0
- package/dist/types/claw/identity-lookup.d.ts +25 -7
- package/dist/types/claw/index.d.ts +1 -1
- package/dist/types/claw/remote-agents.d.ts +2 -0
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/sqlite/group-context-repository.d.ts +9 -0
- package/dist/types/sqlite/link-repository.d.ts +9 -1
- package/dist/types/sqlite/remote-repository.d.ts +2 -0
- package/dist/types/sqlite/store.d.ts +2 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -529,6 +529,12 @@ var Watchdog = class {
|
|
|
529
529
|
};
|
|
530
530
|
|
|
531
531
|
// packages/runtime/src/claw/remote-agents.ts
|
|
532
|
+
function enabledOf(answer2) {
|
|
533
|
+
const data = anyDataOf(answer2, "the approvals setting");
|
|
534
|
+
const enabled = data !== null && typeof data === "object" ? data["enabled"] : void 0;
|
|
535
|
+
if (typeof enabled !== "boolean") throw new ClawAnswerInvalid4("the approvals setting has no enabled");
|
|
536
|
+
return enabled;
|
|
537
|
+
}
|
|
532
538
|
var HttpRemoteAgents = class {
|
|
533
539
|
constructor(http, clientLabel) {
|
|
534
540
|
this.http = http;
|
|
@@ -543,6 +549,12 @@ var HttpRemoteAgents = class {
|
|
|
543
549
|
const path = options.includeHidden === true ? `/connectors?hidden=${REMOTE_HIDDEN_INCLUDE}` : "/connectors";
|
|
544
550
|
return read(remoteConnectorsSchema, anyDataOf(await this.#call("GET", path, key2), "connectors"), "connectors");
|
|
545
551
|
}
|
|
552
|
+
async approvals(key2) {
|
|
553
|
+
return enabledOf(await this.#call("GET", "/approvals", key2));
|
|
554
|
+
}
|
|
555
|
+
async setApprovals(key2, enabled) {
|
|
556
|
+
return enabledOf(await this.#call("PUT", "/approvals", key2, { enabled }));
|
|
557
|
+
}
|
|
546
558
|
async pair(key2) {
|
|
547
559
|
return read(
|
|
548
560
|
remotePairingSchema,
|
|
@@ -1233,6 +1245,7 @@ function createClawClient(options) {
|
|
|
1233
1245
|
// packages/runtime/src/claw/identity-lookup.ts
|
|
1234
1246
|
import {
|
|
1235
1247
|
ClawUnreachable as ClawUnreachable3,
|
|
1248
|
+
isApiKey as isApiKey3,
|
|
1236
1249
|
pyLower,
|
|
1237
1250
|
pyStrip as pyStrip3,
|
|
1238
1251
|
pyStrOf as pyStrOf3,
|
|
@@ -1240,19 +1253,33 @@ import {
|
|
|
1240
1253
|
} from "@primusclaw/personal-agent";
|
|
1241
1254
|
var HttpIdentityLookup = class {
|
|
1242
1255
|
/** `safeApiUrl` is the platform API without a trailing slash (Settings.safeApiUrl). */
|
|
1243
|
-
constructor(safeApiUrl, fetchImpl = fetch, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
1256
|
+
constructor(safeApiUrl, fetchImpl = fetch, timeoutMs = DEFAULT_TIMEOUT_MS, options = {}) {
|
|
1244
1257
|
this.timeoutMs = timeoutMs;
|
|
1245
|
-
this.
|
|
1258
|
+
this.safe = new ClawHttp(safeApiUrl, fetchImpl);
|
|
1259
|
+
this.claw = options.clawApiUrl ? new ClawHttp(options.clawApiUrl, fetchImpl) : void 0;
|
|
1260
|
+
this.logger = options.logger;
|
|
1246
1261
|
}
|
|
1247
1262
|
timeoutMs;
|
|
1248
|
-
|
|
1263
|
+
safe;
|
|
1264
|
+
claw;
|
|
1265
|
+
logger;
|
|
1249
1266
|
async ownerOfKey(key2) {
|
|
1250
|
-
|
|
1267
|
+
if (isApiKey3(key2)) {
|
|
1268
|
+
const body2 = await this.ask(this.safe, "/users/self", key2, "api_key");
|
|
1269
|
+
return body2 === void 0 ? void 0 : ownerIn(body2);
|
|
1270
|
+
}
|
|
1271
|
+
if (this.claw === void 0) return void 0;
|
|
1272
|
+
const body = await this.ask(this.claw, "/v1/me", key2, "identity_token");
|
|
1273
|
+
return body === void 0 ? void 0 : ownerInMe(body);
|
|
1274
|
+
}
|
|
1275
|
+
/** The JSON answer of `GET what` asked with `key`; undefined on any failure. */
|
|
1276
|
+
async ask(http, what, key2, kind) {
|
|
1251
1277
|
let text3;
|
|
1252
1278
|
try {
|
|
1253
|
-
const response = await
|
|
1279
|
+
const response = await http.send({ method: "GET", path: what, key: key2, timeoutMs: this.timeoutMs });
|
|
1254
1280
|
if (response.status !== 200) {
|
|
1255
1281
|
await response.body?.cancel();
|
|
1282
|
+
this.logger?.info("identity lookup: no owner", { route: what, status: response.status, credential: kind });
|
|
1256
1283
|
return void 0;
|
|
1257
1284
|
}
|
|
1258
1285
|
text3 = await readText(response, what);
|
|
@@ -1260,13 +1287,11 @@ var HttpIdentityLookup = class {
|
|
|
1260
1287
|
if (err5 instanceof ClawUnreachable3) return void 0;
|
|
1261
1288
|
throw err5;
|
|
1262
1289
|
}
|
|
1263
|
-
let body;
|
|
1264
1290
|
try {
|
|
1265
|
-
|
|
1291
|
+
return JSON.parse(text3);
|
|
1266
1292
|
} catch {
|
|
1267
1293
|
return void 0;
|
|
1268
1294
|
}
|
|
1269
|
-
return ownerIn(body);
|
|
1270
1295
|
}
|
|
1271
1296
|
};
|
|
1272
1297
|
function field(user, name) {
|
|
@@ -1285,6 +1310,10 @@ function ownerIn(body) {
|
|
|
1285
1310
|
accountType: pyLower(field(user, "type"))
|
|
1286
1311
|
};
|
|
1287
1312
|
}
|
|
1313
|
+
function ownerInMe(body) {
|
|
1314
|
+
if (!isObject5(body)) return void 0;
|
|
1315
|
+
return { id: field(body, "userId"), name: field(body, "name"), email: field(body, "email"), accountType: "" };
|
|
1316
|
+
}
|
|
1288
1317
|
|
|
1289
1318
|
// packages/runtime/src/claw/key-revoker.ts
|
|
1290
1319
|
import { ClawUnreachable as ClawUnreachable4, pyTruthy as pyTruthy5 } from "@primusclaw/personal-agent";
|
|
@@ -1935,11 +1964,13 @@ import {
|
|
|
1935
1964
|
DEFAULT_MAX_CONCURRENT_TASKS,
|
|
1936
1965
|
DEFAULT_MESSAGE_LIMIT,
|
|
1937
1966
|
DEFAULT_MESSAGE_WINDOW_SECONDS,
|
|
1967
|
+
DEFAULT_GROUP_CONTEXT,
|
|
1938
1968
|
decimalBigInt as decimalBigInt2,
|
|
1939
1969
|
parseLimits
|
|
1940
1970
|
} from "@primusclaw/personal-agent";
|
|
1941
1971
|
var GITHUB_CLI_CLIENT_ID = "178c6fc778ccc68e1d6a";
|
|
1942
1972
|
var ASSISTANT_REPLY_WINDOW_SECONDS = 30 * 60;
|
|
1973
|
+
var MAX_GROUP_CONTEXT_MESSAGES = 200;
|
|
1943
1974
|
function readSettings(env, secrets = /* @__PURE__ */ new Map()) {
|
|
1944
1975
|
const read2 = new EnvReader(env);
|
|
1945
1976
|
const store = { values: secrets, source: secretsSourceOf(env) };
|
|
@@ -1975,6 +2006,7 @@ function readSettings(env, secrets = /* @__PURE__ */ new Map()) {
|
|
|
1975
2006
|
directChat: directChat(read2),
|
|
1976
2007
|
onBehalfUsers: new Set(read2.list("TEAMS_ON_BEHALF_USERS")),
|
|
1977
2008
|
observeUsers: new Set(read2.list("TEAMS_OBSERVE_USERS", true)),
|
|
2009
|
+
groupContext: groupContextSettings(read2),
|
|
1978
2010
|
sensitiveLimits: limitsOf(read2),
|
|
1979
2011
|
linkLimits: {
|
|
1980
2012
|
perUser: read2.positiveInt("TEAMS_LINK_MAX_FAILURES_PER_USER", 5),
|
|
@@ -1989,6 +2021,19 @@ function readSettings(env, secrets = /* @__PURE__ */ new Map()) {
|
|
|
1989
2021
|
secretsSource: store.source
|
|
1990
2022
|
};
|
|
1991
2023
|
}
|
|
2024
|
+
function groupContextSettings(read2) {
|
|
2025
|
+
const messages = read2.int("TEAMS_GROUP_CONTEXT_MESSAGES", String(DEFAULT_GROUP_CONTEXT.messages));
|
|
2026
|
+
if (messages < 0 || messages > MAX_GROUP_CONTEXT_MESSAGES) {
|
|
2027
|
+
throw new ConfigError2(
|
|
2028
|
+
`TEAMS_GROUP_CONTEXT_MESSAGES must be between 0 and ${String(MAX_GROUP_CONTEXT_MESSAGES)}, not ${String(messages)}`
|
|
2029
|
+
);
|
|
2030
|
+
}
|
|
2031
|
+
return {
|
|
2032
|
+
messages,
|
|
2033
|
+
maxAgeSeconds: read2.positiveInt("TEAMS_GROUP_CONTEXT_HOURS", DEFAULT_GROUP_CONTEXT.maxAgeSeconds / 3600) * 3600,
|
|
2034
|
+
messageChars: read2.positiveInt("TEAMS_GROUP_CONTEXT_CHARS", DEFAULT_GROUP_CONTEXT.messageChars)
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
1992
2037
|
function secret(read2, store, name) {
|
|
1993
2038
|
const loaded = store.values.get(name);
|
|
1994
2039
|
if (loaded !== void 0) return loaded;
|
|
@@ -3315,6 +3360,7 @@ var SqliteIdempotencyRepository = class {
|
|
|
3315
3360
|
};
|
|
3316
3361
|
|
|
3317
3362
|
// packages/runtime/src/sqlite/link-repository.ts
|
|
3363
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
3318
3364
|
import {
|
|
3319
3365
|
CODE_ALPHABET,
|
|
3320
3366
|
CODE_LENGTH,
|
|
@@ -3333,8 +3379,12 @@ function forgetRepos(db, userId) {
|
|
|
3333
3379
|
}
|
|
3334
3380
|
|
|
3335
3381
|
// packages/runtime/src/sqlite/link-repository.ts
|
|
3382
|
+
function tokenHash(credential) {
|
|
3383
|
+
return createHash2("sha256").update(credential).digest("hex");
|
|
3384
|
+
}
|
|
3336
3385
|
var PERSON_TABLES = [
|
|
3337
3386
|
"links",
|
|
3387
|
+
"bound_users",
|
|
3338
3388
|
"sessions",
|
|
3339
3389
|
"conversation_refs",
|
|
3340
3390
|
"held_events",
|
|
@@ -3433,6 +3483,7 @@ var SqliteLinkRepository = class {
|
|
|
3433
3483
|
col.bytes(row, "key_enc"),
|
|
3434
3484
|
now
|
|
3435
3485
|
);
|
|
3486
|
+
db.run("DELETE FROM bound_users WHERE aad_id = ?", userId);
|
|
3436
3487
|
forgetNotifySub(db, userId);
|
|
3437
3488
|
forgetRepos(db, userId);
|
|
3438
3489
|
db.run("DELETE FROM sessions WHERE aad_id = ?", userId);
|
|
@@ -3465,12 +3516,39 @@ var SqliteLinkRepository = class {
|
|
|
3465
3516
|
setClawUserId(userId, clawUserId) {
|
|
3466
3517
|
this.ctx.db.run("UPDATE links SET claw_user_id = ? WHERE aad_id = ?", clawUserId, userId);
|
|
3467
3518
|
}
|
|
3519
|
+
boundClawUser(userId, credential) {
|
|
3520
|
+
const row = this.ctx.db.get("SELECT claw_user_id, token_hash FROM bound_users WHERE aad_id = ?", userId);
|
|
3521
|
+
if (row === void 0) return null;
|
|
3522
|
+
return { clawUserId: col.str(row, "claw_user_id"), current: col.str(row, "token_hash") === tokenHash(credential) };
|
|
3523
|
+
}
|
|
3524
|
+
/** Ignored for a linked person (a link's id is read for its key). Another account than before forgets the subscription. */
|
|
3525
|
+
setBoundClawUser(userId, clawUserId, credential) {
|
|
3526
|
+
const { db } = this.ctx;
|
|
3527
|
+
if (db.get("SELECT 1 AS one FROM links WHERE aad_id = ?", userId) !== void 0) return;
|
|
3528
|
+
const before = db.get("SELECT claw_user_id FROM bound_users WHERE aad_id = ?", userId);
|
|
3529
|
+
if (before !== void 0 && col.str(before, "claw_user_id") !== clawUserId) forgetNotifySub(db, userId);
|
|
3530
|
+
db.run(
|
|
3531
|
+
"INSERT OR REPLACE INTO bound_users (aad_id, claw_user_id, token_hash, updated_at) VALUES (?, ?, ?, ?)",
|
|
3532
|
+
userId,
|
|
3533
|
+
clawUserId,
|
|
3534
|
+
tokenHash(credential),
|
|
3535
|
+
this.ctx.now()
|
|
3536
|
+
);
|
|
3537
|
+
}
|
|
3538
|
+
/** Ignored for a linked person: their link keeps its own subscription. */
|
|
3539
|
+
forgetBound(userId) {
|
|
3540
|
+
const { db } = this.ctx;
|
|
3541
|
+
if (db.get("SELECT 1 AS one FROM links WHERE aad_id = ?", userId) !== void 0) return;
|
|
3542
|
+
if (db.run("DELETE FROM bound_users WHERE aad_id = ?", userId) > 0) forgetNotifySub(db, userId);
|
|
3543
|
+
}
|
|
3468
3544
|
needsClawUserId(userId) {
|
|
3469
3545
|
const row = this.ctx.db.get("SELECT claw_user_id FROM links WHERE aad_id = ?", userId);
|
|
3470
3546
|
return row !== void 0 && !col.bool(row, "claw_user_id");
|
|
3471
3547
|
}
|
|
3548
|
+
/** The link's (a linked person's id is never taken from anywhere else), else the bound person's. */
|
|
3472
3549
|
clawUserIdOf(userId) {
|
|
3473
|
-
const
|
|
3550
|
+
const { db } = this.ctx;
|
|
3551
|
+
const row = db.get("SELECT claw_user_id FROM links WHERE aad_id = ?", userId) ?? db.get("SELECT claw_user_id FROM bound_users WHERE aad_id = ?", userId);
|
|
3474
3552
|
return row && col.bool(row, "claw_user_id") ? col.str(row, "claw_user_id") : null;
|
|
3475
3553
|
}
|
|
3476
3554
|
linkedUsers() {
|
|
@@ -3608,6 +3686,15 @@ var ADDED_COLUMNS = [
|
|
|
3608
3686
|
]
|
|
3609
3687
|
],
|
|
3610
3688
|
["notify_subs", [["types_version", "INTEGER NOT NULL DEFAULT 0"]]],
|
|
3689
|
+
[
|
|
3690
|
+
"remote_permissions",
|
|
3691
|
+
[
|
|
3692
|
+
["message_id", "TEXT NOT NULL DEFAULT ''"],
|
|
3693
|
+
// '' = the channel never said the card's id
|
|
3694
|
+
["ask_json", "TEXT NOT NULL DEFAULT ''"]
|
|
3695
|
+
// '' = not a question a terminal asks
|
|
3696
|
+
]
|
|
3697
|
+
],
|
|
3611
3698
|
[
|
|
3612
3699
|
"turn_answers",
|
|
3613
3700
|
[
|
|
@@ -3841,6 +3928,26 @@ var SqliteObserveRepository = class {
|
|
|
3841
3928
|
}
|
|
3842
3929
|
};
|
|
3843
3930
|
|
|
3931
|
+
// packages/runtime/src/sqlite/group-context-repository.ts
|
|
3932
|
+
var SqliteGroupContextRepository = class {
|
|
3933
|
+
constructor(ctx) {
|
|
3934
|
+
this.ctx = ctx;
|
|
3935
|
+
}
|
|
3936
|
+
ctx;
|
|
3937
|
+
contextOff(conversationId) {
|
|
3938
|
+
return this.ctx.db.get("SELECT 1 AS off FROM group_context_off WHERE conversation_id = ?", conversationId) !== void 0;
|
|
3939
|
+
}
|
|
3940
|
+
setContextOff(conversationId, off) {
|
|
3941
|
+
if (off) {
|
|
3942
|
+
this.ctx.db.run(
|
|
3943
|
+
"INSERT INTO group_context_off (conversation_id, updated_at) VALUES (?, ?) ON CONFLICT (conversation_id) DO UPDATE SET updated_at = excluded.updated_at",
|
|
3944
|
+
conversationId,
|
|
3945
|
+
this.ctx.now()
|
|
3946
|
+
);
|
|
3947
|
+
} else this.ctx.db.run("DELETE FROM group_context_off WHERE conversation_id = ?", conversationId);
|
|
3948
|
+
}
|
|
3949
|
+
};
|
|
3950
|
+
|
|
3844
3951
|
// packages/runtime/src/sqlite/people-repository.ts
|
|
3845
3952
|
import {
|
|
3846
3953
|
DEFAULT_QUIET,
|
|
@@ -4044,7 +4151,7 @@ var SqliteRemoteSessionNameRepository = class {
|
|
|
4044
4151
|
);
|
|
4045
4152
|
}
|
|
4046
4153
|
};
|
|
4047
|
-
var PERMISSION_COLUMNS = "session_id, action_id, aad_id, tool, options_json, expires_at, state";
|
|
4154
|
+
var PERMISSION_COLUMNS = "session_id, action_id, aad_id, tool, options_json, expires_at, state, message_id, ask_json";
|
|
4048
4155
|
function permission(row) {
|
|
4049
4156
|
return {
|
|
4050
4157
|
sessionId: col.str(row, "session_id"),
|
|
@@ -4053,9 +4160,20 @@ function permission(row) {
|
|
|
4053
4160
|
tool: col.str(row, "tool"),
|
|
4054
4161
|
options: jsonList(col.str(row, "options_json")),
|
|
4055
4162
|
expiresAt: col.num(row, "expires_at"),
|
|
4056
|
-
state: col.str(row, "state")
|
|
4163
|
+
state: col.str(row, "state"),
|
|
4164
|
+
messageId: col.str(row, "message_id"),
|
|
4165
|
+
ask: askOf(col.str(row, "ask_json"))
|
|
4057
4166
|
};
|
|
4058
4167
|
}
|
|
4168
|
+
function askOf(text3) {
|
|
4169
|
+
if (!text3) return null;
|
|
4170
|
+
try {
|
|
4171
|
+
const value = JSON.parse(text3);
|
|
4172
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
4173
|
+
} catch {
|
|
4174
|
+
return null;
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4059
4177
|
var SqliteRemotePermissionRepository = class {
|
|
4060
4178
|
constructor(ctx) {
|
|
4061
4179
|
this.ctx = ctx;
|
|
@@ -4065,14 +4183,15 @@ var SqliteRemotePermissionRepository = class {
|
|
|
4065
4183
|
const now = this.ctx.now();
|
|
4066
4184
|
this.ctx.db.run("DELETE FROM remote_permissions WHERE expires_at < ?", now - REMOTE_PERMISSION_KEEP_SECONDS);
|
|
4067
4185
|
return this.ctx.db.run(
|
|
4068
|
-
"INSERT OR IGNORE INTO remote_permissions (session_id, action_id, aad_id, tool, options_json, expires_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
4186
|
+
"INSERT OR IGNORE INTO remote_permissions (session_id, action_id, aad_id, tool, options_json, expires_at, created_at, ask_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
4069
4187
|
p.sessionId,
|
|
4070
4188
|
p.actionId,
|
|
4071
4189
|
p.userId,
|
|
4072
4190
|
p.tool,
|
|
4073
4191
|
pyJsonDumps(p.options),
|
|
4074
4192
|
p.expiresAt,
|
|
4075
|
-
now
|
|
4193
|
+
now,
|
|
4194
|
+
p.ask ? JSON.stringify(p.ask) : ""
|
|
4076
4195
|
) === 1;
|
|
4077
4196
|
}
|
|
4078
4197
|
getRemotePermission(sessionId, actionId) {
|
|
@@ -4099,17 +4218,21 @@ var SqliteRemotePermissionRepository = class {
|
|
|
4099
4218
|
);
|
|
4100
4219
|
return new Set(rows.map((r) => col.str(r, "session_id")));
|
|
4101
4220
|
}
|
|
4221
|
+
setRemotePermissionMessage(sessionId, actionId, messageId) {
|
|
4222
|
+
this.ctx.db.run(
|
|
4223
|
+
"UPDATE remote_permissions SET message_id = ? WHERE session_id = ? AND action_id = ?",
|
|
4224
|
+
messageId,
|
|
4225
|
+
sessionId,
|
|
4226
|
+
actionId
|
|
4227
|
+
);
|
|
4228
|
+
}
|
|
4102
4229
|
forgetRemotePermission(sessionId, actionId) {
|
|
4103
4230
|
this.ctx.db.run("DELETE FROM remote_permissions WHERE session_id = ? AND action_id = ?", sessionId, actionId);
|
|
4104
4231
|
}
|
|
4105
4232
|
closeRemotePermission(sessionId, actionId, state, options = {}) {
|
|
4106
|
-
const
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
state,
|
|
4110
|
-
sessionId,
|
|
4111
|
-
actionId
|
|
4112
|
-
) === 1;
|
|
4233
|
+
const from = options.onlyIfState ?? (options.onlyIfPending ?? true ? "pending" : null);
|
|
4234
|
+
const sql = "UPDATE remote_permissions SET state = ? WHERE session_id = ? AND action_id = ?";
|
|
4235
|
+
return from === null ? this.ctx.db.run(sql, state, sessionId, actionId) === 1 : this.ctx.db.run(`${sql} AND state = ?`, state, sessionId, actionId, from) === 1;
|
|
4113
4236
|
}
|
|
4114
4237
|
};
|
|
4115
4238
|
|
|
@@ -4499,6 +4622,7 @@ var SqliteStore = class {
|
|
|
4499
4622
|
notifySubs;
|
|
4500
4623
|
approvals;
|
|
4501
4624
|
observe;
|
|
4625
|
+
groupContext;
|
|
4502
4626
|
assistantCards;
|
|
4503
4627
|
assistantButtons;
|
|
4504
4628
|
assistantMessages;
|
|
@@ -4530,6 +4654,7 @@ var SqliteStore = class {
|
|
|
4530
4654
|
this.notifySubs = new SqliteNotifySubscriptionRepository(ctx);
|
|
4531
4655
|
this.approvals = new SqliteApprovalRepository(ctx);
|
|
4532
4656
|
this.observe = new SqliteObserveRepository(ctx);
|
|
4657
|
+
this.groupContext = new SqliteGroupContextRepository(ctx);
|
|
4533
4658
|
this.assistantCards = new SqliteAssistantCardRepository(ctx);
|
|
4534
4659
|
this.assistantButtons = new SqliteAssistantButtonRepository(ctx);
|
|
4535
4660
|
this.assistantMessages = new SqliteAssistantMessageRepository(ctx);
|
package/dist/sqlite/schema.sql
CHANGED
|
@@ -120,3 +120,7 @@ CREATE TABLE IF NOT EXISTS observe_cards (
|
|
|
120
120
|
tool TEXT NOT NULL, args TEXT NOT NULL, message TEXT NOT NULL DEFAULT '', state TEXT NOT NULL DEFAULT 'open',
|
|
121
121
|
created_at REAL NOT NULL);
|
|
122
122
|
CREATE INDEX IF NOT EXISTS observe_cards_by_suggestion ON observe_cards (aad_id, run_id, seq);
|
|
123
|
+
CREATE TABLE IF NOT EXISTS group_context_off (
|
|
124
|
+
conversation_id TEXT PRIMARY KEY, updated_at REAL NOT NULL);
|
|
125
|
+
CREATE TABLE IF NOT EXISTS bound_users (
|
|
126
|
+
aad_id TEXT PRIMARY KEY, claw_user_id TEXT NOT NULL, token_hash TEXT NOT NULL, updated_at REAL NOT NULL);
|
|
@@ -1,15 +1,33 @@
|
|
|
1
|
-
import { type IdentityLookup, type KeyOwner } from "@primusclaw/personal-agent";
|
|
1
|
+
import { type IdentityLookup, type KeyOwner, type Logger } from "@primusclaw/personal-agent";
|
|
2
2
|
import { type Fetch } from "./http.js";
|
|
3
|
+
/** Where a lookup asks besides the platform, and where it says that an answer was not 200. */
|
|
4
|
+
export interface IdentityLookupOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Claw's API without a trailing slash (Settings.clawApiUrl). An exchanged identity token is asked about here, at
|
|
7
|
+
* `GET /v1/me`: it is a token for Claw's audience, which the platform's `/users/self` refuses (401). Without it a
|
|
8
|
+
* token has no owner.
|
|
9
|
+
*/
|
|
10
|
+
readonly clawApiUrl?: string;
|
|
11
|
+
/** Where an answer other than 200 is noted: its status, the route and the kind of credential, never the credential. */
|
|
12
|
+
readonly logger?: Logger;
|
|
13
|
+
}
|
|
3
14
|
/**
|
|
4
|
-
* Who a
|
|
5
|
-
* `safe.safe_user` and `safe.user_id_of`, which read the same answer).
|
|
6
|
-
*
|
|
7
|
-
*
|
|
15
|
+
* Who a credential belongs to. A person's own key (`ak-...`) is asked of the platform's `/users/self` with the key itself
|
|
16
|
+
* (the Python worker's `safe.whoami`, `safe.safe_user` and `safe.user_id_of`, which read the same answer). An identity
|
|
17
|
+
* token exchanged for them (TEAMS_EXCHANGE_FOR_CHAT) is asked of Claw's `/v1/me` with the token, when Claw's URL is
|
|
18
|
+
* given: the platform does not take such a token on its data routes, and Claw answers with the platform's id, name and
|
|
19
|
+
* email of the token's subject (it has no account type: ""). Any failure is no owner: an answer other than 200, a timeout
|
|
20
|
+
* or a broken connection, a body that is not JSON or not an object. The caller says so, or tries again later (the
|
|
21
|
+
* subscriptions' timer that fills in Claw user ids).
|
|
8
22
|
*/
|
|
9
23
|
export declare class HttpIdentityLookup implements IdentityLookup {
|
|
10
24
|
private readonly timeoutMs;
|
|
11
|
-
private readonly
|
|
25
|
+
private readonly safe;
|
|
26
|
+
private readonly claw;
|
|
27
|
+
private readonly logger;
|
|
12
28
|
/** `safeApiUrl` is the platform API without a trailing slash (Settings.safeApiUrl). */
|
|
13
|
-
constructor(safeApiUrl: string, fetchImpl?: Fetch, timeoutMs?: number);
|
|
29
|
+
constructor(safeApiUrl: string, fetchImpl?: Fetch, timeoutMs?: number, options?: IdentityLookupOptions);
|
|
14
30
|
ownerOfKey(key: string): Promise<KeyOwner | undefined>;
|
|
31
|
+
/** The JSON answer of `GET what` asked with `key`; undefined on any failure. */
|
|
32
|
+
private ask;
|
|
15
33
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { createClawClient, DEFAULT_CLIENT_LABEL, type ClawClientOptions } from "./client.js";
|
|
2
2
|
export type { Fetch } from "./http.js";
|
|
3
|
-
export { HttpIdentityLookup } from "./identity-lookup.js";
|
|
3
|
+
export { HttpIdentityLookup, type IdentityLookupOptions } from "./identity-lookup.js";
|
|
4
4
|
export { HttpKeyRevoker } from "./key-revoker.js";
|
|
5
5
|
export { ruleText } from "./rule.js";
|
|
@@ -10,6 +10,8 @@ export declare class HttpRemoteAgents implements ClawRemoteAgentsPort {
|
|
|
10
10
|
connectors(key: ClawCredential, options?: {
|
|
11
11
|
includeHidden?: boolean;
|
|
12
12
|
}): Promise<RemoteConnector[]>;
|
|
13
|
+
approvals(key: ClawCredential): Promise<boolean>;
|
|
14
|
+
setApprovals(key: ClawCredential, enabled: boolean): Promise<boolean>;
|
|
13
15
|
pair(key: ClawCredential): Promise<RemotePairing>;
|
|
14
16
|
hide(key: ClawCredential, connectorId: string, refs: readonly string[], hidden: boolean): Promise<RemoteHidden>;
|
|
15
17
|
revoke(key: ClawCredential, connectorId: string): Promise<void>;
|
|
@@ -4,6 +4,8 @@ import { type Env } from "./env.js";
|
|
|
4
4
|
export declare const GITHUB_CLI_CLIENT_ID = "178c6fc778ccc68e1d6a";
|
|
5
5
|
/** The default of ASSISTANT_REPLY_WINDOW_SECONDS. */
|
|
6
6
|
export declare const ASSISTANT_REPLY_WINDOW_SECONDS: number;
|
|
7
|
+
/** The most messages TEAMS_GROUP_CONTEXT_MESSAGES may keep per group chat. */
|
|
8
|
+
export declare const MAX_GROUP_CONTEXT_MESSAGES = 200;
|
|
7
9
|
/**
|
|
8
10
|
* The worker's channel-neutral settings from the environment (the Python worker's `Settings.from_env`, without the
|
|
9
11
|
* Teams fields). `secrets` are what the deployment's SecretSource loaded; a name it holds is taken from there and never
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** GroupContextRepository on SQLite (table `group_context_off`: one row per chat that turned the context off). */
|
|
2
|
+
import type { GroupContextRepository } from "@primusclaw/personal-agent";
|
|
3
|
+
import type { StoreContext } from "./context.js";
|
|
4
|
+
export declare class SqliteGroupContextRepository implements GroupContextRepository {
|
|
5
|
+
private readonly ctx;
|
|
6
|
+
constructor(ctx: StoreContext);
|
|
7
|
+
contextOff(conversationId: string): boolean;
|
|
8
|
+
setContextOff(conversationId: string, off: boolean): void;
|
|
9
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** LinkRepository on SQLite (tables `links`, `pending_keys`, `link_attempts`). */
|
|
2
1
|
import { type LinkLimits, type LinkRepository, type RedeemOutcome } from "@primusclaw/personal-agent";
|
|
3
2
|
import { type StoreContext, type SecurityEventSink } from "./context.js";
|
|
4
3
|
export declare class SqliteLinkRepository implements LinkRepository {
|
|
@@ -19,7 +18,16 @@ export declare class SqliteLinkRepository implements LinkRepository {
|
|
|
19
18
|
getKey(userId: string): string | null;
|
|
20
19
|
unlink(userId: string): void;
|
|
21
20
|
setClawUserId(userId: string, clawUserId: string): void;
|
|
21
|
+
boundClawUser(userId: string, credential: string): {
|
|
22
|
+
clawUserId: string;
|
|
23
|
+
current: boolean;
|
|
24
|
+
} | null;
|
|
25
|
+
/** Ignored for a linked person (a link's id is read for its key). Another account than before forgets the subscription. */
|
|
26
|
+
setBoundClawUser(userId: string, clawUserId: string, credential: string): void;
|
|
27
|
+
/** Ignored for a linked person: their link keeps its own subscription. */
|
|
28
|
+
forgetBound(userId: string): void;
|
|
22
29
|
needsClawUserId(userId: string): boolean;
|
|
30
|
+
/** The link's (a linked person's id is never taken from anywhere else), else the bound person's. */
|
|
23
31
|
clawUserIdOf(userId: string): string | null;
|
|
24
32
|
linkedUsers(): string[];
|
|
25
33
|
userForClawUser(clawUserId: string): string | null;
|
|
@@ -17,8 +17,10 @@ export declare class SqliteRemotePermissionRepository implements RemotePermissio
|
|
|
17
17
|
getRemotePermission(sessionId: string, actionId: string): RemotePermission | null;
|
|
18
18
|
pendingRemotePermissions(userId: string, sessionId: string): RemotePermission[];
|
|
19
19
|
waitingRemoteSessions(userId: string): Set<string>;
|
|
20
|
+
setRemotePermissionMessage(sessionId: string, actionId: string, messageId: string): void;
|
|
20
21
|
forgetRemotePermission(sessionId: string, actionId: string): void;
|
|
21
22
|
closeRemotePermission(sessionId: string, actionId: string, state: RemotePermissionState, options?: {
|
|
22
23
|
readonly onlyIfPending?: boolean;
|
|
24
|
+
readonly onlyIfState?: RemotePermissionState;
|
|
23
25
|
}): boolean;
|
|
24
26
|
}
|
|
@@ -13,6 +13,7 @@ import { SqliteLinkRepository } from "./link-repository.js";
|
|
|
13
13
|
import { SqliteMemberDirectoryRepository } from "./member-repository.js";
|
|
14
14
|
import { SqliteNotifySubscriptionRepository } from "./notify-repository.js";
|
|
15
15
|
import { SqliteObserveRepository } from "./observe-repository.js";
|
|
16
|
+
import { SqliteGroupContextRepository } from "./group-context-repository.js";
|
|
16
17
|
import { SqliteOnboardingRepository } from "./onboarding-repository.js";
|
|
17
18
|
import { SqliteConversationRefRepository, SqliteHeldEventRepository, SqlitePrefsRepository } from "./people-repository.js";
|
|
18
19
|
import { type ReencryptCounts } from "./reencrypt.js";
|
|
@@ -45,6 +46,7 @@ export declare class SqliteStore implements StorePort {
|
|
|
45
46
|
readonly notifySubs: SqliteNotifySubscriptionRepository;
|
|
46
47
|
readonly approvals: SqliteApprovalRepository;
|
|
47
48
|
readonly observe: SqliteObserveRepository;
|
|
49
|
+
readonly groupContext: SqliteGroupContextRepository;
|
|
48
50
|
readonly assistantCards: SqliteAssistantCardRepository;
|
|
49
51
|
readonly assistantButtons: SqliteAssistantButtonRepository;
|
|
50
52
|
readonly assistantMessages: SqliteAssistantMessageRepository;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primusclaw/personal-agent-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Node adapters for the personal agent's ports: clock, randomness, logging (and, as the port lands, storage, Claw, metrics, HTTP).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@primusclaw/personal-agent": "0.
|
|
32
|
+
"@primusclaw/personal-agent": "0.3.0",
|
|
33
33
|
"fastify": "^5.12.8",
|
|
34
34
|
"zod": "^4.6.7"
|
|
35
35
|
}
|