@ghostpaw/affinity 0.6.0 → 0.6.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/README.md +19 -6
- package/dist/cli.js +99 -35
- package/dist/index.cjs +69 -26
- package/dist/index.d.cts +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +69 -26
- package/dist/mcp-server.js +86 -27
- package/dist/mcp.cjs +83 -27
- package/dist/mcp.js +83 -27
- package/package.json +1 -1
- package/skills/affinity/references/import-history-without-faking-evidence.md +1 -1
- package/skills/affinity/references/record-direct-evidence-well.md +1 -1
- package/skills/affinity/references/record-observations-and-referrals.md +2 -1
- package/skills/affinity/references/tools.md +7 -6
package/dist/mcp-server.js
CHANGED
|
@@ -296,7 +296,7 @@ var init_mcp_stdio = __esm({
|
|
|
296
296
|
var affinityVersion;
|
|
297
297
|
var init_version = __esm({
|
|
298
298
|
"src/version.ts"() {
|
|
299
|
-
affinityVersion = "0.6.
|
|
299
|
+
affinityVersion = "0.6.1";
|
|
300
300
|
}
|
|
301
301
|
});
|
|
302
302
|
|
|
@@ -767,6 +767,8 @@ function mapEventRowToEventRecord(row, participants) {
|
|
|
767
767
|
momentKind: row.moment_kind,
|
|
768
768
|
participants
|
|
769
769
|
};
|
|
770
|
+
if (row.provenance_json != null)
|
|
771
|
+
base.provenance = JSON.parse(row.provenance_json);
|
|
770
772
|
if (row.content != null) base.content = decodeEventText(row.content);
|
|
771
773
|
if (row.channel != null) base.channel = row.channel;
|
|
772
774
|
if (row.external_source != null && row.external_id != null)
|
|
@@ -1082,14 +1084,9 @@ function parseCalendarTime(value, field = "time", now = Date.now()) {
|
|
|
1082
1084
|
text = text.replace(" ", "T");
|
|
1083
1085
|
if (!/(?:Z|[+-]\d{2}:\d{2})$/.test(text)) text += "Z";
|
|
1084
1086
|
}
|
|
1085
|
-
let rounding = 0;
|
|
1086
1087
|
text = text.replace(
|
|
1087
1088
|
/\.(\d{4,})(?=Z|[+-]\d{2}:\d{2}$)/,
|
|
1088
|
-
(_, digits) => {
|
|
1089
|
-
const ms = Math.round(Number(`0.${digits}`) * 1e3);
|
|
1090
|
-
rounding = ms === 1e3 ? 1 : 0;
|
|
1091
|
-
return `.${String(ms === 1e3 ? 999 : ms).padStart(3, "0")}`;
|
|
1092
|
-
}
|
|
1089
|
+
(_, digits) => `.${digits.slice(0, 3)}`
|
|
1093
1090
|
);
|
|
1094
1091
|
if (/^(today|yesterday|tomorrow|[+-]\d+d)$/.test(text)) {
|
|
1095
1092
|
const offset = text === "today" ? 0 : text === "tomorrow" ? 1 : text === "yesterday" ? -1 : Number(text.slice(0, -1));
|
|
@@ -1109,7 +1106,7 @@ function parseCalendarTime(value, field = "time", now = Date.now()) {
|
|
|
1109
1106
|
const midnight = Date.parse(`${day}T00:00:00Z`);
|
|
1110
1107
|
if (!Number.isFinite(midnight) || new Date(midnight).toISOString().slice(0, 10) !== day)
|
|
1111
1108
|
return fail();
|
|
1112
|
-
const timestamp = dateOnly ? midnight + (field.split(".").at(-1) === "dueAt" ? 86399999 : 0) : Date.parse(text)
|
|
1109
|
+
const timestamp = dateOnly ? midnight + (field.split(".").at(-1) === "dueAt" ? 86399999 : 0) : Date.parse(text);
|
|
1113
1110
|
if (!Number.isFinite(timestamp)) return fail();
|
|
1114
1111
|
return { timestamp, day };
|
|
1115
1112
|
}
|
|
@@ -2289,7 +2286,7 @@ var init_event_metadata_schema = __esm({
|
|
|
2289
2286
|
);
|
|
2290
2287
|
eventMetadataSchema = {
|
|
2291
2288
|
affectsBond: booleanSchema(
|
|
2292
|
-
"Whether evidence affects the relationship; defaults false for observations with significance <=2."
|
|
2289
|
+
"Whether evidence affects the relationship; defaults false for like/follow/repost labels and observations with significance <=2; explicit true opts in."
|
|
2293
2290
|
),
|
|
2294
2291
|
content: stringSchema(
|
|
2295
2292
|
"Verbatim text, including empty text; at most 1 MB UTF-8. Summary may be omitted when content is supplied."
|
|
@@ -2876,6 +2873,13 @@ function validateToolInput(schema, value, path = "input", injectedNow = Date.now
|
|
|
2876
2873
|
`${path}.${key}`,
|
|
2877
2874
|
referenceNow
|
|
2878
2875
|
);
|
|
2876
|
+
if (!path.endsWith(".patch") && ["occurredAt", "dueAt"].includes(key) && typeof object[key] === "string" && /\.\d{4,}/.test(object[key])) {
|
|
2877
|
+
const provenance = object.provenance;
|
|
2878
|
+
object.provenance = {
|
|
2879
|
+
...provenance && typeof provenance === "object" ? provenance : {},
|
|
2880
|
+
[`raw${key[0]?.toUpperCase()}${key.slice(1)}`]: object[key]
|
|
2881
|
+
};
|
|
2882
|
+
}
|
|
2879
2883
|
object[key] = parsed.timestamp;
|
|
2880
2884
|
if (!path.endsWith(".patch") && ["occurredAt", "dueAt"].includes(key))
|
|
2881
2885
|
object[key.replace(/At$/, "On")] ??= parsed.day;
|
|
@@ -2893,7 +2897,7 @@ function validateToolInput(schema, value, path = "input", injectedNow = Date.now
|
|
|
2893
2897
|
const error = validateToolInput(
|
|
2894
2898
|
child,
|
|
2895
2899
|
object[key],
|
|
2896
|
-
`${path}.${key}`,
|
|
2900
|
+
key === "input" && path === "input" ? path : `${path}.${key}`,
|
|
2897
2901
|
referenceNow,
|
|
2898
2902
|
strict
|
|
2899
2903
|
);
|
|
@@ -3408,6 +3412,11 @@ function setAttribute(db, target, name, value) {
|
|
|
3408
3412
|
const stored = normalizeAttributeValue(value);
|
|
3409
3413
|
const now = resolveNow();
|
|
3410
3414
|
const existing = getLiveAttributeRow(db, target, key);
|
|
3415
|
+
if (existing?.value === stored)
|
|
3416
|
+
return buildAttributeMutationReceipt(
|
|
3417
|
+
mapAttributeRowToAttributeRecord(existing),
|
|
3418
|
+
{}
|
|
3419
|
+
);
|
|
3411
3420
|
if (existing) {
|
|
3412
3421
|
db.prepare(
|
|
3413
3422
|
"UPDATE attributes SET value = ?, updated_at = ? WHERE id = ?"
|
|
@@ -3578,7 +3587,30 @@ function linkLocatorSchema2(description) {
|
|
|
3578
3587
|
};
|
|
3579
3588
|
}
|
|
3580
3589
|
function resolveAttributeTarget(db, target) {
|
|
3581
|
-
if (target.kind === "event")
|
|
3590
|
+
if (target.kind === "event") {
|
|
3591
|
+
if (target.eventId === void 0 === (target.ref === void 0))
|
|
3592
|
+
return {
|
|
3593
|
+
kind: "error",
|
|
3594
|
+
result: toolFailure(
|
|
3595
|
+
"protocol",
|
|
3596
|
+
"invalid_input",
|
|
3597
|
+
"Invalid event target.",
|
|
3598
|
+
"Provide exactly one of target.eventId or target.ref."
|
|
3599
|
+
)
|
|
3600
|
+
};
|
|
3601
|
+
const eventId = target.eventId ?? (target.ref && getEventByExternalRef(db, target.ref.source, target.ref.id)?.id);
|
|
3602
|
+
if (eventId === void 0)
|
|
3603
|
+
return {
|
|
3604
|
+
kind: "error",
|
|
3605
|
+
result: toolFailure(
|
|
3606
|
+
"domain",
|
|
3607
|
+
"not_found",
|
|
3608
|
+
"Event not found.",
|
|
3609
|
+
"No event matches target.ref."
|
|
3610
|
+
)
|
|
3611
|
+
};
|
|
3612
|
+
return { kind: "ok", target: { kind: "event", eventId } };
|
|
3613
|
+
}
|
|
3582
3614
|
if (target.kind === "contact") {
|
|
3583
3615
|
const contact = resolveContactLocator(db, target.contact, "target.contact");
|
|
3584
3616
|
if (!contact.ok) {
|
|
@@ -3642,6 +3674,8 @@ var init_manage_attribute_tool = __esm({
|
|
|
3642
3674
|
init_replace_attributes();
|
|
3643
3675
|
init_set_attribute();
|
|
3644
3676
|
init_unset_attribute();
|
|
3677
|
+
init_event_metadata();
|
|
3678
|
+
init_event_metadata_schema();
|
|
3645
3679
|
init_tool_metadata();
|
|
3646
3680
|
init_tool_mutation();
|
|
3647
3681
|
init_tool_names();
|
|
@@ -3672,7 +3706,8 @@ var init_manage_attribute_tool = __esm({
|
|
|
3672
3706
|
target: objectSchema(
|
|
3673
3707
|
{
|
|
3674
3708
|
kind: enumSchema("Target kind.", ["contact", "link", "event"]),
|
|
3675
|
-
eventId: integerSchema("Target event id."),
|
|
3709
|
+
eventId: integerSchema("Target event id; provide eventId or ref."),
|
|
3710
|
+
ref: externalRefSchema,
|
|
3676
3711
|
contact: contactLocatorSchema2(
|
|
3677
3712
|
"Contact locator. Required when kind=contact. Provide contactId or identity."
|
|
3678
3713
|
),
|
|
@@ -3730,7 +3765,9 @@ function insertJournalEvent(db, params) {
|
|
|
3730
3765
|
);
|
|
3731
3766
|
const eventId = Number(result.lastInsertRowid);
|
|
3732
3767
|
db.prepare("UPDATE events SET affects_bond=? WHERE id=?").run(
|
|
3733
|
-
params.affectsBond === void 0 ? params.type === "observation" && params.significance <= 2
|
|
3768
|
+
params.affectsBond === void 0 ? params.type === "observation" && params.significance <= 2 || ["activity", "milestone", "observation"].includes(params.type) && /^(?:follow(?:ed|ing)?|like(?:d)?|repost(?:ed)?)(?:\b|[-_])/i.test(
|
|
3769
|
+
summary
|
|
3770
|
+
) && !/^follow[ -]up\b/i.test(summary) ? 0 : 1 : Number(params.affectsBond),
|
|
3734
3771
|
eventId
|
|
3735
3772
|
);
|
|
3736
3773
|
db.prepare("UPDATE events SET declared_moment_kind=? WHERE id=?").run(
|
|
@@ -5538,7 +5575,7 @@ function exportContact(db, id) {
|
|
|
5538
5575
|
const contact = query("SELECT * FROM contacts WHERE id = ?", id)[0];
|
|
5539
5576
|
if (!contact) throw new AffinityNotFoundError("contact not found");
|
|
5540
5577
|
const events = query(
|
|
5541
|
-
"SELECT DISTINCT e.*, CAST(e.content AS BLOB) AS content, CAST(e.summary AS BLOB) AS summary FROM events e JOIN event_participants p ON p.event_id = e.id WHERE p.contact_id = ? AND e.type != 'date_anchor' ORDER BY e.id",
|
|
5578
|
+
"SELECT DISTINCT e.*, COALESCE(e.parent_event_id, (SELECT parent.id FROM events parent WHERE parent.external_source=e.parent_source AND parent.external_id=e.parent_ref_id AND parent.deleted_at IS NULL)) AS parent_event_id, CAST(e.content AS BLOB) AS content, CAST(e.summary AS BLOB) AS summary FROM events e JOIN event_participants p ON p.event_id = e.id WHERE p.contact_id = ? AND e.type != 'date_anchor' ORDER BY e.id",
|
|
5542
5579
|
id
|
|
5543
5580
|
).map((event) => ({
|
|
5544
5581
|
...event,
|
|
@@ -9200,12 +9237,14 @@ function getEventThread(db, source, id, options) {
|
|
|
9200
9237
|
const rows = db.prepare(`WITH RECURSIVE thread(id) AS (
|
|
9201
9238
|
SELECT ? UNION SELECT e.id FROM events e JOIN thread t JOIN events p ON p.id=t.id
|
|
9202
9239
|
WHERE e.deleted_at IS NULL AND (e.parent_event_id=p.id OR (e.parent_source=p.external_source AND e.parent_ref_id=p.external_id) OR (e.thread_source=? AND e.thread_id=?))
|
|
9203
|
-
) SELECT e.id FROM events e JOIN thread t ON e.id=t.id WHERE e.deleted_at IS NULL AND (? IS NULL OR e.channel=?) ORDER BY e.occurred_at,e.id LIMIT ? OFFSET ?`).all(
|
|
9240
|
+
) SELECT e.id FROM events e JOIN thread t ON e.id=t.id WHERE e.deleted_at IS NULL AND (? IS NULL OR e.channel=?) AND (? IS NULL OR e.type IN (SELECT value FROM json_each(?))) ORDER BY e.occurred_at,e.id LIMIT ? OFFSET ?`).all(
|
|
9204
9241
|
root.id,
|
|
9205
9242
|
root.externalRef?.source ?? null,
|
|
9206
9243
|
root.externalRef?.id ?? null,
|
|
9207
9244
|
options?.channel ?? null,
|
|
9208
9245
|
options?.channel?.trim().toLowerCase() ?? null,
|
|
9246
|
+
options?.eventTypes === void 0 ? null : JSON.stringify(options.eventTypes),
|
|
9247
|
+
options?.eventTypes === void 0 ? null : JSON.stringify(options.eventTypes),
|
|
9209
9248
|
limit,
|
|
9210
9249
|
offset
|
|
9211
9250
|
);
|
|
@@ -9923,12 +9962,12 @@ function reviewPage(db, rawInput, pageOnly) {
|
|
|
9923
9962
|
if (!pageOnly && input.view !== "graph.chart") {
|
|
9924
9963
|
const limit = input.limit ?? 50;
|
|
9925
9964
|
const offset = input.offset ?? 0;
|
|
9926
|
-
if (!Number.isInteger(limit) || limit < 1 || limit >
|
|
9965
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1e4 || !Number.isInteger(offset) || offset < 0)
|
|
9927
9966
|
return toolFailure(
|
|
9928
9967
|
"protocol",
|
|
9929
9968
|
"invalid_input",
|
|
9930
9969
|
"Invalid pagination.",
|
|
9931
|
-
"limit must be 1..
|
|
9970
|
+
"limit must be 1..10000; use offset to page through larger results; offset must be non-negative"
|
|
9932
9971
|
);
|
|
9933
9972
|
const all = [];
|
|
9934
9973
|
let page;
|
|
@@ -9980,7 +10019,7 @@ function reviewPage(db, rawInput, pageOnly) {
|
|
|
9980
10019
|
{
|
|
9981
10020
|
...input.contactIds === void 0 ? {} : { contactIds: input.contactIds },
|
|
9982
10021
|
...input.exactOnly === void 0 ? {} : { exactOnly: input.exactOnly },
|
|
9983
|
-
...input.minScore === void 0 ? {} : { minScore: input.minScore },
|
|
10022
|
+
...input.minScore === void 0 ? { minScore: 0.5 } : { minScore: input.minScore },
|
|
9984
10023
|
...input.includeDismissed === void 0 ? {} : { includeDismissed: input.includeDismissed }
|
|
9985
10024
|
},
|
|
9986
10025
|
options
|
|
@@ -9994,9 +10033,10 @@ function reviewPage(db, rawInput, pageOnly) {
|
|
|
9994
10033
|
"Missing thread reference.",
|
|
9995
10034
|
"ref is required for events.thread."
|
|
9996
10035
|
);
|
|
9997
|
-
items = getEventThread(db, input.ref.source, input.ref.id,
|
|
9998
|
-
|
|
9999
|
-
|
|
10036
|
+
items = getEventThread(db, input.ref.source, input.ref.id, {
|
|
10037
|
+
...options,
|
|
10038
|
+
...input.eventTypes === void 0 ? {} : { eventTypes: input.eventTypes }
|
|
10039
|
+
}).map(toEventListToolItem);
|
|
10000
10040
|
break;
|
|
10001
10041
|
}
|
|
10002
10042
|
case "events.contact_journal": {
|
|
@@ -10284,7 +10324,7 @@ var init_review_affinity_tool = __esm({
|
|
|
10284
10324
|
includeDismissed: "Whether dismissed pairs should be included in duplicate review (marked with dismissed flag).",
|
|
10285
10325
|
since: "Optional lower timestamp bound for time-based views.",
|
|
10286
10326
|
until: "Optional upper timestamp bound for time-based views.",
|
|
10287
|
-
limit: "Optional result limit.",
|
|
10327
|
+
limit: "Optional result limit, 1..10000 (default 50); use offset for paging.",
|
|
10288
10328
|
offset: "Optional result offset."
|
|
10289
10329
|
},
|
|
10290
10330
|
outputDescription: "Returns compact review items, a count, the applied filters, and the chart payload for graph review.",
|
|
@@ -10389,13 +10429,17 @@ var init_review_affinity_tool = __esm({
|
|
|
10389
10429
|
]),
|
|
10390
10430
|
horizonDays: integerSchema("Optional horizon-days filter."),
|
|
10391
10431
|
exactOnly: booleanSchema("Whether duplicate review is exact-only."),
|
|
10392
|
-
minScore: numberSchema(
|
|
10432
|
+
minScore: numberSchema(
|
|
10433
|
+
"Optional duplicate minimum score (default 0.5; use 0 for all candidates)."
|
|
10434
|
+
),
|
|
10393
10435
|
includeDismissed: booleanSchema(
|
|
10394
10436
|
"Whether dismissed pairs are included in duplicate review (marked with dismissed flag)."
|
|
10395
10437
|
),
|
|
10396
10438
|
since: integerSchema("Optional lower timestamp bound."),
|
|
10397
10439
|
until: integerSchema("Optional upper timestamp bound."),
|
|
10398
|
-
limit: integerSchema(
|
|
10440
|
+
limit: integerSchema(
|
|
10441
|
+
"Optional result limit, 1..10000 (default 50); use offset for paging."
|
|
10442
|
+
),
|
|
10399
10443
|
offset: integerSchema("Optional result offset.")
|
|
10400
10444
|
},
|
|
10401
10445
|
["view"],
|
|
@@ -10733,10 +10777,18 @@ function normalizeTimeInputs(value, now = Date.now(), inPatch = false) {
|
|
|
10733
10777
|
`Invalid timestamp "${item}". ${error.message}`
|
|
10734
10778
|
);
|
|
10735
10779
|
}
|
|
10780
|
+
if (!inPatch && ["occurredAt", "dueAt"].includes(key) && typeof item === "string" && /\.\d{4,}/.test(item)) {
|
|
10781
|
+
const provenance = result.provenance ?? value.provenance;
|
|
10782
|
+
result.provenance = {
|
|
10783
|
+
...provenance && typeof provenance === "object" ? provenance : {},
|
|
10784
|
+
[`raw${key[0]?.toUpperCase()}${key.slice(1)}`]: item
|
|
10785
|
+
};
|
|
10786
|
+
}
|
|
10736
10787
|
result[key] = parsed.timestamp;
|
|
10737
10788
|
if (!inPatch && ["occurredAt", "dueAt"].includes(key))
|
|
10738
10789
|
result[key.replace(/At$/, "On")] = parsed.day;
|
|
10739
|
-
} else
|
|
10790
|
+
} else if (key !== "provenance" || result.provenance === void 0)
|
|
10791
|
+
result[key] = key === "provenance" ? item : normalizeTimeInputs(item, now, key === "patch");
|
|
10740
10792
|
}
|
|
10741
10793
|
return result;
|
|
10742
10794
|
}
|
|
@@ -10778,6 +10830,12 @@ var init_timestamp_schema = __esm({
|
|
|
10778
10830
|
function surfaceProperties(tool) {
|
|
10779
10831
|
return {
|
|
10780
10832
|
...tool.inputSchema.properties,
|
|
10833
|
+
...tool.name === "review_affinity" ? {
|
|
10834
|
+
includeContent: {
|
|
10835
|
+
type: "boolean",
|
|
10836
|
+
description: "Deprecated alias for --with-content; includes verbatim event content."
|
|
10837
|
+
}
|
|
10838
|
+
} : {},
|
|
10781
10839
|
...["inspect_affinity_item", "review_affinity", "manage_event"].includes(
|
|
10782
10840
|
tool.name
|
|
10783
10841
|
) ? {
|
|
@@ -11464,9 +11522,9 @@ async function openAffinityDb(path) {
|
|
|
11464
11522
|
}
|
|
11465
11523
|
const db = new sqlite.DatabaseSync(path);
|
|
11466
11524
|
try {
|
|
11467
|
-
db.exec("PRAGMA
|
|
11525
|
+
db.exec("PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL;");
|
|
11468
11526
|
const context = db;
|
|
11469
|
-
initAffinityTables(context);
|
|
11527
|
+
withTransaction(context, () => initAffinityTables(context));
|
|
11470
11528
|
return context;
|
|
11471
11529
|
} catch (error) {
|
|
11472
11530
|
db.close();
|
|
@@ -11476,6 +11534,7 @@ async function openAffinityDb(path) {
|
|
|
11476
11534
|
var init_open_affinity_db = __esm({
|
|
11477
11535
|
"src/surface/open_affinity_db.ts"() {
|
|
11478
11536
|
init_init_affinity_tables();
|
|
11537
|
+
init_with_transaction();
|
|
11479
11538
|
}
|
|
11480
11539
|
});
|
|
11481
11540
|
function resolveAffinityDbPath(options = {}) {
|
package/dist/mcp.cjs
CHANGED
|
@@ -5,7 +5,7 @@ var node_path = require('node:path');
|
|
|
5
5
|
var node_os = require('node:os');
|
|
6
6
|
|
|
7
7
|
// src/version.ts
|
|
8
|
-
var affinityVersion = "0.6.
|
|
8
|
+
var affinityVersion = "0.6.1";
|
|
9
9
|
|
|
10
10
|
// src/surface/affinity_mcp_instructions.ts
|
|
11
11
|
var affinityMcpInstructions = "Affinity is a local relationship model and social CRM. Search before creating contacts. Inspect before revising or merging. Record direct evidence honestly, keep identities and structural ties distinct from social links, and ask for clarification when a target is ambiguous. Review commitments and recurring dates regularly. Derived scores guide attention and do not prove relationship quality.";
|
|
@@ -382,6 +382,8 @@ function mapEventRowToEventRecord(row, participants) {
|
|
|
382
382
|
momentKind: row.moment_kind,
|
|
383
383
|
participants
|
|
384
384
|
};
|
|
385
|
+
if (row.provenance_json != null)
|
|
386
|
+
base.provenance = JSON.parse(row.provenance_json);
|
|
385
387
|
if (row.content != null) base.content = decodeEventText(row.content);
|
|
386
388
|
if (row.channel != null) base.channel = row.channel;
|
|
387
389
|
if (row.external_source != null && row.external_id != null)
|
|
@@ -672,14 +674,9 @@ function parseCalendarTime(value, field = "time", now = Date.now()) {
|
|
|
672
674
|
text = text.replace(" ", "T");
|
|
673
675
|
if (!/(?:Z|[+-]\d{2}:\d{2})$/.test(text)) text += "Z";
|
|
674
676
|
}
|
|
675
|
-
let rounding = 0;
|
|
676
677
|
text = text.replace(
|
|
677
678
|
/\.(\d{4,})(?=Z|[+-]\d{2}:\d{2}$)/,
|
|
678
|
-
(_, digits) => {
|
|
679
|
-
const ms = Math.round(Number(`0.${digits}`) * 1e3);
|
|
680
|
-
rounding = ms === 1e3 ? 1 : 0;
|
|
681
|
-
return `.${String(ms === 1e3 ? 999 : ms).padStart(3, "0")}`;
|
|
682
|
-
}
|
|
679
|
+
(_, digits) => `.${digits.slice(0, 3)}`
|
|
683
680
|
);
|
|
684
681
|
if (/^(today|yesterday|tomorrow|[+-]\d+d)$/.test(text)) {
|
|
685
682
|
const offset = text === "today" ? 0 : text === "tomorrow" ? 1 : text === "yesterday" ? -1 : Number(text.slice(0, -1));
|
|
@@ -699,7 +696,7 @@ function parseCalendarTime(value, field = "time", now = Date.now()) {
|
|
|
699
696
|
const midnight = Date.parse(`${day}T00:00:00Z`);
|
|
700
697
|
if (!Number.isFinite(midnight) || new Date(midnight).toISOString().slice(0, 10) !== day)
|
|
701
698
|
return fail();
|
|
702
|
-
const timestamp = dateOnly ? midnight + (field.split(".").at(-1) === "dueAt" ? 86399999 : 0) : Date.parse(text)
|
|
699
|
+
const timestamp = dateOnly ? midnight + (field.split(".").at(-1) === "dueAt" ? 86399999 : 0) : Date.parse(text);
|
|
703
700
|
if (!Number.isFinite(timestamp)) return fail();
|
|
704
701
|
return { timestamp, day };
|
|
705
702
|
}
|
|
@@ -1739,7 +1736,7 @@ var externalRefSchema = objectSchema(
|
|
|
1739
1736
|
);
|
|
1740
1737
|
var eventMetadataSchema = {
|
|
1741
1738
|
affectsBond: booleanSchema(
|
|
1742
|
-
"Whether evidence affects the relationship; defaults false for observations with significance <=2."
|
|
1739
|
+
"Whether evidence affects the relationship; defaults false for like/follow/repost labels and observations with significance <=2; explicit true opts in."
|
|
1743
1740
|
),
|
|
1744
1741
|
content: stringSchema(
|
|
1745
1742
|
"Verbatim text, including empty text; at most 1 MB UTF-8. Summary may be omitted when content is supplied."
|
|
@@ -2289,6 +2286,13 @@ function validateToolInput(schema, value, path = "input", injectedNow = Date.now
|
|
|
2289
2286
|
`${path}.${key}`,
|
|
2290
2287
|
referenceNow
|
|
2291
2288
|
);
|
|
2289
|
+
if (!path.endsWith(".patch") && ["occurredAt", "dueAt"].includes(key) && typeof object[key] === "string" && /\.\d{4,}/.test(object[key])) {
|
|
2290
|
+
const provenance = object.provenance;
|
|
2291
|
+
object.provenance = {
|
|
2292
|
+
...provenance && typeof provenance === "object" ? provenance : {},
|
|
2293
|
+
[`raw${key[0]?.toUpperCase()}${key.slice(1)}`]: object[key]
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2292
2296
|
object[key] = parsed.timestamp;
|
|
2293
2297
|
if (!path.endsWith(".patch") && ["occurredAt", "dueAt"].includes(key))
|
|
2294
2298
|
object[key.replace(/At$/, "On")] ??= parsed.day;
|
|
@@ -2306,7 +2310,7 @@ function validateToolInput(schema, value, path = "input", injectedNow = Date.now
|
|
|
2306
2310
|
const error = validateToolInput(
|
|
2307
2311
|
child,
|
|
2308
2312
|
object[key],
|
|
2309
|
-
`${path}.${key}`,
|
|
2313
|
+
key === "input" && path === "input" ? path : `${path}.${key}`,
|
|
2310
2314
|
referenceNow,
|
|
2311
2315
|
strict
|
|
2312
2316
|
);
|
|
@@ -2743,6 +2747,11 @@ function setAttribute(db, target, name, value) {
|
|
|
2743
2747
|
const stored = normalizeAttributeValue(value);
|
|
2744
2748
|
const now = resolveNow();
|
|
2745
2749
|
const existing = getLiveAttributeRow(db, target, key);
|
|
2750
|
+
if (existing?.value === stored)
|
|
2751
|
+
return buildAttributeMutationReceipt(
|
|
2752
|
+
mapAttributeRowToAttributeRecord(existing),
|
|
2753
|
+
{}
|
|
2754
|
+
);
|
|
2746
2755
|
if (existing) {
|
|
2747
2756
|
db.prepare(
|
|
2748
2757
|
"UPDATE attributes SET value = ?, updated_at = ? WHERE id = ?"
|
|
@@ -2882,7 +2891,30 @@ function linkLocatorSchema2(description) {
|
|
|
2882
2891
|
};
|
|
2883
2892
|
}
|
|
2884
2893
|
function resolveAttributeTarget(db, target) {
|
|
2885
|
-
if (target.kind === "event")
|
|
2894
|
+
if (target.kind === "event") {
|
|
2895
|
+
if (target.eventId === void 0 === (target.ref === void 0))
|
|
2896
|
+
return {
|
|
2897
|
+
kind: "error",
|
|
2898
|
+
result: toolFailure(
|
|
2899
|
+
"protocol",
|
|
2900
|
+
"invalid_input",
|
|
2901
|
+
"Invalid event target.",
|
|
2902
|
+
"Provide exactly one of target.eventId or target.ref."
|
|
2903
|
+
)
|
|
2904
|
+
};
|
|
2905
|
+
const eventId = target.eventId ?? (target.ref && getEventByExternalRef(db, target.ref.source, target.ref.id)?.id);
|
|
2906
|
+
if (eventId === void 0)
|
|
2907
|
+
return {
|
|
2908
|
+
kind: "error",
|
|
2909
|
+
result: toolFailure(
|
|
2910
|
+
"domain",
|
|
2911
|
+
"not_found",
|
|
2912
|
+
"Event not found.",
|
|
2913
|
+
"No event matches target.ref."
|
|
2914
|
+
)
|
|
2915
|
+
};
|
|
2916
|
+
return { kind: "ok", target: { kind: "event", eventId } };
|
|
2917
|
+
}
|
|
2886
2918
|
if (target.kind === "contact") {
|
|
2887
2919
|
const contact = resolveContactLocator(db, target.contact, "target.contact");
|
|
2888
2920
|
if (!contact.ok) {
|
|
@@ -2964,7 +2996,8 @@ var manageAttributeTool = defineAffinityTool({
|
|
|
2964
2996
|
target: objectSchema(
|
|
2965
2997
|
{
|
|
2966
2998
|
kind: enumSchema("Target kind.", ["contact", "link", "event"]),
|
|
2967
|
-
eventId: integerSchema("Target event id."),
|
|
2999
|
+
eventId: integerSchema("Target event id; provide eventId or ref."),
|
|
3000
|
+
ref: externalRefSchema,
|
|
2968
3001
|
contact: contactLocatorSchema2(
|
|
2969
3002
|
"Contact locator. Required when kind=contact. Provide contactId or identity."
|
|
2970
3003
|
),
|
|
@@ -3020,7 +3053,9 @@ function insertJournalEvent(db, params) {
|
|
|
3020
3053
|
);
|
|
3021
3054
|
const eventId = Number(result.lastInsertRowid);
|
|
3022
3055
|
db.prepare("UPDATE events SET affects_bond=? WHERE id=?").run(
|
|
3023
|
-
params.affectsBond === void 0 ? params.type === "observation" && params.significance <= 2
|
|
3056
|
+
params.affectsBond === void 0 ? params.type === "observation" && params.significance <= 2 || ["activity", "milestone", "observation"].includes(params.type) && /^(?:follow(?:ed|ing)?|like(?:d)?|repost(?:ed)?)(?:\b|[-_])/i.test(
|
|
3057
|
+
summary
|
|
3058
|
+
) && !/^follow[ -]up\b/i.test(summary) ? 0 : 1 : Number(params.affectsBond),
|
|
3024
3059
|
eventId
|
|
3025
3060
|
);
|
|
3026
3061
|
db.prepare("UPDATE events SET declared_moment_kind=? WHERE id=?").run(
|
|
@@ -4692,7 +4727,7 @@ function exportContact(db, id) {
|
|
|
4692
4727
|
const contact = query("SELECT * FROM contacts WHERE id = ?", id)[0];
|
|
4693
4728
|
if (!contact) throw new AffinityNotFoundError("contact not found");
|
|
4694
4729
|
const events = query(
|
|
4695
|
-
"SELECT DISTINCT e.*, CAST(e.content AS BLOB) AS content, CAST(e.summary AS BLOB) AS summary FROM events e JOIN event_participants p ON p.event_id = e.id WHERE p.contact_id = ? AND e.type != 'date_anchor' ORDER BY e.id",
|
|
4730
|
+
"SELECT DISTINCT e.*, COALESCE(e.parent_event_id, (SELECT parent.id FROM events parent WHERE parent.external_source=e.parent_source AND parent.external_id=e.parent_ref_id AND parent.deleted_at IS NULL)) AS parent_event_id, CAST(e.content AS BLOB) AS content, CAST(e.summary AS BLOB) AS summary FROM events e JOIN event_participants p ON p.event_id = e.id WHERE p.contact_id = ? AND e.type != 'date_anchor' ORDER BY e.id",
|
|
4696
4731
|
id
|
|
4697
4732
|
).map((event) => ({
|
|
4698
4733
|
...event,
|
|
@@ -7833,12 +7868,14 @@ function getEventThread(db, source, id, options) {
|
|
|
7833
7868
|
const rows = db.prepare(`WITH RECURSIVE thread(id) AS (
|
|
7834
7869
|
SELECT ? UNION SELECT e.id FROM events e JOIN thread t JOIN events p ON p.id=t.id
|
|
7835
7870
|
WHERE e.deleted_at IS NULL AND (e.parent_event_id=p.id OR (e.parent_source=p.external_source AND e.parent_ref_id=p.external_id) OR (e.thread_source=? AND e.thread_id=?))
|
|
7836
|
-
) SELECT e.id FROM events e JOIN thread t ON e.id=t.id WHERE e.deleted_at IS NULL AND (? IS NULL OR e.channel=?) ORDER BY e.occurred_at,e.id LIMIT ? OFFSET ?`).all(
|
|
7871
|
+
) SELECT e.id FROM events e JOIN thread t ON e.id=t.id WHERE e.deleted_at IS NULL AND (? IS NULL OR e.channel=?) AND (? IS NULL OR e.type IN (SELECT value FROM json_each(?))) ORDER BY e.occurred_at,e.id LIMIT ? OFFSET ?`).all(
|
|
7837
7872
|
root.id,
|
|
7838
7873
|
root.externalRef?.source ?? null,
|
|
7839
7874
|
root.externalRef?.id ?? null,
|
|
7840
7875
|
options?.channel ?? null,
|
|
7841
7876
|
options?.channel?.trim().toLowerCase() ?? null,
|
|
7877
|
+
options?.eventTypes === void 0 ? null : JSON.stringify(options.eventTypes),
|
|
7878
|
+
options?.eventTypes === void 0 ? null : JSON.stringify(options.eventTypes),
|
|
7842
7879
|
limit,
|
|
7843
7880
|
offset
|
|
7844
7881
|
);
|
|
@@ -8490,12 +8527,12 @@ function reviewPage(db, rawInput, pageOnly) {
|
|
|
8490
8527
|
if (!pageOnly && input.view !== "graph.chart") {
|
|
8491
8528
|
const limit = input.limit ?? 50;
|
|
8492
8529
|
const offset = input.offset ?? 0;
|
|
8493
|
-
if (!Number.isInteger(limit) || limit < 1 || limit >
|
|
8530
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1e4 || !Number.isInteger(offset) || offset < 0)
|
|
8494
8531
|
return toolFailure(
|
|
8495
8532
|
"protocol",
|
|
8496
8533
|
"invalid_input",
|
|
8497
8534
|
"Invalid pagination.",
|
|
8498
|
-
"limit must be 1..
|
|
8535
|
+
"limit must be 1..10000; use offset to page through larger results; offset must be non-negative"
|
|
8499
8536
|
);
|
|
8500
8537
|
const all = [];
|
|
8501
8538
|
let page;
|
|
@@ -8547,7 +8584,7 @@ function reviewPage(db, rawInput, pageOnly) {
|
|
|
8547
8584
|
{
|
|
8548
8585
|
...input.contactIds === void 0 ? {} : { contactIds: input.contactIds },
|
|
8549
8586
|
...input.exactOnly === void 0 ? {} : { exactOnly: input.exactOnly },
|
|
8550
|
-
...input.minScore === void 0 ? {} : { minScore: input.minScore },
|
|
8587
|
+
...input.minScore === void 0 ? { minScore: 0.5 } : { minScore: input.minScore },
|
|
8551
8588
|
...input.includeDismissed === void 0 ? {} : { includeDismissed: input.includeDismissed }
|
|
8552
8589
|
},
|
|
8553
8590
|
options
|
|
@@ -8561,9 +8598,10 @@ function reviewPage(db, rawInput, pageOnly) {
|
|
|
8561
8598
|
"Missing thread reference.",
|
|
8562
8599
|
"ref is required for events.thread."
|
|
8563
8600
|
);
|
|
8564
|
-
items = getEventThread(db, input.ref.source, input.ref.id,
|
|
8565
|
-
|
|
8566
|
-
|
|
8601
|
+
items = getEventThread(db, input.ref.source, input.ref.id, {
|
|
8602
|
+
...options,
|
|
8603
|
+
...input.eventTypes === void 0 ? {} : { eventTypes: input.eventTypes }
|
|
8604
|
+
}).map(toEventListToolItem);
|
|
8567
8605
|
break;
|
|
8568
8606
|
}
|
|
8569
8607
|
case "events.contact_journal": {
|
|
@@ -8822,7 +8860,7 @@ var reviewAffinityTool = defineAffinityTool({
|
|
|
8822
8860
|
includeDismissed: "Whether dismissed pairs should be included in duplicate review (marked with dismissed flag).",
|
|
8823
8861
|
since: "Optional lower timestamp bound for time-based views.",
|
|
8824
8862
|
until: "Optional upper timestamp bound for time-based views.",
|
|
8825
|
-
limit: "Optional result limit.",
|
|
8863
|
+
limit: "Optional result limit, 1..10000 (default 50); use offset for paging.",
|
|
8826
8864
|
offset: "Optional result offset."
|
|
8827
8865
|
},
|
|
8828
8866
|
outputDescription: "Returns compact review items, a count, the applied filters, and the chart payload for graph review.",
|
|
@@ -8927,13 +8965,17 @@ var reviewAffinityTool = defineAffinityTool({
|
|
|
8927
8965
|
]),
|
|
8928
8966
|
horizonDays: integerSchema("Optional horizon-days filter."),
|
|
8929
8967
|
exactOnly: booleanSchema("Whether duplicate review is exact-only."),
|
|
8930
|
-
minScore: numberSchema(
|
|
8968
|
+
minScore: numberSchema(
|
|
8969
|
+
"Optional duplicate minimum score (default 0.5; use 0 for all candidates)."
|
|
8970
|
+
),
|
|
8931
8971
|
includeDismissed: booleanSchema(
|
|
8932
8972
|
"Whether dismissed pairs are included in duplicate review (marked with dismissed flag)."
|
|
8933
8973
|
),
|
|
8934
8974
|
since: integerSchema("Optional lower timestamp bound."),
|
|
8935
8975
|
until: integerSchema("Optional upper timestamp bound."),
|
|
8936
|
-
limit: integerSchema(
|
|
8976
|
+
limit: integerSchema(
|
|
8977
|
+
"Optional result limit, 1..10000 (default 50); use offset for paging."
|
|
8978
|
+
),
|
|
8937
8979
|
offset: integerSchema("Optional result offset.")
|
|
8938
8980
|
},
|
|
8939
8981
|
["view"],
|
|
@@ -9227,10 +9269,18 @@ function normalizeTimeInputs(value, now = Date.now(), inPatch = false) {
|
|
|
9227
9269
|
`Invalid timestamp "${item}". ${error.message}`
|
|
9228
9270
|
);
|
|
9229
9271
|
}
|
|
9272
|
+
if (!inPatch && ["occurredAt", "dueAt"].includes(key) && typeof item === "string" && /\.\d{4,}/.test(item)) {
|
|
9273
|
+
const provenance = result.provenance ?? value.provenance;
|
|
9274
|
+
result.provenance = {
|
|
9275
|
+
...provenance && typeof provenance === "object" ? provenance : {},
|
|
9276
|
+
[`raw${key[0]?.toUpperCase()}${key.slice(1)}`]: item
|
|
9277
|
+
};
|
|
9278
|
+
}
|
|
9230
9279
|
result[key] = parsed.timestamp;
|
|
9231
9280
|
if (!inPatch && ["occurredAt", "dueAt"].includes(key))
|
|
9232
9281
|
result[key.replace(/At$/, "On")] = parsed.day;
|
|
9233
|
-
} else
|
|
9282
|
+
} else if (key !== "provenance" || result.provenance === void 0)
|
|
9283
|
+
result[key] = key === "provenance" ? item : normalizeTimeInputs(item, now, key === "patch");
|
|
9234
9284
|
}
|
|
9235
9285
|
return result;
|
|
9236
9286
|
}
|
|
@@ -9262,6 +9312,12 @@ function timestampSchema(schema) {
|
|
|
9262
9312
|
function surfaceProperties(tool) {
|
|
9263
9313
|
return {
|
|
9264
9314
|
...tool.inputSchema.properties,
|
|
9315
|
+
...tool.name === "review_affinity" ? {
|
|
9316
|
+
includeContent: {
|
|
9317
|
+
type: "boolean",
|
|
9318
|
+
description: "Deprecated alias for --with-content; includes verbatim event content."
|
|
9319
|
+
}
|
|
9320
|
+
} : {},
|
|
9265
9321
|
...["inspect_affinity_item", "review_affinity", "manage_event"].includes(
|
|
9266
9322
|
tool.name
|
|
9267
9323
|
) ? {
|
|
@@ -9862,9 +9918,9 @@ async function openAffinityDb(path) {
|
|
|
9862
9918
|
}
|
|
9863
9919
|
const db = new sqlite.DatabaseSync(path);
|
|
9864
9920
|
try {
|
|
9865
|
-
db.exec("PRAGMA
|
|
9921
|
+
db.exec("PRAGMA busy_timeout=5000; PRAGMA journal_mode=WAL;");
|
|
9866
9922
|
const context = db;
|
|
9867
|
-
initAffinityTables(context);
|
|
9923
|
+
withTransaction(context, () => initAffinityTables(context));
|
|
9868
9924
|
return context;
|
|
9869
9925
|
} catch (error) {
|
|
9870
9926
|
db.close();
|