forge-openclaw-plugin 0.2.47 → 0.2.49
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/README.md +6 -5
- package/dist/assets/index-2_tuemtU.css +1 -0
- package/dist/assets/index-BAmEvOXb.js +91 -0
- package/dist/assets/index-BAmEvOXb.js.map +1 -0
- package/dist/index.html +2 -2
- package/dist/openclaw/api-client.js +15 -1
- package/dist/openclaw/session-registry.js +17 -0
- package/dist/openclaw/tools.js +1 -1
- package/dist/server/server/migrations/052_agent_identity_tightening.sql +307 -0
- package/dist/server/server/migrations/053_agent_runtime_session_canonical_labels.sql +9 -0
- package/dist/server/server/src/app.js +42 -12
- package/dist/server/server/src/health-workout-adapters.js +465 -0
- package/dist/server/server/src/health.js +134 -9
- package/dist/server/server/src/openapi.js +33 -0
- package/dist/server/server/src/repositories/agent-runtime-sessions.js +122 -16
- package/dist/server/server/src/repositories/habits.js +62 -25
- package/dist/server/server/src/repositories/model-settings.js +5 -0
- package/dist/server/server/src/repositories/settings.js +101 -13
- package/dist/server/server/src/repositories/users.js +23 -0
- package/dist/server/server/src/types.js +22 -6
- package/dist/server/server/src/watch-mobile.js +33 -21
- package/dist/server/src/lib/date-keys.js +21 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +5 -2
- package/server/migrations/052_agent_identity_tightening.sql +307 -0
- package/server/migrations/053_agent_runtime_session_canonical_labels.sql +9 -0
- package/skills/forge-openclaw/SKILL.md +3 -1
- package/skills/forge-openclaw/entity_conversation_playbooks.md +45 -8
- package/skills/forge-openclaw/psyche_entity_playbooks.md +14 -0
- package/dist/assets/index-BejDHw1R.js +0 -91
- package/dist/assets/index-BejDHw1R.js.map +0 -1
- package/dist/assets/index-DtEvFzXp.css +0 -1
|
@@ -11,8 +11,9 @@ import { recordActivityEvent } from "./activity-events.js";
|
|
|
11
11
|
import { filterDeletedEntities, filterDeletedIds, isEntityDeleted } from "./deleted-entities.js";
|
|
12
12
|
import { recordHabitCheckInReward, reverseLatestHabitCheckInReward } from "./rewards.js";
|
|
13
13
|
import { createHabitCheckInSchema, createHabitSchema, habitCheckInSchema, habitSchema, updateHabitSchema } from "../types.js";
|
|
14
|
+
import { formatLocalDateKey } from "../../../src/lib/date-keys.js";
|
|
14
15
|
function todayKey(now = new Date()) {
|
|
15
|
-
return now
|
|
16
|
+
return formatLocalDateKey(now);
|
|
16
17
|
}
|
|
17
18
|
function parseWeekDays(raw) {
|
|
18
19
|
const parsed = JSON.parse(raw);
|
|
@@ -87,25 +88,25 @@ function calculateStreak(habit, checkIns, now = new Date()) {
|
|
|
87
88
|
statusByDate.set(checkIn.dateKey, checkIn.status);
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
const isScheduledOn = (date) => habit.frequency === "daily" || habit.weekDays.includes(date.
|
|
91
|
-
const toDateKey = (date) => date
|
|
92
|
-
const
|
|
91
|
+
const isScheduledOn = (date) => habit.frequency === "daily" || habit.weekDays.includes(date.getDay());
|
|
92
|
+
const toDateKey = (date) => formatLocalDateKey(date);
|
|
93
|
+
const atLocalDayStart = (date) => new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
93
94
|
const previousScheduledDate = (date) => {
|
|
94
|
-
const cursor =
|
|
95
|
+
const cursor = atLocalDayStart(date);
|
|
95
96
|
do {
|
|
96
|
-
cursor.
|
|
97
|
+
cursor.setDate(cursor.getDate() - 1);
|
|
97
98
|
} while (!isScheduledOn(cursor));
|
|
98
99
|
return cursor;
|
|
99
100
|
};
|
|
100
|
-
const
|
|
101
|
-
const start =
|
|
102
|
-
const offset = (start.
|
|
103
|
-
start.
|
|
101
|
+
const startOfLocalWeek = (date) => {
|
|
102
|
+
const start = atLocalDayStart(date);
|
|
103
|
+
const offset = (start.getDay() + 6) % 7;
|
|
104
|
+
start.setDate(start.getDate() - offset);
|
|
104
105
|
return start;
|
|
105
106
|
};
|
|
106
|
-
const
|
|
107
|
-
const start =
|
|
108
|
-
start.
|
|
107
|
+
const previousLocalWeek = (date) => {
|
|
108
|
+
const start = startOfLocalWeek(date);
|
|
109
|
+
start.setDate(start.getDate() - 7);
|
|
109
110
|
return start;
|
|
110
111
|
};
|
|
111
112
|
const alignedStatusOn = (date) => {
|
|
@@ -113,7 +114,7 @@ function calculateStreak(habit, checkIns, now = new Date()) {
|
|
|
113
114
|
return status ? isAligned(habit, { status }) : false;
|
|
114
115
|
};
|
|
115
116
|
if (habit.frequency === "daily") {
|
|
116
|
-
const today =
|
|
117
|
+
const today = atLocalDayStart(now);
|
|
117
118
|
let cursor = isScheduledOn(today) && !statusByDate.has(toDateKey(today))
|
|
118
119
|
? previousScheduledDate(today)
|
|
119
120
|
: today;
|
|
@@ -128,21 +129,21 @@ function calculateStreak(habit, checkIns, now = new Date()) {
|
|
|
128
129
|
let count = 0;
|
|
129
130
|
for (let offset = 0; offset < 7; offset += 1) {
|
|
130
131
|
const day = new Date(weekStart);
|
|
131
|
-
day.
|
|
132
|
+
day.setDate(weekStart.getDate() + offset);
|
|
132
133
|
if (isScheduledOn(day) && alignedStatusOn(day)) {
|
|
133
134
|
count += 1;
|
|
134
135
|
}
|
|
135
136
|
}
|
|
136
137
|
return count;
|
|
137
138
|
};
|
|
138
|
-
const currentWeekStart =
|
|
139
|
+
const currentWeekStart = startOfLocalWeek(now);
|
|
139
140
|
let cursor = alignedCountForWeek(currentWeekStart) >= habit.targetCount
|
|
140
141
|
? currentWeekStart
|
|
141
|
-
:
|
|
142
|
+
: previousLocalWeek(currentWeekStart);
|
|
142
143
|
let streak = 0;
|
|
143
144
|
while (alignedCountForWeek(cursor) >= habit.targetCount) {
|
|
144
145
|
streak += 1;
|
|
145
|
-
cursor =
|
|
146
|
+
cursor = previousLocalWeek(cursor);
|
|
146
147
|
}
|
|
147
148
|
return streak;
|
|
148
149
|
}
|
|
@@ -157,7 +158,7 @@ function isHabitDueToday(habit, latestCheckIn, now = new Date()) {
|
|
|
157
158
|
if (habit.frequency === "daily") {
|
|
158
159
|
return true;
|
|
159
160
|
}
|
|
160
|
-
return habit.weekDays.includes(now.
|
|
161
|
+
return habit.weekDays.includes(now.getDay());
|
|
161
162
|
}
|
|
162
163
|
function mapHabit(row, checkIns = listCheckInsForHabit(row.id)) {
|
|
163
164
|
const latestCheckIn = checkIns[0] ?? null;
|
|
@@ -235,21 +236,28 @@ function sortHabits(habits, orderBy) {
|
|
|
235
236
|
const nextHabits = [...habits];
|
|
236
237
|
nextHabits.sort((left, right) => {
|
|
237
238
|
if (orderBy === "name") {
|
|
238
|
-
return (left.title.localeCompare(right.title, undefined, {
|
|
239
|
-
|
|
239
|
+
return (left.title.localeCompare(right.title, undefined, {
|
|
240
|
+
sensitivity: "base"
|
|
241
|
+
}) || compareDateDesc(left.createdAt, right.createdAt));
|
|
240
242
|
}
|
|
241
243
|
if (orderBy === "streak") {
|
|
242
244
|
return (right.streakCount - left.streakCount ||
|
|
243
245
|
Number(right.dueToday) - Number(left.dueToday) ||
|
|
244
|
-
left.title.localeCompare(right.title, undefined, {
|
|
246
|
+
left.title.localeCompare(right.title, undefined, {
|
|
247
|
+
sensitivity: "base"
|
|
248
|
+
}));
|
|
245
249
|
}
|
|
246
250
|
if (orderBy === "created_at") {
|
|
247
251
|
return (compareDateDesc(left.createdAt, right.createdAt) ||
|
|
248
|
-
left.title.localeCompare(right.title, undefined, {
|
|
252
|
+
left.title.localeCompare(right.title, undefined, {
|
|
253
|
+
sensitivity: "base"
|
|
254
|
+
}));
|
|
249
255
|
}
|
|
250
256
|
if (orderBy === "updated_at") {
|
|
251
257
|
return (compareDateDesc(left.updatedAt, right.updatedAt) ||
|
|
252
|
-
left.title.localeCompare(right.title, undefined, {
|
|
258
|
+
left.title.localeCompare(right.title, undefined, {
|
|
259
|
+
sensitivity: "base"
|
|
260
|
+
}));
|
|
253
261
|
}
|
|
254
262
|
return (Number(right.dueToday) - Number(left.dueToday) ||
|
|
255
263
|
compareDateAsc(left.lastCheckInAt, right.lastCheckInAt) ||
|
|
@@ -367,6 +375,32 @@ export function updateHabit(habitId, input, activity) {
|
|
|
367
375
|
return undefined;
|
|
368
376
|
}
|
|
369
377
|
const parsed = updateHabitSchema.parse(input);
|
|
378
|
+
const shouldApplyEntityPatch = parsed.title !== undefined ||
|
|
379
|
+
parsed.description !== undefined ||
|
|
380
|
+
parsed.status !== undefined ||
|
|
381
|
+
parsed.polarity !== undefined ||
|
|
382
|
+
parsed.frequency !== undefined ||
|
|
383
|
+
parsed.targetCount !== undefined ||
|
|
384
|
+
parsed.weekDays !== undefined ||
|
|
385
|
+
parsed.linkedGoalIds !== undefined ||
|
|
386
|
+
parsed.linkedProjectIds !== undefined ||
|
|
387
|
+
parsed.linkedTaskIds !== undefined ||
|
|
388
|
+
parsed.linkedValueIds !== undefined ||
|
|
389
|
+
parsed.linkedPatternIds !== undefined ||
|
|
390
|
+
parsed.linkedBehaviorIds !== undefined ||
|
|
391
|
+
parsed.linkedBeliefIds !== undefined ||
|
|
392
|
+
parsed.linkedModeIds !== undefined ||
|
|
393
|
+
parsed.linkedReportIds !== undefined ||
|
|
394
|
+
parsed.linkedBehaviorId !== undefined ||
|
|
395
|
+
parsed.rewardXp !== undefined ||
|
|
396
|
+
parsed.penaltyXp !== undefined ||
|
|
397
|
+
parsed.generatedHealthEventTemplate !== undefined ||
|
|
398
|
+
parsed.userId !== undefined;
|
|
399
|
+
if (!shouldApplyEntityPatch) {
|
|
400
|
+
return parsed.checkIn
|
|
401
|
+
? createHabitCheckIn(habitId, parsed.checkIn, activity)
|
|
402
|
+
: current;
|
|
403
|
+
}
|
|
370
404
|
const nextLinkedBehaviorIds = parsed.linkedBehaviorIds !== undefined ||
|
|
371
405
|
parsed.linkedBehaviorId !== undefined
|
|
372
406
|
? normalizeLinkedBehaviorIds({
|
|
@@ -385,7 +419,7 @@ export function updateHabit(habitId, input, activity) {
|
|
|
385
419
|
validateExistingIds(parsed.linkedBeliefIds ?? current.linkedBeliefIds, getBeliefEntryById, "belief_not_found", "Belief");
|
|
386
420
|
validateExistingIds(parsed.linkedModeIds ?? current.linkedModeIds, getModeProfileById, "mode_not_found", "Mode");
|
|
387
421
|
validateExistingIds(parsed.linkedReportIds ?? current.linkedReportIds, getTriggerReportById, "report_not_found", "Report");
|
|
388
|
-
|
|
422
|
+
const updatedHabit = runInTransaction(() => {
|
|
389
423
|
const updatedAt = new Date().toISOString();
|
|
390
424
|
getDatabase()
|
|
391
425
|
.prepare(`UPDATE habits
|
|
@@ -419,6 +453,9 @@ export function updateHabit(habitId, input, activity) {
|
|
|
419
453
|
}
|
|
420
454
|
return habit;
|
|
421
455
|
});
|
|
456
|
+
return parsed.checkIn
|
|
457
|
+
? createHabitCheckIn(habitId, parsed.checkIn, activity) ?? updatedHabit
|
|
458
|
+
: updatedHabit;
|
|
422
459
|
}
|
|
423
460
|
export function deleteHabit(habitId, activity) {
|
|
424
461
|
const current = getHabitById(habitId);
|
|
@@ -47,6 +47,11 @@ export function buildConnectionAgentIdentity(connection) {
|
|
|
47
47
|
id: connection.agentId,
|
|
48
48
|
label: connection.agentLabel,
|
|
49
49
|
agentType: connection.provider,
|
|
50
|
+
identityKey: `model:${connection.id}`,
|
|
51
|
+
provider: null,
|
|
52
|
+
machineKey: null,
|
|
53
|
+
personaKey: connection.provider,
|
|
54
|
+
linkedUsers: [],
|
|
50
55
|
trustLevel: "trusted",
|
|
51
56
|
autonomyMode: "approval_required",
|
|
52
57
|
approvalMode: "approval_by_default",
|
|
@@ -7,6 +7,7 @@ import { recordActivityEvent } from "./activity-events.js";
|
|
|
7
7
|
import { recordEventLog } from "./event-log.js";
|
|
8
8
|
import { resolveGoogleCalendarOauthPublicConfig } from "../services/google-calendar-oauth-config.js";
|
|
9
9
|
import { buildConnectionAgentIdentity, FORGE_DEFAULT_AGENT_ID, listAiModelConnections, syncForgeManagedWikiProfile } from "./model-settings.js";
|
|
10
|
+
import { listUsersByIds } from "./users.js";
|
|
10
11
|
import { agentBootstrapPolicySchema, agentScopePolicySchema, createAgentTokenSchema, legacyAgentBootstrapPolicy, defaultAgentScopePolicy, agentIdentitySchema, customThemeSchema, settingsPayloadSchema, updateSettingsSchema } from "../types.js";
|
|
11
12
|
const settingsFileSchema = settingsPayloadSchema.deepPartial();
|
|
12
13
|
let settingsFileSyncDepth = 0;
|
|
@@ -303,11 +304,40 @@ function pickComparableOverrideSubset(source, template) {
|
|
|
303
304
|
}
|
|
304
305
|
return picked;
|
|
305
306
|
}
|
|
306
|
-
function
|
|
307
|
+
function listAgentIdentityUserLinks(agentIds) {
|
|
308
|
+
if (agentIds.length === 0) {
|
|
309
|
+
return new Map();
|
|
310
|
+
}
|
|
311
|
+
const placeholders = agentIds.map(() => "?").join(",");
|
|
312
|
+
const rows = getDatabase()
|
|
313
|
+
.prepare(`SELECT agent_id, user_id, role
|
|
314
|
+
FROM agent_identity_users
|
|
315
|
+
WHERE agent_id IN (${placeholders})
|
|
316
|
+
ORDER BY role = 'primary' DESC, created_at ASC`)
|
|
317
|
+
.all(...agentIds);
|
|
318
|
+
const usersById = new Map(listUsersByIds(rows.map((row) => row.user_id)).map((user) => [user.id, user]));
|
|
319
|
+
const linksByAgentId = new Map();
|
|
320
|
+
for (const row of rows) {
|
|
321
|
+
const current = linksByAgentId.get(row.agent_id) ?? [];
|
|
322
|
+
current.push({
|
|
323
|
+
userId: row.user_id,
|
|
324
|
+
role: row.role,
|
|
325
|
+
user: usersById.get(row.user_id) ?? null
|
|
326
|
+
});
|
|
327
|
+
linksByAgentId.set(row.agent_id, current);
|
|
328
|
+
}
|
|
329
|
+
return linksByAgentId;
|
|
330
|
+
}
|
|
331
|
+
function mapAgent(row, linkedUsers = []) {
|
|
307
332
|
return agentIdentitySchema.parse({
|
|
308
333
|
id: row.id,
|
|
309
334
|
label: row.label,
|
|
310
335
|
agentType: row.agent_type,
|
|
336
|
+
identityKey: row.identity_key,
|
|
337
|
+
provider: row.provider,
|
|
338
|
+
machineKey: row.machine_key,
|
|
339
|
+
personaKey: row.persona_key,
|
|
340
|
+
linkedUsers,
|
|
311
341
|
trustLevel: row.trust_level,
|
|
312
342
|
autonomyMode: row.autonomy_mode,
|
|
313
343
|
approvalMode: row.approval_mode,
|
|
@@ -351,6 +381,10 @@ function findAgentIdentity(agentId) {
|
|
|
351
381
|
agent_identities.id,
|
|
352
382
|
agent_identities.label,
|
|
353
383
|
agent_identities.agent_type,
|
|
384
|
+
agent_identities.identity_key,
|
|
385
|
+
agent_identities.provider,
|
|
386
|
+
agent_identities.machine_key,
|
|
387
|
+
agent_identities.persona_key,
|
|
354
388
|
agent_identities.trust_level,
|
|
355
389
|
agent_identities.autonomy_mode,
|
|
356
390
|
agent_identities.approval_mode,
|
|
@@ -358,36 +392,76 @@ function findAgentIdentity(agentId) {
|
|
|
358
392
|
agent_identities.created_at,
|
|
359
393
|
agent_identities.updated_at,
|
|
360
394
|
COUNT(agent_tokens.id) AS token_count,
|
|
361
|
-
COALESCE(SUM(CASE WHEN agent_tokens.revoked_at IS NULL THEN 1 ELSE 0 END), 0) AS active_token_count
|
|
395
|
+
COALESCE(SUM(CASE WHEN agent_tokens.id IS NOT NULL AND agent_tokens.revoked_at IS NULL THEN 1 ELSE 0 END), 0) AS active_token_count
|
|
362
396
|
FROM agent_identities
|
|
363
397
|
LEFT JOIN agent_tokens ON agent_tokens.agent_id = agent_identities.id
|
|
364
398
|
WHERE agent_identities.id = ?
|
|
365
399
|
GROUP BY agent_identities.id`)
|
|
366
400
|
.get(agentId);
|
|
367
|
-
|
|
401
|
+
const links = row ? listAgentIdentityUserLinks([row.id]) : new Map();
|
|
402
|
+
return row ? mapAgent(row, links.get(row.id) ?? []) : undefined;
|
|
403
|
+
}
|
|
404
|
+
function normalizeAgentIdentityPart(value) {
|
|
405
|
+
return value
|
|
406
|
+
?.trim()
|
|
407
|
+
.toLowerCase()
|
|
408
|
+
.replace(/[^a-z0-9._:]+/g, "_")
|
|
409
|
+
.replace(/^_+|_+$/g, "") || "";
|
|
410
|
+
}
|
|
411
|
+
function runtimeProviderFromAgentType(agentType) {
|
|
412
|
+
const normalized = normalizeAgentIdentityPart(agentType);
|
|
413
|
+
if (normalized === "openclaw" ||
|
|
414
|
+
normalized === "hermes" ||
|
|
415
|
+
normalized === "codex") {
|
|
416
|
+
return normalized;
|
|
417
|
+
}
|
|
418
|
+
return null;
|
|
419
|
+
}
|
|
420
|
+
function deriveTokenAgentIdentityFields(input) {
|
|
421
|
+
const provider = runtimeProviderFromAgentType(input.agentType);
|
|
422
|
+
if (!provider) {
|
|
423
|
+
return {
|
|
424
|
+
identityKey: null,
|
|
425
|
+
provider: null,
|
|
426
|
+
machineKey: null,
|
|
427
|
+
personaKey: null
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
return {
|
|
431
|
+
identityKey: `runtime:${provider}:token:default`,
|
|
432
|
+
provider,
|
|
433
|
+
machineKey: "token",
|
|
434
|
+
personaKey: "default"
|
|
435
|
+
};
|
|
368
436
|
}
|
|
369
437
|
function upsertAgentIdentity(input) {
|
|
370
438
|
const now = new Date().toISOString();
|
|
439
|
+
const identityFields = deriveTokenAgentIdentityFields(input);
|
|
371
440
|
const existing = getDatabase()
|
|
372
441
|
.prepare(`SELECT id
|
|
373
442
|
FROM agent_identities
|
|
374
|
-
WHERE
|
|
443
|
+
WHERE (? IS NOT NULL AND identity_key = ?)
|
|
444
|
+
OR lower(label) = lower(?)
|
|
375
445
|
LIMIT 1`)
|
|
376
|
-
.get(input.agentLabel);
|
|
446
|
+
.get(identityFields.identityKey, identityFields.identityKey, input.agentLabel);
|
|
377
447
|
if (existing) {
|
|
378
448
|
getDatabase()
|
|
379
449
|
.prepare(`UPDATE agent_identities
|
|
380
|
-
SET agent_type = ?,
|
|
450
|
+
SET agent_type = ?, identity_key = COALESCE(identity_key, ?),
|
|
451
|
+
provider = COALESCE(provider, ?), machine_key = COALESCE(machine_key, ?),
|
|
452
|
+
persona_key = COALESCE(persona_key, ?), trust_level = ?,
|
|
453
|
+
autonomy_mode = ?, approval_mode = ?, description = ?, updated_at = ?
|
|
381
454
|
WHERE id = ?`)
|
|
382
|
-
.run(input.agentType, input.trustLevel, input.autonomyMode, input.approvalMode, input.description, now, existing.id);
|
|
455
|
+
.run(input.agentType, identityFields.identityKey, identityFields.provider, identityFields.machineKey, identityFields.personaKey, input.trustLevel, input.autonomyMode, input.approvalMode, input.description, now, existing.id);
|
|
383
456
|
return findAgentIdentity(existing.id);
|
|
384
457
|
}
|
|
385
458
|
const agentId = `agt_${randomUUID().replaceAll("-", "").slice(0, 10)}`;
|
|
386
459
|
getDatabase()
|
|
387
460
|
.prepare(`INSERT INTO agent_identities (
|
|
388
|
-
id, label, agent_type,
|
|
389
|
-
|
|
390
|
-
|
|
461
|
+
id, label, agent_type, identity_key, provider, machine_key, persona_key,
|
|
462
|
+
trust_level, autonomy_mode, approval_mode, description, created_at, updated_at
|
|
463
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
464
|
+
.run(agentId, input.agentLabel, input.agentType, identityFields.identityKey, identityFields.provider, identityFields.machineKey, identityFields.personaKey, input.trustLevel, input.autonomyMode, input.approvalMode, input.description, now, now);
|
|
391
465
|
return findAgentIdentity(agentId);
|
|
392
466
|
}
|
|
393
467
|
function ensureSettingsRow(now = new Date().toISOString()) {
|
|
@@ -441,6 +515,10 @@ export function listAgentIdentities() {
|
|
|
441
515
|
agent_identities.id,
|
|
442
516
|
agent_identities.label,
|
|
443
517
|
agent_identities.agent_type,
|
|
518
|
+
agent_identities.identity_key,
|
|
519
|
+
agent_identities.provider,
|
|
520
|
+
agent_identities.machine_key,
|
|
521
|
+
agent_identities.persona_key,
|
|
444
522
|
agent_identities.trust_level,
|
|
445
523
|
agent_identities.autonomy_mode,
|
|
446
524
|
agent_identities.approval_mode,
|
|
@@ -448,19 +526,25 @@ export function listAgentIdentities() {
|
|
|
448
526
|
agent_identities.created_at,
|
|
449
527
|
agent_identities.updated_at,
|
|
450
528
|
COUNT(agent_tokens.id) AS token_count,
|
|
451
|
-
COALESCE(SUM(CASE WHEN agent_tokens.revoked_at IS NULL THEN 1 ELSE 0 END), 0) AS active_token_count
|
|
529
|
+
COALESCE(SUM(CASE WHEN agent_tokens.id IS NOT NULL AND agent_tokens.revoked_at IS NULL THEN 1 ELSE 0 END), 0) AS active_token_count
|
|
452
530
|
FROM agent_identities
|
|
453
531
|
LEFT JOIN agent_tokens ON agent_tokens.agent_id = agent_identities.id
|
|
454
532
|
GROUP BY agent_identities.id
|
|
455
533
|
ORDER BY agent_identities.created_at DESC`)
|
|
456
534
|
.all();
|
|
457
|
-
const
|
|
535
|
+
const links = listAgentIdentityUserLinks(rows.map((row) => row.id));
|
|
536
|
+
const manualAgents = rows.map((row) => mapAgent(row, links.get(row.id) ?? []));
|
|
458
537
|
const modelAgents = listAiModelConnections().map(buildConnectionAgentIdentity);
|
|
459
538
|
const settings = readSettingsRow();
|
|
460
539
|
const forgeAgent = agentIdentitySchema.parse({
|
|
461
540
|
id: FORGE_DEFAULT_AGENT_ID,
|
|
462
541
|
label: "Forge Agent",
|
|
463
542
|
agentType: "forge_default",
|
|
543
|
+
identityKey: "forge:default",
|
|
544
|
+
provider: null,
|
|
545
|
+
machineKey: null,
|
|
546
|
+
personaKey: "default",
|
|
547
|
+
linkedUsers: [],
|
|
464
548
|
trustLevel: "trusted",
|
|
465
549
|
autonomyMode: "approval_required",
|
|
466
550
|
approvalMode: "approval_by_default",
|
|
@@ -470,7 +554,11 @@ export function listAgentIdentities() {
|
|
|
470
554
|
createdAt: settings.created_at,
|
|
471
555
|
updatedAt: settings.updated_at
|
|
472
556
|
});
|
|
473
|
-
|
|
557
|
+
const deduped = new Map();
|
|
558
|
+
for (const agent of [forgeAgent, ...modelAgents, ...manualAgents]) {
|
|
559
|
+
deduped.set(agent.identityKey ?? agent.id, agent);
|
|
560
|
+
}
|
|
561
|
+
return Array.from(deduped.values());
|
|
474
562
|
}
|
|
475
563
|
export function isPsycheAuthRequired() {
|
|
476
564
|
ensureSettingsRow();
|
|
@@ -153,6 +153,29 @@ export function ensureSystemUsers() {
|
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
155
|
}
|
|
156
|
+
export function ensureBotUser(input) {
|
|
157
|
+
const parsed = createUserSchema.parse({
|
|
158
|
+
kind: "bot",
|
|
159
|
+
handle: normalizeHandle(input.handle),
|
|
160
|
+
displayName: input.displayName,
|
|
161
|
+
description: input.description,
|
|
162
|
+
accentColor: input.accentColor
|
|
163
|
+
});
|
|
164
|
+
const now = new Date().toISOString();
|
|
165
|
+
getDatabase()
|
|
166
|
+
.prepare(`INSERT INTO users (id, kind, handle, display_name, description, accent_color, created_at, updated_at)
|
|
167
|
+
VALUES (?, 'bot', ?, ?, ?, ?, ?, ?)
|
|
168
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
169
|
+
kind = 'bot',
|
|
170
|
+
handle = excluded.handle,
|
|
171
|
+
display_name = excluded.display_name,
|
|
172
|
+
description = excluded.description,
|
|
173
|
+
accent_color = excluded.accent_color,
|
|
174
|
+
updated_at = excluded.updated_at`)
|
|
175
|
+
.run(input.id, parsed.handle, parsed.displayName, parsed.description, parsed.accentColor, now, now);
|
|
176
|
+
ensurePermissiveGrantsForUser(input.id, now);
|
|
177
|
+
return getUserById(input.id);
|
|
178
|
+
}
|
|
156
179
|
function ensurePermissiveGrantsForUser(userId, now) {
|
|
157
180
|
const existingUsers = listUsers();
|
|
158
181
|
for (const otherUser of existingUsers) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { LEGACY_WORKBENCH_PORT_KINDS, WORKBENCH_PORT_KINDS, normalizeWorkbenchPortKind } from "../../src/lib/workbench/nodes.js";
|
|
3
|
+
import { formatLocalDateKey } from "../../src/lib/date-keys.js";
|
|
3
4
|
export const taskStatusSchema = z.enum([
|
|
4
5
|
"backlog",
|
|
5
6
|
"focus",
|
|
@@ -2103,6 +2104,15 @@ export const agentIdentitySchema = z.object({
|
|
|
2103
2104
|
id: z.string(),
|
|
2104
2105
|
label: z.string(),
|
|
2105
2106
|
agentType: z.string(),
|
|
2107
|
+
identityKey: z.string().nullable().default(null),
|
|
2108
|
+
provider: agentRuntimeProviderSchema.nullable().default(null),
|
|
2109
|
+
machineKey: z.string().nullable().default(null),
|
|
2110
|
+
personaKey: z.string().nullable().default(null),
|
|
2111
|
+
linkedUsers: z.array(z.object({
|
|
2112
|
+
userId: z.string(),
|
|
2113
|
+
role: z.string(),
|
|
2114
|
+
user: userSummarySchema.nullable().default(null)
|
|
2115
|
+
})).default([]),
|
|
2106
2116
|
trustLevel: agentTrustLevelSchema,
|
|
2107
2117
|
autonomyMode: autonomyModeSchema,
|
|
2108
2118
|
approvalMode: approvalModeSchema,
|
|
@@ -2187,6 +2197,10 @@ export const createAgentRuntimeSessionSchema = z.object({
|
|
|
2187
2197
|
provider: agentRuntimeProviderSchema,
|
|
2188
2198
|
agentLabel: nonEmptyTrimmedString,
|
|
2189
2199
|
agentType: trimmedString.default("assistant"),
|
|
2200
|
+
agentIdentityKey: trimmedString.optional(),
|
|
2201
|
+
machineKey: trimmedString.optional(),
|
|
2202
|
+
personaKey: trimmedString.optional(),
|
|
2203
|
+
linkedUserIds: uniqueStringArraySchema.default([]),
|
|
2190
2204
|
actorLabel: nonEmptyTrimmedString,
|
|
2191
2205
|
sessionKey: nonEmptyTrimmedString,
|
|
2192
2206
|
sessionLabel: trimmedString.default(""),
|
|
@@ -3120,6 +3134,12 @@ export const taskMutationShape = {
|
|
|
3120
3134
|
notes: z.array(nestedCreateNoteSchema).default([])
|
|
3121
3135
|
};
|
|
3122
3136
|
export const createTaskSchema = z.object(taskMutationShape);
|
|
3137
|
+
const habitCheckInWriteSchema = z.object({
|
|
3138
|
+
dateKey: dateOnlySchema.default(() => formatLocalDateKey()),
|
|
3139
|
+
status: habitCheckInStatusSchema,
|
|
3140
|
+
note: trimmedString.default(""),
|
|
3141
|
+
description: trimmedString.optional()
|
|
3142
|
+
});
|
|
3123
3143
|
const habitMutationShape = {
|
|
3124
3144
|
title: nonEmptyTrimmedString,
|
|
3125
3145
|
description: trimmedString.default(""),
|
|
@@ -3206,6 +3226,7 @@ export const updateHabitSchema = z
|
|
|
3206
3226
|
linkedBehaviorId: nonEmptyTrimmedString.nullable().optional(),
|
|
3207
3227
|
rewardXp: z.number().int().min(1).max(100).optional(),
|
|
3208
3228
|
penaltyXp: z.number().int().min(1).max(100).optional(),
|
|
3229
|
+
checkIn: habitCheckInWriteSchema.optional(),
|
|
3209
3230
|
generatedHealthEventTemplate: z
|
|
3210
3231
|
.object({
|
|
3211
3232
|
enabled: z.boolean().optional(),
|
|
@@ -3371,12 +3392,7 @@ export const taskRunFinishSchema = z.object({
|
|
|
3371
3392
|
export const taskRunFocusSchema = z.object({
|
|
3372
3393
|
actor: nonEmptyTrimmedString.optional()
|
|
3373
3394
|
});
|
|
3374
|
-
export const createHabitCheckInSchema =
|
|
3375
|
-
dateKey: dateOnlySchema.default(new Date().toISOString().slice(0, 10)),
|
|
3376
|
-
status: habitCheckInStatusSchema,
|
|
3377
|
-
note: trimmedString.default(""),
|
|
3378
|
-
description: trimmedString.optional()
|
|
3379
|
-
});
|
|
3395
|
+
export const createHabitCheckInSchema = habitCheckInWriteSchema;
|
|
3380
3396
|
export const updateSettingsSchema = z.object({
|
|
3381
3397
|
profile: z
|
|
3382
3398
|
.object({
|
|
@@ -5,6 +5,7 @@ import { HttpError } from "./errors.js";
|
|
|
5
5
|
import { updateWorkoutMetadata } from "./health.js";
|
|
6
6
|
import { canonicalizeMovementCategoryTags, listMovementPlaces, normalizeMovementCategoryTag, updateMovementPlace } from "./movement.js";
|
|
7
7
|
import { listHabits } from "./repositories/habits.js";
|
|
8
|
+
import { formatLocalDateKey } from "../../src/lib/date-keys.js";
|
|
8
9
|
const watchCapability = "watch-ready";
|
|
9
10
|
const watchHistoryStateSchema = z.enum(["aligned", "unaligned", "unknown"]);
|
|
10
11
|
const watchPromptKindSchema = z.enum([
|
|
@@ -56,7 +57,11 @@ export const mobileWatchHabitCheckInSchema = z.object({
|
|
|
56
57
|
sessionId: z.string().trim().min(1),
|
|
57
58
|
pairingToken: z.string().trim().min(1),
|
|
58
59
|
dedupeKey: z.string().trim().min(1),
|
|
59
|
-
dateKey: z
|
|
60
|
+
dateKey: z
|
|
61
|
+
.string()
|
|
62
|
+
.trim()
|
|
63
|
+
.min(1)
|
|
64
|
+
.default(() => formatLocalDateKey()),
|
|
60
65
|
status: z.enum(["done", "missed"]),
|
|
61
66
|
note: z.string().trim().default(""),
|
|
62
67
|
description: z.string().trim().optional()
|
|
@@ -82,24 +87,24 @@ function nowIso() {
|
|
|
82
87
|
return new Date().toISOString();
|
|
83
88
|
}
|
|
84
89
|
function formatDateKey(date) {
|
|
85
|
-
return date
|
|
90
|
+
return formatLocalDateKey(date);
|
|
86
91
|
}
|
|
87
92
|
function parseDateKey(dateKey) {
|
|
88
93
|
const [year, month, day] = dateKey.split("-").map(Number);
|
|
89
|
-
return new Date(
|
|
94
|
+
return new Date(year, month - 1, day);
|
|
90
95
|
}
|
|
91
|
-
function
|
|
92
|
-
return new Date(
|
|
96
|
+
function startOfLocalDay(date) {
|
|
97
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
93
98
|
}
|
|
94
|
-
function
|
|
99
|
+
function addLocalDays(date, days) {
|
|
95
100
|
const next = new Date(date);
|
|
96
|
-
next.
|
|
101
|
+
next.setDate(next.getDate() + days);
|
|
97
102
|
return next;
|
|
98
103
|
}
|
|
99
|
-
function
|
|
100
|
-
const start =
|
|
101
|
-
const offset = (start.
|
|
102
|
-
start.
|
|
104
|
+
function startOfLocalWeek(date) {
|
|
105
|
+
const start = startOfLocalDay(date);
|
|
106
|
+
const offset = (start.getDay() + 6) % 7;
|
|
107
|
+
start.setDate(start.getDate() - offset);
|
|
103
108
|
return start;
|
|
104
109
|
}
|
|
105
110
|
function isAlignedCheckIn(habit, status) {
|
|
@@ -125,15 +130,15 @@ function buildHabitHistory(habit, options) {
|
|
|
125
130
|
? parseDateKey(options.anchorDateKey)
|
|
126
131
|
: new Date();
|
|
127
132
|
if (habit.frequency === "daily") {
|
|
128
|
-
const today =
|
|
133
|
+
const today = startOfLocalDay(now);
|
|
129
134
|
return Array.from({ length: 7 }, (_, index) => {
|
|
130
135
|
const offset = index - 6;
|
|
131
|
-
const date =
|
|
136
|
+
const date = addLocalDays(today, offset);
|
|
132
137
|
const dateKey = formatDateKey(date);
|
|
133
138
|
const checkIn = habit.checkIns.find((entry) => entry.dateKey === dateKey) ?? null;
|
|
134
139
|
return {
|
|
135
140
|
id: dateKey,
|
|
136
|
-
label: ["S", "M", "T", "W", "T", "F", "S"][date.
|
|
141
|
+
label: ["S", "M", "T", "W", "T", "F", "S"][date.getDay()],
|
|
137
142
|
periodKey: dateKey,
|
|
138
143
|
current: offset === 0,
|
|
139
144
|
state: checkIn
|
|
@@ -144,13 +149,13 @@ function buildHabitHistory(habit, options) {
|
|
|
144
149
|
};
|
|
145
150
|
});
|
|
146
151
|
}
|
|
147
|
-
const thisWeek =
|
|
152
|
+
const thisWeek = startOfLocalWeek(now);
|
|
148
153
|
return Array.from({ length: 7 }, (_, index) => {
|
|
149
154
|
const offset = index - 6;
|
|
150
|
-
const weekStart =
|
|
155
|
+
const weekStart = addLocalDays(thisWeek, offset * 7);
|
|
151
156
|
const weekKey = formatDateKey(weekStart);
|
|
152
157
|
const weekEntries = habit.checkIns.filter((entry) => {
|
|
153
|
-
const entryWeek = formatDateKey(
|
|
158
|
+
const entryWeek = formatDateKey(startOfLocalWeek(parseDateKey(entry.dateKey)));
|
|
154
159
|
return entryWeek === weekKey;
|
|
155
160
|
});
|
|
156
161
|
const alignedCount = weekEntries.filter((entry) => isAlignedCheckIn(habit, entry.status)).length;
|
|
@@ -375,7 +380,9 @@ function projectionForStoredEvent(event) {
|
|
|
375
380
|
const categoryCandidate = Array.isArray(event.payload.categoryTags)
|
|
376
381
|
? event.payload.categoryTags
|
|
377
382
|
: typeof event.payload.category === "string"
|
|
378
|
-
? watchCategoryMap.get(event.payload.category) ?? [
|
|
383
|
+
? (watchCategoryMap.get(event.payload.category) ?? [
|
|
384
|
+
event.payload.category
|
|
385
|
+
])
|
|
379
386
|
: [];
|
|
380
387
|
const categoryTags = canonicalizeMovementCategoryTags(categoryCandidate.flatMap((value) => typeof value === "string" ? [normalizeMovementCategoryTag(value)] : []));
|
|
381
388
|
try {
|
|
@@ -403,12 +410,15 @@ function projectionForStoredEvent(event) {
|
|
|
403
410
|
status: "projection_failed",
|
|
404
411
|
details: {
|
|
405
412
|
reason: "place_update_failed",
|
|
406
|
-
message: error instanceof Error
|
|
413
|
+
message: error instanceof Error
|
|
414
|
+
? error.message
|
|
415
|
+
: "Unknown place update error"
|
|
407
416
|
}
|
|
408
417
|
};
|
|
409
418
|
}
|
|
410
419
|
}
|
|
411
|
-
if (event.eventType === "workout_annotation" &&
|
|
420
|
+
if (event.eventType === "workout_annotation" &&
|
|
421
|
+
event.linkedContext.workoutId) {
|
|
412
422
|
try {
|
|
413
423
|
const workout = updateWorkoutMetadata(event.linkedContext.workoutId, {
|
|
414
424
|
subjectiveEffort: typeof event.payload.subjectiveEffort === "number"
|
|
@@ -449,7 +459,9 @@ function projectionForStoredEvent(event) {
|
|
|
449
459
|
status: "projection_failed",
|
|
450
460
|
details: {
|
|
451
461
|
reason: "workout_update_failed",
|
|
452
|
-
message: error instanceof Error
|
|
462
|
+
message: error instanceof Error
|
|
463
|
+
? error.message
|
|
464
|
+
: "Unknown workout update error"
|
|
453
465
|
}
|
|
454
466
|
};
|
|
455
467
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
function readPart(parts, type) {
|
|
2
|
+
return parts.find((part) => part.type === type)?.value ?? "";
|
|
3
|
+
}
|
|
4
|
+
export function getRuntimeTimeZone() {
|
|
5
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
6
|
+
}
|
|
7
|
+
export function formatDateKeyInTimeZone(date, timeZone) {
|
|
8
|
+
const parts = new Intl.DateTimeFormat("en-CA", {
|
|
9
|
+
timeZone,
|
|
10
|
+
year: "numeric",
|
|
11
|
+
month: "2-digit",
|
|
12
|
+
day: "2-digit"
|
|
13
|
+
}).formatToParts(date);
|
|
14
|
+
const year = readPart(parts, "year");
|
|
15
|
+
const month = readPart(parts, "month");
|
|
16
|
+
const day = readPart(parts, "day");
|
|
17
|
+
return `${year}-${month}-${day}`;
|
|
18
|
+
}
|
|
19
|
+
export function formatLocalDateKey(date = new Date()) {
|
|
20
|
+
return formatDateKeyInTimeZone(date, getRuntimeTimeZone());
|
|
21
|
+
}
|
package/openclaw.plugin.json
CHANGED