@wirecat/cli-core 0.548.0 → 0.550.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.
Files changed (72) hide show
  1. package/CHANGELOG.md +232 -0
  2. package/dist/calendar/cli/index.d.ts +10 -1
  3. package/dist/calendar/cli/index.js +9 -3
  4. package/dist/calendar/cli/search-all.d.ts +17 -0
  5. package/dist/calendar/cli/search-all.js +72 -0
  6. package/dist/cli/answer-entity.d.ts +4 -0
  7. package/dist/cli/answer-entity.js +5 -0
  8. package/dist/cli/bot/context.d.ts +2 -0
  9. package/dist/cli/compact.d.ts +41 -0
  10. package/dist/cli/compact.js +313 -0
  11. package/dist/cli/context.d.ts +11 -2
  12. package/dist/cli/context.js +15 -6
  13. package/dist/cli/envelope.d.ts +5 -1
  14. package/dist/cli/envelope.js +15 -4
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.js +2 -0
  17. package/dist/cli/messenger/search-command.d.ts +10 -0
  18. package/dist/cli/messenger/search-command.js +19 -13
  19. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  20. package/dist/cli/program.d.ts +3 -0
  21. package/dist/cli/program.js +7 -1
  22. package/dist/domain/providers.d.ts +3 -0
  23. package/dist/domain/providers.js +20 -4
  24. package/dist/domain/ref-arguments.js +3 -7
  25. package/dist/domain/ref-summaries.d.ts +56 -0
  26. package/dist/domain/ref-summaries.js +63 -0
  27. package/dist/entities/index.d.ts +11 -0
  28. package/dist/entities/relationship.d.ts +2 -0
  29. package/dist/entities/relationship.js +2 -0
  30. package/dist/entities/task-results.d.ts +10 -0
  31. package/dist/entities/task-results.js +1 -0
  32. package/dist/errors/reasons.js +2 -0
  33. package/dist/mail/partial.d.ts +9 -3
  34. package/dist/mail/partial.js +35 -10
  35. package/dist/mail/source.d.ts +7 -1
  36. package/dist/mail/sync.js +1 -1
  37. package/dist/output-detail.js +9 -5
  38. package/dist/output.d.ts +5 -1
  39. package/dist/output.js +29 -4
  40. package/dist/services/proposals/kinds/relationship-update.d.ts +4 -0
  41. package/dist/services/proposals/kinds/relationship-update.js +53 -0
  42. package/dist/services/proposals/registry.js +2 -0
  43. package/dist/store/decisions.d.ts +3 -1
  44. package/dist/store/decisions.js +7 -1
  45. package/dist/store/index.d.ts +2 -1
  46. package/dist/store/sqlite/email-locations.d.ts +22 -1
  47. package/dist/store/sqlite/email-locations.js +29 -2
  48. package/dist/store/sqlite/emails.d.ts +3 -7
  49. package/dist/store/sqlite/emails.js +4 -2
  50. package/dist/store/sqlite/evidence-sources.d.ts +2 -2
  51. package/dist/store/sqlite/evidence-sources.js +41 -77
  52. package/dist/store/sqlite/knowledge.d.ts +17 -23
  53. package/dist/store/sqlite/knowledge.js +13 -13
  54. package/dist/store/sqlite/manifest.js +2 -0
  55. package/dist/store/sqlite/migrations.generated.js +12 -0
  56. package/dist/store/sqlite/notes.d.ts +5 -0
  57. package/dist/store/sqlite/notes.js +6 -2
  58. package/dist/store/sqlite/open-store.js +2 -0
  59. package/dist/store/sqlite/proposed-actions.d.ts +1 -1
  60. package/dist/store/sqlite/proposed-actions.js +10 -2
  61. package/dist/store/sqlite/ref-summaries.d.ts +17 -0
  62. package/dist/store/sqlite/ref-summaries.js +378 -0
  63. package/dist/store/sqlite/relation-proposals.d.ts +40 -0
  64. package/dist/store/sqlite/relation-proposals.js +88 -1
  65. package/dist/store/sqlite/schema.d.ts +16 -0
  66. package/dist/store/sqlite/schema.js +1 -0
  67. package/dist/store/sqlite/suggested-links.d.ts +10 -0
  68. package/dist/store/sqlite/suggested-links.js +68 -0
  69. package/dist/store/store.d.ts +3 -0
  70. package/dist/store/store.js +5 -0
  71. package/package.json +3 -3
  72. package/parity.json +4 -0
@@ -1,10 +1,23 @@
1
1
  import { CliError } from "@wirecat/cli-base";
2
2
  import { assertMayDecide, deciderAuthor } from "../decisions.js";
3
3
  import { actorOfOrigin, botNamed, ownerPerson } from "./actors.js";
4
- import { insertLink, linkOf } from "./notes.js";
4
+ import { insertLink, linkOf, purposeOf } from "./notes.js";
5
5
  import { proposalOperationsOver, proposerOfAction } from "./proposed-actions.js";
6
6
  import { thingOf } from "./things.js";
7
7
  import { write } from "./transaction.js";
8
+ export const relationOf = (link) => ({
9
+ id: link.id,
10
+ from: link.from,
11
+ to: String(link.to),
12
+ kind: link.kind,
13
+ role: link.role,
14
+ purpose: link.purpose,
15
+ evidence: link.evidence,
16
+ confirmed: link.confirmed,
17
+ provenance: link.provenance,
18
+ confidenceCategory: link.confirmed ? "owner-confirmed" : "weak",
19
+ createdAt: link.createdAt,
20
+ });
8
21
  /** `relationship` proposals in `proposed_actions`: an agent's or a rule's, and the rejected ones a scan skips. */
9
22
  export const relationProposalsOver = (context) => {
10
23
  const { database, now } = context;
@@ -26,6 +39,14 @@ export const relationProposalsOver = (context) => {
26
39
  "VALUES ('relationship', ?, ?, ?, ?, 'rejected', ?, ?, ?, ?, ?, ?, ?, ?)")
27
40
  .run(from?.type ?? null, from?.id ?? null, JSON.stringify({ from: link.from, to: link.to, kind: link.kind, ...(link.role ? { role: link.role } : {}) }), link.evidence, proposed.type, proposed.id, decided.type, decided.id, at, JSON.stringify({ reason: why.join(": "), link: link.id }), at, at);
28
41
  };
42
+ const applyUpdate = (link, input) => {
43
+ const changes = changesOf(input);
44
+ database
45
+ .prepare("UPDATE links SET role = CASE WHEN ? THEN ? ELSE role END, purpose = CASE WHEN ? THEN ? ELSE purpose END, " +
46
+ "updated_at = ? WHERE id = ?")
47
+ .run(Number("role" in changes), changes.role ?? null, Number("purpose" in changes), changes.purpose ?? null, now(), Number(link.id));
48
+ return linkOf(database, database.prepare("SELECT * FROM links WHERE id = ?").get(Number(link.id)));
49
+ };
29
50
  return {
30
51
  /** The same pending pair returns the proposal already held. */
31
52
  propose: (from, input, by) => {
@@ -48,6 +69,7 @@ export const relationProposalsOver = (context) => {
48
69
  to: input.to,
49
70
  kind: input.kind,
50
71
  ...(input.role === undefined ? {} : { role: input.role }),
72
+ ...(input.purpose === undefined ? {} : { purpose: purposeOf(input.purpose) }),
51
73
  evidence,
52
74
  },
53
75
  ...(input.reason === undefined ? {} : { reason: input.reason }),
@@ -55,6 +77,53 @@ export const relationProposalsOver = (context) => {
55
77
  });
56
78
  },
57
79
  rejected: (link, by, reason) => rejected(link, by, reason),
80
+ update: (link, input) => write(database, () => applyUpdate(link, input)),
81
+ proposeUpdate: (link, input, by) => {
82
+ const changes = changesOf(input);
83
+ return write(database, () => {
84
+ const from = thingOf(database, link.from);
85
+ const held = database
86
+ .prepare("SELECT id FROM proposed_actions WHERE kind='relationship_update' AND status='proposed' AND target_type=? " +
87
+ "AND target_id=? AND json_extract(payload,'$.relationship')=? AND json_extract(payload,'$.changes')=json(?) " +
88
+ "ORDER BY id LIMIT 1")
89
+ .get(from?.type ?? null, from?.id ?? null, link.id, JSON.stringify(changes));
90
+ if (held)
91
+ return operations.get(String(held.id));
92
+ return operations.propose({
93
+ kind: "relationship_update",
94
+ by,
95
+ target: link.from,
96
+ payload: {
97
+ relationship: link.id,
98
+ from: link.from,
99
+ to: link.to,
100
+ kind: link.kind,
101
+ changes,
102
+ before: { role: link.role, purpose: link.purpose },
103
+ },
104
+ ...(input.reason === undefined ? {} : { reason: input.reason }),
105
+ });
106
+ });
107
+ },
108
+ decideUpdate: (id, decision) => write(database, () => {
109
+ const proposal = operations.get(id);
110
+ if (proposal.kind !== "relationship_update")
111
+ throw new CliError("validation_error", `${id} is a ${proposal.kind} proposal, not a relationship change`);
112
+ assertMayDecide({ id, proposedBy: proposerOfAction(proposal) }, decision.by, context.decisions);
113
+ const author = deciderAuthor(decision.by);
114
+ const reason = decision.reason?.trim() || undefined;
115
+ if (!decision.confirm)
116
+ return { proposal: operations.reject(id, author, reason === undefined ? undefined : { reason }), link: null };
117
+ const payload = proposal.payload;
118
+ const row = database.prepare("SELECT * FROM links WHERE id = ?").get(Number(payload.relationship));
119
+ if (!row)
120
+ throw new CliError("not_found", `relationship ${payload.relationship} is gone`);
121
+ const link = applyUpdate(linkOf(database, row), payload.changes);
122
+ return {
123
+ proposal: operations.approve(id, { relationship: link.id, ...(reason === undefined ? {} : { reason }) }, author),
124
+ link,
125
+ };
126
+ }),
58
127
  /**
59
128
  * Decides a stored `relationship` or `key_document` proposal in one transaction: the confirmed link and the
60
129
  * decision land together or not at all.
@@ -75,6 +144,7 @@ export const relationProposalsOver = (context) => {
75
144
  to: String(key ? payload.project : payload.to),
76
145
  kind: key ? "related-to" : (payload.kind ?? "member-of"),
77
146
  ...(key ? { role: "key" } : payload.role === undefined ? {} : { role: payload.role }),
147
+ ...(key || payload.purpose === undefined ? {} : { purpose: payload.purpose }),
78
148
  ...(proposal.reason === null ? {} : { evidence: proposal.reason }),
79
149
  ...decidedOrigin(decision.by),
80
150
  });
@@ -104,6 +174,23 @@ export const relationProposalsOver = (context) => {
104
174
  }),
105
175
  };
106
176
  };
177
+ /** What a relationship update sets, checked; `null` clears a field and an absent one is left alone. */
178
+ const changesOf = (input) => {
179
+ const changes = {};
180
+ if (input.role !== undefined)
181
+ changes.role = input.role === null ? null : roleOf(input.role);
182
+ if (input.purpose !== undefined)
183
+ changes.purpose = input.purpose === null ? null : purposeOf(input.purpose);
184
+ if (!Object.keys(changes).length)
185
+ throw new CliError("validation_error", "a relationship update sets a role, a purpose or both");
186
+ return changes;
187
+ };
188
+ const roleOf = (role) => {
189
+ const value = role.trim();
190
+ if (!value || value.length > 200)
191
+ throw new CliError("validation_error", "a role takes 1–200 characters");
192
+ return value;
193
+ };
107
194
  /** A link an agent confirmed for the owner says so in its provenance. */
108
195
  const decidedOrigin = (by) => by === "owner"
109
196
  ? { origin: "owner" }
@@ -14757,6 +14757,22 @@ export declare const links: import("drizzle-orm/sqlite-core").SQLiteTableWithCol
14757
14757
  identity: undefined;
14758
14758
  generated: undefined;
14759
14759
  }, {}>;
14760
+ purpose: import("drizzle-orm/sqlite-core").SQLiteColumn<{
14761
+ name: string;
14762
+ tableName: "links";
14763
+ dataType: "string";
14764
+ data: string;
14765
+ driverParam: string;
14766
+ notNull: false;
14767
+ hasDefault: false;
14768
+ isPrimaryKey: false;
14769
+ isAutoincrement: false;
14770
+ hasRuntimeDefault: false;
14771
+ enumValues: [string, ...string[]];
14772
+ baseColumn: never;
14773
+ identity: undefined;
14774
+ generated: undefined;
14775
+ }, {}>;
14760
14776
  };
14761
14777
  dialect: 'sqlite';
14762
14778
  isAlias: false;
@@ -1475,6 +1475,7 @@ export const links = sqliteTable("links", {
1475
1475
  createdAt: integer("created_at").notNull(),
1476
1476
  author: text("author"),
1477
1477
  updatedAt: integer("updated_at").notNull(),
1478
+ purpose: text("purpose"),
1478
1479
  }, (table) => [
1479
1480
  index("links_unresolved").on(table.targetFolded).where(sql `to_id IS NULL`),
1480
1481
  index("links_by_from_type_from_id").on(table.fromType, table.fromId),
@@ -0,0 +1,10 @@
1
+ import type { CacheDatabase } from "../driver.js";
2
+ /** Set by store version 44 and deleted once the move ran. */
3
+ export declare const SUGGESTED_LINKS_TO_MOVE = "suggested_links_to_move";
4
+ /**
5
+ * Moves the unconfirmed agent and rule links into `proposed_actions` as `relationship` and `key_document`
6
+ * proposals, with their bot, reason and time, so `links` holds what the owner confirmed. The moved row keeps the
7
+ * link's provenance under `suggestedLink`, which marks its proposer unverified: only the owner decides it. A
8
+ * suggestion an older build writes later stays a link, and the proposal kinds still list it.
9
+ */
10
+ export declare const moveSuggestedLinks: (database: CacheDatabase, now: () => number) => number;
@@ -0,0 +1,68 @@
1
+ import { botNamed } from "./actors.js";
2
+ import { linkOf } from "./notes.js";
3
+ import { write } from "./transaction.js";
4
+ /** Set by store version 44 and deleted once the move ran. */
5
+ export const SUGGESTED_LINKS_TO_MOVE = "suggested_links_to_move";
6
+ /**
7
+ * An agent's `agent:<bot>` or a rule's `rule:<id>` suggestion. A rule becomes the seeded bot `rule`, since the text
8
+ * does not say which tool ran it; anything else (Drive's editor links) is a record, not a proposal, and stays.
9
+ */
10
+ const proposerOf = (provenance) => {
11
+ if (provenance?.startsWith("rule:"))
12
+ return { name: "rule", kind: "rule" };
13
+ if (!provenance?.startsWith("agent:"))
14
+ return undefined;
15
+ const name = provenance.slice("agent:".length).split(/[\s;]/, 1)[0] ?? "";
16
+ return { name: name && name.length <= 100 ? name : "agent", kind: "agent" };
17
+ };
18
+ /**
19
+ * Moves the unconfirmed agent and rule links into `proposed_actions` as `relationship` and `key_document`
20
+ * proposals, with their bot, reason and time, so `links` holds what the owner confirmed. The moved row keeps the
21
+ * link's provenance under `suggestedLink`, which marks its proposer unverified: only the owner decides it. A
22
+ * suggestion an older build writes later stays a link, and the proposal kinds still list it.
23
+ */
24
+ export const moveSuggestedLinks = (database, now) => {
25
+ if (!database.prepare("SELECT 1 AS found FROM store_settings WHERE key = ?").get(SUGGESTED_LINKS_TO_MOVE))
26
+ return 0;
27
+ return write(database, () => {
28
+ const rows = database
29
+ .prepare("SELECT * FROM links WHERE confirmed = 0 AND to_id IS NOT NULL AND kind IN ('member-of', 'related-to') ORDER BY id")
30
+ .all();
31
+ const at = now();
32
+ let moved = 0;
33
+ for (const row of rows) {
34
+ const link = linkOf(database, row);
35
+ const proposer = proposerOf(link.provenance);
36
+ const key = link.role === "key";
37
+ if (!proposer || !link.from || !link.to || (key && !link.to.startsWith("project:")))
38
+ continue;
39
+ const kind = key ? "key_document" : "relationship";
40
+ const held = database
41
+ .prepare("SELECT id FROM proposed_actions WHERE kind = ? AND status = 'proposed' AND target_type = ? AND target_id = ? " +
42
+ "AND json_extract(payload, ?) = ?")
43
+ .get(kind, row.from_type, row.from_id, key ? "$.project" : "$.to", link.to);
44
+ if (!held) {
45
+ const bot = botNamed(database, proposer.name, at, proposer.kind);
46
+ const suggestedLink = { id: link.id, provenance: link.provenance };
47
+ const payload = key
48
+ ? { ref: link.from, project: link.to, suggestedLink }
49
+ : {
50
+ from: link.from,
51
+ to: link.to,
52
+ kind: link.kind,
53
+ ...(link.role === null ? {} : { role: link.role }),
54
+ evidence: [],
55
+ suggestedLink,
56
+ };
57
+ database
58
+ .prepare("INSERT INTO proposed_actions (kind, target_type, target_id, payload, reason, status, proposed_by_type, " +
59
+ "proposed_by_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, 'proposed', ?, ?, ?, ?)")
60
+ .run(kind, row.from_type, row.from_id, JSON.stringify(payload), link.evidence, bot.type, bot.id, row.created_at, at);
61
+ }
62
+ database.prepare("DELETE FROM links WHERE id = ?").run(Number(row.id));
63
+ moved += 1;
64
+ }
65
+ database.prepare("DELETE FROM store_settings WHERE key = ?").run(SUGGESTED_LINKS_TO_MOVE);
66
+ return moved;
67
+ });
68
+ };
@@ -1,4 +1,5 @@
1
1
  import type { ContextEvidenceReader, ContextIndex, ContextRecordReader } from "../context/evidence.js";
2
+ import type { RefsStore } from "../domain/ref-summaries.js";
2
3
  import type { EventStore } from "../meetings/index.js";
3
4
  import { type AccountStore } from "./account-store.js";
4
5
  import { type AccountsStore } from "./accounts.js";
@@ -91,6 +92,8 @@ export interface MessageStore {
91
92
  readonly meetingFindings: MeetingFindingsStore;
92
93
  /** Cited facts and follow-ups about one project from stored messages, mail and documents, for review. */
93
94
  readonly evidenceFindings: EvidenceFindingsStore;
95
+ /** What any ref names, a page of them at a time: read once per page, never once per row. */
96
+ readonly refs: RefsStore;
94
97
  readonly facts: FactsStore;
95
98
  /** What agents concluded: summaries, digests, facts, preferences, each with evidence and a scope. */
96
99
  readonly memories: MemoriesStore;
@@ -37,6 +37,7 @@ import { notesStoreOver } from "./sqlite/notes.js";
37
37
  import { openStoreContext } from "./sqlite/open-store.js";
38
38
  import { projectAnnotationStoreOver } from "./sqlite/project-transfer/store.js";
39
39
  import { proposedActionsStoreOver } from "./sqlite/proposed-actions.js";
40
+ import { refSummariesOf } from "./sqlite/ref-summaries.js";
40
41
  import * as stems from "./sqlite/stems.js";
41
42
  import { taskStoreOver } from "./sqlite/tasks.js";
42
43
  import { syncStoreOver } from "./sync.js";
@@ -105,6 +106,9 @@ const storeOver = (context) => {
105
106
  const calendar = once(() => ({ ...eventStoreOver(context), ...eventSearchOver(context) }));
106
107
  const meetingFindings = once(() => meetingFindingsStoreOver(context));
107
108
  const evidenceFindings = once(() => evidenceFindingsStoreOver(context));
109
+ const refs = {
110
+ summaries: async (texts, options) => refSummariesOf(context.database, texts, options),
111
+ };
108
112
  const facts = once(() => factsStoreOver(context));
109
113
  const memories = once(() => memoriesStoreOver(context));
110
114
  const proposedActions = once(() => proposedActionsStoreOver(context));
@@ -199,6 +203,7 @@ const storeOver = (context) => {
199
203
  get meetingFindings() {
200
204
  return meetingFindings();
201
205
  },
206
+ refs,
202
207
  get evidenceFindings() {
203
208
  return evidenceFindings();
204
209
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wirecat/cli-core",
3
- "version": "0.548.0",
3
+ "version": "0.550.0",
4
4
  "description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Viacheslav Ptsarev",
@@ -251,7 +251,7 @@
251
251
  "@types/node": "^22.20.5",
252
252
  "@types/pngjs": "6.0.5",
253
253
  "@vitest/coverage-v8": "^5.0.5",
254
- "@wirecat/cli-base": "0.73.0",
254
+ "@wirecat/cli-base": "0.74.0",
255
255
  "@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
256
256
  "commander": "15.0.0",
257
257
  "cspell": "10.4.0",
@@ -271,7 +271,7 @@
271
271
  },
272
272
  "peerDependencies": {
273
273
  "@napi-rs/canvas": ">=0.1.69 <2",
274
- "@wirecat/cli-base": ">=0.73.0 <0.74.0",
274
+ "@wirecat/cli-base": ">=0.74.0 <0.75.0",
275
275
  "commander": ">=15"
276
276
  },
277
277
  "bin": {
package/parity.json CHANGED
@@ -242,6 +242,9 @@
242
242
  "value": "<post>",
243
243
  "meaning": "comment on this channel post; the comment goes to the post's discussion group"
244
244
  },
245
+ "--compact": {
246
+ "meaning": "one line per item, built from the same answer as --json, with its paging and next steps as # lines"
247
+ },
245
248
  "--component": {
246
249
  "value": "<name>",
247
250
  "meaning": "the ranking component whose contributing messages or answer pairs to read"
@@ -1131,6 +1134,7 @@
1131
1134
  },
1132
1135
  "globalOptions": {
1133
1136
  "--brief": "all",
1137
+ "--compact": "all",
1134
1138
  "--debug": "all",
1135
1139
  "--dry-run": "all",
1136
1140
  "--full": "all",