@wirecat/cli-core 0.545.0 → 0.547.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 (175) hide show
  1. package/CHANGELOG.md +228 -0
  2. package/dist/calendar/busy.js +4 -1
  3. package/dist/calendar/cli/answers.d.ts +31 -0
  4. package/dist/calendar/cli/answers.js +8 -0
  5. package/dist/calendar/cli/index.d.ts +6 -0
  6. package/dist/calendar/cli/index.js +43 -4
  7. package/dist/calendar/cli/remind.js +2 -1
  8. package/dist/calendar/expand.d.ts +17 -0
  9. package/dist/calendar/expand.js +340 -0
  10. package/dist/calendar/feeds.d.ts +96 -0
  11. package/dist/calendar/feeds.js +324 -0
  12. package/dist/calendar/ics.d.ts +15 -4
  13. package/dist/calendar/ics.js +18 -7
  14. package/dist/calendar/index.d.ts +3 -1
  15. package/dist/calendar/index.js +2 -0
  16. package/dist/calendar/server-search.js +3 -1
  17. package/dist/calendar/writes.js +15 -4
  18. package/dist/cli/after-dashes.d.ts +16 -0
  19. package/dist/cli/after-dashes.js +41 -0
  20. package/dist/cli/bot/context.d.ts +1 -1
  21. package/dist/cli/bot/context.js +9 -1
  22. package/dist/cli/bot/messages.js +2 -0
  23. package/dist/cli/bot/setup.js +1 -0
  24. package/dist/cli/contract-templates.js +2 -2
  25. package/dist/cli/entry.d.ts +2 -0
  26. package/dist/cli/entry.js +3 -2
  27. package/dist/cli/index.d.ts +2 -1
  28. package/dist/cli/index.js +2 -1
  29. package/dist/cli/messenger/admin-commands.js +5 -1
  30. package/dist/cli/messenger/admin-contacts-command.js +5 -1
  31. package/dist/cli/messenger/admin-folders-command.js +5 -1
  32. package/dist/cli/messenger/complete-command.js +1 -0
  33. package/dist/cli/messenger/history-query.d.ts +10 -4
  34. package/dist/cli/messenger/history-query.js +87 -31
  35. package/dist/cli/messenger/messages-command.js +12 -23
  36. package/dist/cli/messenger/messages-comments-command.js +4 -2
  37. package/dist/cli/messenger/messages-edit-command.js +1 -0
  38. package/dist/cli/messenger/messages-press-command.js +1 -0
  39. package/dist/cli/messenger/messages-send-command.js +1 -0
  40. package/dist/cli/messenger/observed.js +2 -2
  41. package/dist/cli/messenger/polls-command.js +1 -0
  42. package/dist/cli/messenger/private-people-command.js +5 -1
  43. package/dist/cli/messenger/store-maintenance-command.d.ts +1 -1
  44. package/dist/cli/messenger/stored.js +56 -3
  45. package/dist/cli/messenger/topics-command.js +1 -1
  46. package/dist/cli/option-hints.d.ts +6 -0
  47. package/dist/cli/option-hints.js +15 -0
  48. package/dist/cli/program.js +17 -6
  49. package/dist/cli/schedule/schedule-command.js +1 -0
  50. package/dist/domain/message-link.d.ts +3 -0
  51. package/dist/domain/message-link.js +9 -14
  52. package/dist/domain/money-words.d.ts +11 -0
  53. package/dist/domain/money-words.js +38 -0
  54. package/dist/domain/providers.d.ts +2 -0
  55. package/dist/domain/providers.js +15 -3
  56. package/dist/domain/ref-arguments.d.ts +31 -0
  57. package/dist/domain/ref-arguments.js +79 -0
  58. package/dist/domain/references.d.ts +20 -2
  59. package/dist/domain/references.js +132 -47
  60. package/dist/errors/reasons.js +31 -1
  61. package/dist/index.d.ts +1 -1
  62. package/dist/index.js +1 -1
  63. package/dist/mail/cli/index.js +4 -4
  64. package/dist/mail/index.d.ts +2 -0
  65. package/dist/mail/index.js +2 -0
  66. package/dist/mail/partial.d.ts +34 -0
  67. package/dist/mail/partial.js +94 -0
  68. package/dist/mail/server-search.js +17 -9
  69. package/dist/mail/source.d.ts +13 -2
  70. package/dist/mail/source.js +7 -1
  71. package/dist/mail/sync-types.d.ts +112 -0
  72. package/dist/mail/sync-types.js +1 -0
  73. package/dist/mail/sync.d.ts +4 -106
  74. package/dist/mail/sync.js +49 -4
  75. package/dist/mail/testing/fake.d.ts +3 -1
  76. package/dist/mail/testing/fake.js +10 -2
  77. package/dist/meetings/mcp/index.d.ts +15 -15
  78. package/dist/oauth/client-file.d.ts +14 -0
  79. package/dist/oauth/client-file.js +28 -0
  80. package/dist/oauth/index.d.ts +1 -0
  81. package/dist/oauth/index.js +1 -0
  82. package/dist/ports/messenger.d.ts +3 -2
  83. package/dist/resolve.d.ts +3 -1
  84. package/dist/resolve.js +8 -0
  85. package/dist/search/task-signature.d.ts +5 -1
  86. package/dist/search/task-signature.js +27 -9
  87. package/dist/services/agenda.d.ts +5 -0
  88. package/dist/services/agenda.js +3 -1
  89. package/dist/services/deps.d.ts +0 -2
  90. package/dist/services/deps.js +0 -2
  91. package/dist/services/documents/ingestion.js +1 -1
  92. package/dist/services/documents/knowledge.d.ts +10 -6
  93. package/dist/services/documents/knowledge.js +25 -11
  94. package/dist/services/index.d.ts +6 -1
  95. package/dist/services/index.js +6 -1
  96. package/dist/services/message-resolution.d.ts +4 -1
  97. package/dist/services/message-resolution.js +19 -3
  98. package/dist/services/messages.d.ts +2 -0
  99. package/dist/services/messages.js +40 -36
  100. package/dist/services/money-words.d.ts +3 -0
  101. package/dist/services/money-words.js +5 -0
  102. package/dist/services/proposal-tasks.d.ts +3 -2
  103. package/dist/services/proposal-tasks.js +5 -2
  104. package/dist/services/proposals/ids.d.ts +5 -0
  105. package/dist/services/proposals/ids.js +43 -0
  106. package/dist/services/proposals/index.d.ts +3 -0
  107. package/dist/services/proposals/index.js +2 -0
  108. package/dist/services/proposals/kinds/action.d.ts +7 -0
  109. package/dist/services/proposals/kinds/action.js +45 -0
  110. package/dist/services/proposals/kinds/fact.d.ts +4 -0
  111. package/dist/services/proposals/kinds/fact.js +43 -0
  112. package/dist/services/proposals/kinds/findings.d.ts +7 -0
  113. package/dist/services/proposals/kinds/findings.js +75 -0
  114. package/dist/services/proposals/kinds/key-document.d.ts +9 -0
  115. package/dist/services/proposals/kinds/key-document.js +75 -0
  116. package/dist/services/proposals/kinds/memory.d.ts +4 -0
  117. package/dist/services/proposals/kinds/memory.js +42 -0
  118. package/dist/services/proposals/kinds/relationship.d.ts +10 -0
  119. package/dist/services/proposals/kinds/relationship.js +82 -0
  120. package/dist/services/proposals/kinds/shared.d.ts +25 -0
  121. package/dist/services/proposals/kinds/shared.js +49 -0
  122. package/dist/services/proposals/registry.d.ts +9 -0
  123. package/dist/services/proposals/registry.js +129 -0
  124. package/dist/services/proposals/types.d.ts +85 -0
  125. package/dist/services/proposals/types.js +1 -0
  126. package/dist/services/questions.d.ts +7 -2
  127. package/dist/services/questions.js +13 -4
  128. package/dist/services/server-search.d.ts +7 -4
  129. package/dist/services/server-search.js +5 -2
  130. package/dist/services/stored-project-gathering.d.ts +9 -0
  131. package/dist/services/stored-project-gathering.js +9 -4
  132. package/dist/services/task-categories.d.ts +2 -0
  133. package/dist/services/task-categories.js +19 -8
  134. package/dist/services/tasks.js +8 -4
  135. package/dist/store/decisions.d.ts +65 -0
  136. package/dist/store/decisions.js +55 -0
  137. package/dist/store/evidence-findings.d.ts +6 -6
  138. package/dist/store/index.d.ts +4 -3
  139. package/dist/store/index.js +2 -1
  140. package/dist/store/meeting-findings.d.ts +2 -0
  141. package/dist/store/messages.d.ts +4 -9
  142. package/dist/store/sqlite/actors.d.ts +13 -2
  143. package/dist/store/sqlite/actors.js +24 -1
  144. package/dist/store/sqlite/email-locations.d.ts +16 -0
  145. package/dist/store/sqlite/email-locations.js +26 -0
  146. package/dist/store/sqlite/emails.d.ts +6 -2
  147. package/dist/store/sqlite/emails.js +2 -1
  148. package/dist/store/sqlite/evidence-findings.js +13 -3
  149. package/dist/store/sqlite/evidence-sources.js +2 -2
  150. package/dist/store/sqlite/facts.d.ts +16 -8
  151. package/dist/store/sqlite/facts.js +48 -15
  152. package/dist/store/sqlite/findings-core.d.ts +2 -1
  153. package/dist/store/sqlite/findings-core.js +4 -4
  154. package/dist/store/sqlite/identities.js +1 -1
  155. package/dist/store/sqlite/knowledge.d.ts +43 -7
  156. package/dist/store/sqlite/knowledge.js +62 -17
  157. package/dist/store/sqlite/meeting-findings.js +6 -6
  158. package/dist/store/sqlite/memories.d.ts +12 -8
  159. package/dist/store/sqlite/memories.js +33 -10
  160. package/dist/store/sqlite/notes.d.ts +5 -1
  161. package/dist/store/sqlite/notes.js +22 -6
  162. package/dist/store/sqlite/open-store.d.ts +4 -1
  163. package/dist/store/sqlite/open-store.js +8 -2
  164. package/dist/store/sqlite/open.d.ts +3 -0
  165. package/dist/store/sqlite/proposed-actions.d.ts +21 -8
  166. package/dist/store/sqlite/proposed-actions.js +29 -9
  167. package/dist/store/sqlite/reads.d.ts +13 -2
  168. package/dist/store/sqlite/reads.js +8 -4
  169. package/dist/store/sqlite/relation-proposals.d.ts +34 -0
  170. package/dist/store/sqlite/relation-proposals.js +114 -0
  171. package/dist/store/sqlite/things.d.ts +0 -1
  172. package/dist/store/sqlite/things.js +25 -24
  173. package/dist/store/store.d.ts +3 -0
  174. package/dist/store/store.js +2 -0
  175. package/package.json +1 -1
@@ -0,0 +1,43 @@
1
+ import { kept, proposerOfAuthor, pushed } from "./shared.js";
2
+ const STATUS = {
3
+ proposed: "proposed",
4
+ accepted: "confirmed",
5
+ superseded: "confirmed",
6
+ reversed: "confirmed",
7
+ rejected: "rejected",
8
+ };
9
+ const rowOf = (fact) => ({
10
+ id: fact.ref,
11
+ kind: "fact",
12
+ status: STATUS[fact.status],
13
+ project: fact.project,
14
+ subject: fact.subject ?? fact.ref,
15
+ object: null,
16
+ summary: fact.statement,
17
+ proposedBy: proposerOfAuthor(fact.author),
18
+ reason: null,
19
+ evidence: fact.evidence.slice(0, 5),
20
+ score: null,
21
+ signals: null,
22
+ parts: null,
23
+ createdAt: fact.createdAt,
24
+ });
25
+ /** Facts keep their own table and lifecycle; an agent's waits as `proposed`. */
26
+ export const factKind = (store) => ({
27
+ kind: "fact",
28
+ prefix: "fact",
29
+ list: async (filter) => {
30
+ const status = filter.status ?? "proposed";
31
+ const facts = await store.facts.list({
32
+ ...pushed(filter),
33
+ ...(status === "all" ? {} : { status: status === "confirmed" ? "accepted" : status }),
34
+ order: "created",
35
+ });
36
+ return kept(facts.map(rowOf), filter);
37
+ },
38
+ get: async (id) => rowOf(await store.facts.get(id)),
39
+ decide: async (id, { confirm, by, reason }) => {
40
+ const fact = confirm ? await store.facts.accept(id, by, reason) : await store.facts.reject(id, by, reason);
41
+ return { id, kind: "fact", status: confirm ? "confirmed" : "rejected", made: confirm ? [fact.ref] : [] };
42
+ },
43
+ });
@@ -0,0 +1,7 @@
1
+ import { type MessageStore } from "../../../store/index.js";
2
+ import type { ProposalKind } from "../types.js";
3
+ /**
4
+ * An agent's batch of cited facts and tasks from stored messages, mail and documents. Confirming accepts the parts
5
+ * named, or every finding not rejected before; the rest are rejected. Meeting findings stay with their meeting.
6
+ */
7
+ export declare const findingsKind: (store: Pick<MessageStore, "evidenceFindings" | "proposedActions">) => ProposalKind;
@@ -0,0 +1,75 @@
1
+ import { EVIDENCE_FINDINGS_KIND } from "../../../store/index.js";
2
+ import { kept, proposerOfAuthor } from "./shared.js";
3
+ const statusOf = (batch) => batch.status === "proposed" ? "proposed" : batch.receipt?.accepted.length ? "confirmed" : "rejected";
4
+ const rowOf = (batch, createdAt) => {
5
+ const findings = batch.input.findings;
6
+ const outcome = (id) => batch.receipt?.items.find((item) => item.id === id)?.status ?? null;
7
+ return {
8
+ id: batch.id,
9
+ kind: "findings",
10
+ status: statusOf(batch),
11
+ project: batch.project,
12
+ subject: batch.subject ?? batch.project ?? `findings:${batch.id}`,
13
+ object: null,
14
+ summary: `${findings.length} finding${findings.length === 1 ? "" : "s"}`,
15
+ proposedBy: proposerOfAuthor(batch.author),
16
+ reason: null,
17
+ evidence: [...new Set(findings.flatMap((finding) => finding.evidence.map((one) => one.reference)))].slice(0, 5),
18
+ score: null,
19
+ signals: null,
20
+ parts: findings.map((finding) => ({
21
+ id: finding.id,
22
+ kind: finding.kind,
23
+ summary: finding.kind === "fact" ? finding.statement : finding.title,
24
+ outcome: outcome(finding.id),
25
+ })),
26
+ createdAt,
27
+ };
28
+ };
29
+ /**
30
+ * An agent's batch of cited facts and tasks from stored messages, mail and documents. Confirming accepts the parts
31
+ * named, or every finding not rejected before; the rest are rejected. Meeting findings stay with their meeting.
32
+ */
33
+ export const findingsKind = (store) => {
34
+ // The batch view carries no staging time; its stored proposal does.
35
+ const created = async (batch) => rowOf(batch, (await store.proposedActions.get(batch.id)).createdAt);
36
+ return {
37
+ kind: "findings",
38
+ stored: [EVIDENCE_FINDINGS_KIND],
39
+ parts: true,
40
+ list: async (filter) => {
41
+ const status = filter.status ?? "proposed";
42
+ const rows = [];
43
+ // The findings list filters by project and status only; a proposer filter is applied page by page until full.
44
+ for (let offset = 0; rows.length < filter.limit; offset += 50) {
45
+ const page = await store.evidenceFindings.list({
46
+ ...(filter.project === undefined ? {} : { project: filter.project }),
47
+ ...(status === "proposed" ? { status: "proposed" } : {}),
48
+ ...(status === "confirmed" || status === "rejected" ? { status: "reviewed" } : {}),
49
+ limit: 50,
50
+ offset,
51
+ });
52
+ rows.push(...kept(await Promise.all(page.items.map(created)), { ...filter, limit: 50 }));
53
+ if (!page.hasMore)
54
+ break;
55
+ }
56
+ return kept(rows, filter);
57
+ },
58
+ get: async (id) => created(await store.evidenceFindings.get(id)),
59
+ decide: async (id, { confirm, by, parts, reason }) => {
60
+ const batch = await store.evidenceFindings.get(id);
61
+ const turnedDown = new Set(batch.previous.filter((one) => one.status === "rejected").map((one) => one.id));
62
+ const accept = !confirm
63
+ ? []
64
+ : (parts ?? batch.input.findings.map((finding) => finding.id).filter((one) => !turnedDown.has(one)));
65
+ const reviewed = await store.evidenceFindings.review(id, {
66
+ fingerprint: batch.fingerprint,
67
+ accept,
68
+ by,
69
+ ...(reason === undefined ? {} : { reason }),
70
+ });
71
+ const made = (reviewed.receipt?.items ?? []).flatMap((item) => item.status === "accepted" && item.reference && !item.reused ? [item.reference] : []);
72
+ return { id, kind: "findings", status: accept.length ? "confirmed" : "rejected", made };
73
+ },
74
+ };
75
+ };
@@ -0,0 +1,9 @@
1
+ import type { MessageStore } from "../../../store/index.js";
2
+ import type { ProposalKind } from "../types.js";
3
+ type KeyDocumentStore = Pick<MessageStore, "knowledge" | "notes" | "proposedActions">;
4
+ /**
5
+ * A note, document or message proposed as one of a project's key documents. Stored `key_document` rows are the new
6
+ * home; older suggestions are unconfirmed `key` links, shown as `key:<id>`.
7
+ */
8
+ export declare const keyDocumentKind: (store: KeyDocumentStore) => ProposalKind;
9
+ export {};
@@ -0,0 +1,75 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { proposerOfAction } from "../../../store/index.js";
3
+ import { actionStatus, kept, proposedLink, pushed, storedStatus } from "./shared.js";
4
+ const linkRow = (link) => ({
5
+ id: `key:${link.id}`,
6
+ kind: "key_document",
7
+ status: "proposed",
8
+ project: link.to,
9
+ subject: link.from,
10
+ object: link.to,
11
+ summary: null,
12
+ proposedBy: { type: "unknown" },
13
+ reason: link.evidence,
14
+ evidence: [link.from],
15
+ score: null,
16
+ signals: null,
17
+ parts: null,
18
+ createdAt: link.createdAt,
19
+ });
20
+ const storedRow = (action) => {
21
+ const payload = action.payload;
22
+ return {
23
+ id: action.id,
24
+ kind: "key_document",
25
+ status: actionStatus(action.status),
26
+ project: payload.project,
27
+ subject: payload.ref,
28
+ object: payload.project,
29
+ summary: null,
30
+ proposedBy: proposerOfAction(action),
31
+ reason: action.reason ?? payload.reason ?? null,
32
+ evidence: [payload.ref],
33
+ score: null,
34
+ signals: null,
35
+ parts: null,
36
+ createdAt: action.createdAt,
37
+ };
38
+ };
39
+ /**
40
+ * A note, document or message proposed as one of a project's key documents. Stored `key_document` rows are the new
41
+ * home; older suggestions are unconfirmed `key` links, shown as `key:<id>`.
42
+ */
43
+ export const keyDocumentKind = (store) => {
44
+ const legacy = async () => (await store.notes.links({ kind: "related-to", role: "key", confirmed: false })).filter((link) => link.to?.startsWith("project:") && proposedLink(link.provenance));
45
+ const legacyLink = async (id) => {
46
+ const link = (await legacy()).find((one) => `key:${one.id}` === id);
47
+ if (!link)
48
+ throw new CliError("not_found", `no suggested key document ${id}`);
49
+ return link;
50
+ };
51
+ return {
52
+ kind: "key_document",
53
+ prefix: "key",
54
+ stored: ["key_document"],
55
+ list: async (filter) => {
56
+ const status = storedStatus(filter);
57
+ const stored = await store.proposedActions.list({
58
+ kinds: ["key_document"],
59
+ ...(status === undefined ? {} : { status }),
60
+ ...pushed(filter),
61
+ });
62
+ const links = (filter.status ?? "proposed") === "rejected" ? [] : await legacy();
63
+ return kept([...stored.map(storedRow), ...links.map(linkRow)], filter);
64
+ },
65
+ get: async (id) => id.startsWith("key:") ? linkRow(await legacyLink(id)) : storedRow(await store.proposedActions.get(id)),
66
+ decide: async (id, { confirm, by, reason }) => {
67
+ const status = confirm ? "confirmed" : "rejected";
68
+ const decision = { confirm, by, ...(reason === undefined ? {} : { reason }) };
69
+ const link = id.startsWith("key:")
70
+ ? await store.knowledge.decideSuggestedLink(id.slice("key:".length), decision)
71
+ : (await store.knowledge.decideLinkProposal(id, decision)).link;
72
+ return { id, kind: "key_document", status, made: link ? [`link:${link.id}`] : [] };
73
+ },
74
+ };
75
+ };
@@ -0,0 +1,4 @@
1
+ import type { MessageStore } from "../../../store/index.js";
2
+ import type { ProposalKind } from "../types.js";
3
+ /** Memories keep their own table; rejecting one marks it stale. */
4
+ export declare const memoryKind: (store: Pick<MessageStore, "memories">) => ProposalKind;
@@ -0,0 +1,42 @@
1
+ import { kept, proposerOfAuthor, pushed } from "./shared.js";
2
+ /** A stale memory is the one a person turned down; a superseded one held until another replaced it. */
3
+ const STATUS = {
4
+ proposed: "proposed",
5
+ confirmed: "confirmed",
6
+ superseded: "confirmed",
7
+ stale: "rejected",
8
+ };
9
+ const rowOf = (memory) => ({
10
+ id: memory.ref,
11
+ kind: "memory",
12
+ status: STATUS[memory.status],
13
+ project: memory.project,
14
+ subject: memory.subject ?? memory.ref,
15
+ object: null,
16
+ summary: null,
17
+ proposedBy: proposerOfAuthor(memory.author),
18
+ reason: null,
19
+ evidence: memory.evidence.slice(0, 5),
20
+ score: null,
21
+ signals: null,
22
+ parts: null,
23
+ createdAt: memory.createdAt,
24
+ });
25
+ /** Memories keep their own table; rejecting one marks it stale. */
26
+ export const memoryKind = (store) => ({
27
+ kind: "memory",
28
+ prefix: "memory",
29
+ list: async (filter) => {
30
+ const status = filter.status ?? "proposed";
31
+ const memories = await store.memories.list({
32
+ ...pushed(filter),
33
+ ...(status === "all" ? {} : { status: status === "rejected" ? "stale" : status }),
34
+ });
35
+ return kept(memories.map(rowOf), filter);
36
+ },
37
+ get: async (id) => rowOf(await store.memories.get(id)),
38
+ decide: async (id, { confirm, by }) => {
39
+ const memory = confirm ? await store.memories.confirm(id, by) : await store.memories.markStale(id, by);
40
+ return { id, kind: "memory", status: confirm ? "confirmed" : "rejected", made: confirm ? [memory.ref] : [] };
41
+ },
42
+ });
@@ -0,0 +1,10 @@
1
+ import type { MessageStore } from "../../../store/index.js";
2
+ import type { ProposalKind } from "../types.js";
3
+ type RelationshipStore = Pick<MessageStore, "knowledge" | "notes" | "proposedActions">;
4
+ /**
5
+ * A person, organization, chat or mail thread proposed as related to another thing. New proposals are stored
6
+ * `relationship` rows; suggestions written before them are unconfirmed links, shown as `link:<id>` until the store
7
+ * moves them.
8
+ */
9
+ export declare const relationshipKind: (store: RelationshipStore) => ProposalKind;
10
+ export {};
@@ -0,0 +1,82 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { proposerOfAction } from "../../../store/index.js";
3
+ import { actionStatus, kept, projectOf, proposedLink, pushed, storedStatus } from "./shared.js";
4
+ const linkRow = (link) => ({
5
+ id: `link:${link.id}`,
6
+ kind: "relationship",
7
+ status: "proposed",
8
+ project: projectOf(link.to, link.from),
9
+ subject: link.from,
10
+ object: link.to,
11
+ summary: link.role,
12
+ proposedBy: { type: "unknown" },
13
+ reason: link.evidence,
14
+ evidence: [],
15
+ score: null,
16
+ signals: null,
17
+ parts: null,
18
+ createdAt: link.createdAt,
19
+ });
20
+ const storedRow = (action) => {
21
+ const payload = action.payload;
22
+ return {
23
+ id: action.id,
24
+ kind: "relationship",
25
+ status: actionStatus(action.status),
26
+ project: projectOf(payload.to, payload.from),
27
+ subject: payload.from,
28
+ object: payload.to,
29
+ summary: payload.role ?? null,
30
+ proposedBy: proposerOfAction(action),
31
+ reason: action.reason,
32
+ evidence: (payload.evidence ?? []).slice(0, 5),
33
+ score: payload.score ?? null,
34
+ signals: payload.signals ?? null,
35
+ parts: null,
36
+ createdAt: action.createdAt,
37
+ };
38
+ };
39
+ /**
40
+ * A person, organization, chat or mail thread proposed as related to another thing. New proposals are stored
41
+ * `relationship` rows; suggestions written before them are unconfirmed links, shown as `link:<id>` until the store
42
+ * moves them.
43
+ */
44
+ export const relationshipKind = (store) => {
45
+ const legacy = async () => {
46
+ const [members, related] = await Promise.all([
47
+ store.notes.links({ kind: "member-of", confirmed: false }),
48
+ store.notes.links({ kind: "related-to", confirmed: false }),
49
+ ]);
50
+ return [...members, ...related].filter((link) => link.to !== null && link.role !== "key" && proposedLink(link.provenance));
51
+ };
52
+ const legacyLink = async (id) => {
53
+ const link = (await legacy()).find((one) => `link:${one.id}` === id);
54
+ if (!link)
55
+ throw new CliError("not_found", `no proposed relationship ${id}`);
56
+ return link;
57
+ };
58
+ return {
59
+ kind: "relationship",
60
+ prefix: "link",
61
+ stored: ["relationship"],
62
+ list: async (filter) => {
63
+ const status = storedStatus(filter);
64
+ const stored = await store.proposedActions.list({
65
+ kinds: ["relationship"],
66
+ ...(status === undefined ? {} : { status }),
67
+ ...pushed(filter),
68
+ });
69
+ const links = (filter.status ?? "proposed") === "rejected" ? [] : await legacy();
70
+ return kept([...stored.map(storedRow), ...links.map(linkRow)], filter);
71
+ },
72
+ get: async (id) => id.startsWith("link:") ? linkRow(await legacyLink(id)) : storedRow(await store.proposedActions.get(id)),
73
+ decide: async (id, { confirm, by, reason }) => {
74
+ const status = confirm ? "confirmed" : "rejected";
75
+ const decision = { confirm, by, ...(reason === undefined ? {} : { reason }) };
76
+ const link = id.startsWith("link:")
77
+ ? await store.knowledge.decideSuggestedLink(id.slice("link:".length), decision)
78
+ : (await store.knowledge.decideLinkProposal(id, decision)).link;
79
+ return { id, kind: "relationship", status, made: link ? [`relationship:${link.id}`] : [] };
80
+ },
81
+ };
82
+ };
@@ -0,0 +1,25 @@
1
+ import type { Proposer } from "../../../store/decisions.js";
2
+ import type { ProposalFilter, ProposalRow } from "../types.js";
3
+ /** A stored author as a proposer, with the kind read from its bot row. */
4
+ export declare const proposerOfAuthor: (author: {
5
+ type: "person" | "bot";
6
+ id: string;
7
+ name: string | null;
8
+ kind: "agent" | "rule" | null;
9
+ }) => Proposer;
10
+ /** What a kind keeps of the rows it read: the filter's status and proposer, newest first, at most `limit`. */
11
+ export declare const kept: (rows: ProposalRow[], filter: ProposalFilter) => ProposalRow[];
12
+ /** The filters every store list takes, so a page is filled by the query and not cut after it. */
13
+ export declare const pushed: (filter: ProposalFilter) => {
14
+ project?: string | undefined;
15
+ proposedBy?: "agent" | "rule" | {
16
+ bot: string;
17
+ } | undefined;
18
+ limit: number;
19
+ };
20
+ /** A suggestion an agent or a rule wrote; Drive's editor links and other records stay out of the inbox. */
21
+ export declare const proposedLink: (provenance: string | null) => boolean;
22
+ export declare const projectOf: (...refs: (string | null | undefined)[]) => string | null;
23
+ export declare const actionStatus: (status: string) => ProposalRow["status"];
24
+ /** The stored statuses a filter's status reads, so the store does the filtering when it can. */
25
+ export declare const storedStatus: (filter: ProposalFilter) => "proposed" | "rejected" | undefined;
@@ -0,0 +1,49 @@
1
+ /** A stored author as a proposer, with the kind read from its bot row. */
2
+ export const proposerOfAuthor = (author) => {
3
+ if (author.type === "person")
4
+ return { type: "person", id: author.id };
5
+ if (author.name === null)
6
+ return { type: "unknown" };
7
+ return { type: "bot", id: author.id, name: author.name, kind: author.kind ?? "agent" };
8
+ };
9
+ const byMatches = (row, by) => {
10
+ if (by === undefined)
11
+ return true;
12
+ const proposer = row.proposedBy;
13
+ if (proposer.type !== "bot")
14
+ return false;
15
+ if (by === "agent" || by === "rule")
16
+ return proposer.kind === by;
17
+ return proposer.name === by.bot;
18
+ };
19
+ /** What a kind keeps of the rows it read: the filter's status and proposer, newest first, at most `limit`. */
20
+ export const kept = (rows, filter) => {
21
+ const status = filter.status ?? "proposed";
22
+ return rows
23
+ .filter((row) => (status === "all" || row.status === status) && byMatches(row, filter.by))
24
+ .filter((row) => filter.project === undefined || row.project === filter.project)
25
+ .sort((a, b) => b.createdAt.localeCompare(a.createdAt))
26
+ .slice(0, filter.limit);
27
+ };
28
+ /** The filters every store list takes, so a page is filled by the query and not cut after it. */
29
+ export const pushed = (filter) => ({
30
+ ...(filter.project === undefined ? {} : { project: filter.project }),
31
+ ...(filter.by === undefined ? {} : { proposedBy: filter.by }),
32
+ limit: Math.min(filter.limit, 500),
33
+ });
34
+ /** A suggestion an agent or a rule wrote; Drive's editor links and other records stay out of the inbox. */
35
+ export const proposedLink = (provenance) => /^(agent|rule):/.test(provenance ?? "");
36
+ export const projectOf = (...refs) => refs.find((ref) => typeof ref === "string" && ref.startsWith("project:")) ?? null;
37
+ const ACTION_STATUS = {
38
+ proposed: "proposed",
39
+ approved: "confirmed",
40
+ executed: "confirmed",
41
+ failed: "confirmed",
42
+ rejected: "rejected",
43
+ };
44
+ export const actionStatus = (status) => ACTION_STATUS[status] ?? "proposed";
45
+ /** The stored statuses a filter's status reads, so the store does the filtering when it can. */
46
+ export const storedStatus = (filter) => {
47
+ const status = filter.status ?? "proposed";
48
+ return status === "proposed" || status === "rejected" ? status : undefined;
49
+ };
@@ -0,0 +1,9 @@
1
+ import type { MessageStore } from "../../store/store.js";
2
+ import type { ProposalRegistry } from "./types.js";
3
+ /** An empty registry: the host registers its kinds. `proposalRegistry` is this with the core kinds. */
4
+ export declare const emptyProposalRegistry: (store: Pick<MessageStore, "proposedActions" | "decisionPolicy">) => ProposalRegistry & {
5
+ claimed(): string[];
6
+ };
7
+ /** Every proposal kind core knows, behind one check of who may decide. */
8
+ /** The policy is the store's, set when it was opened, so the registry and the store methods never disagree. */
9
+ export declare const proposalRegistry: (store: MessageStore) => ProposalRegistry;
@@ -0,0 +1,129 @@
1
+ import { CliError } from "@wirecat/cli-base";
2
+ import { mayDecide } from "../../store/decisions.js";
3
+ import { actionKind } from "./kinds/action.js";
4
+ import { factKind } from "./kinds/fact.js";
5
+ import { findingsKind } from "./kinds/findings.js";
6
+ import { keyDocumentKind } from "./kinds/key-document.js";
7
+ import { memoryKind } from "./kinds/memory.js";
8
+ import { relationshipKind } from "./kinds/relationship.js";
9
+ const PREFIX = /^([a-z][a-z_]*):/;
10
+ const refusalOf = (id, error) => {
11
+ if (!(error instanceof CliError))
12
+ throw error;
13
+ const code = error.code === "not_found" || error.code === "permission_error" ? error.code : "validation_error";
14
+ const reason = error.details?.reason;
15
+ return { id, code, reason: typeof reason === "string" ? reason : code, message: error.message };
16
+ };
17
+ /** An empty registry: the host registers its kinds. `proposalRegistry` is this with the core kinds. */
18
+ export const emptyProposalRegistry = (store) => {
19
+ const policy = store.decisionPolicy;
20
+ const kinds = [];
21
+ const claimed = () => kinds.flatMap((kind) => kind.stored ?? []);
22
+ const kindOf = async (id) => {
23
+ const prefix = PREFIX.exec(id)?.[1];
24
+ const found = prefix !== undefined
25
+ ? kinds.find((kind) => kind.prefix === prefix)
26
+ : await (async () => {
27
+ const stored = (await store.proposedActions.get(id)).kind;
28
+ return (kinds.find((kind) => kind.stored?.includes(stored)) ??
29
+ kinds.find((kind) => kind.prefix === undefined && kind.stored === undefined));
30
+ })();
31
+ if (!found)
32
+ throw new CliError("not_found", `no proposal ${id}`, { reason: "proposal.unknown-kind" });
33
+ return found;
34
+ };
35
+ const registry = {
36
+ claimed,
37
+ register: (kind) => {
38
+ if (kinds.some((one) => one.kind === kind.kind))
39
+ throw new CliError("configuration_error", `proposal kind ${kind.kind} is registered twice`);
40
+ if (kind.prefix !== undefined && kinds.some((one) => one.prefix === kind.prefix))
41
+ throw new CliError("configuration_error", `proposal prefix ${kind.prefix} is registered twice`);
42
+ kinds.push(kind);
43
+ },
44
+ list: async (filter) => {
45
+ if (!Number.isSafeInteger(filter.limit) || filter.limit < 1 || filter.limit > 50)
46
+ throw new CliError("validation_error", "a proposal list takes 1–50 items");
47
+ if (!Number.isSafeInteger(filter.offset) || filter.offset < 0)
48
+ throw new CliError("validation_error", "a proposal list takes a nonnegative offset");
49
+ const asked = filter.kinds === undefined ? kinds : kinds.filter((kind) => filter.kinds?.includes(kind.kind));
50
+ const wide = { ...filter, offset: 0, limit: filter.offset + filter.limit + 1 };
51
+ const rows = (await Promise.all(asked.map((kind) => kind.list(wide)))).flat();
52
+ rows.sort((a, b) => b.createdAt.localeCompare(a.createdAt) || b.id.localeCompare(a.id));
53
+ const page = rows.slice(filter.offset, filter.offset + filter.limit + 1);
54
+ return { items: page.slice(0, filter.limit), hasMore: page.length > filter.limit };
55
+ },
56
+ get: async (id) => (await kindOf(id)).get(id),
57
+ decide: async (ids, decision) => {
58
+ const parts = new Map();
59
+ const whole = new Set();
60
+ const order = [];
61
+ for (const id of ids) {
62
+ const [batch, part] = id.split("/");
63
+ if (!batch)
64
+ continue;
65
+ if (!parts.has(batch)) {
66
+ parts.set(batch, []);
67
+ order.push(batch);
68
+ }
69
+ if (part === undefined)
70
+ whole.add(batch);
71
+ else
72
+ parts.get(batch)?.push(part);
73
+ }
74
+ const decided = [];
75
+ const refused = [];
76
+ const refuseIds = (id, message) => refused.push({ id, code: "validation_error", reason: "proposal.ids", message });
77
+ for (const id of order) {
78
+ const chosen = parts.get(id) ?? [];
79
+ if (chosen.length && whole.has(id)) {
80
+ refuseIds(id, `name ${id} or some of its parts (${id}/${chosen[0]}), not both`);
81
+ continue;
82
+ }
83
+ try {
84
+ const kind = await kindOf(id);
85
+ if (chosen.length && !kind.parts) {
86
+ refuseIds(id, `a ${kind.kind} proposal has no parts; name ${id} alone`);
87
+ continue;
88
+ }
89
+ const row = await kind.get(id);
90
+ if (row.status !== "proposed") {
91
+ refused.push({
92
+ id,
93
+ code: "validation_error",
94
+ reason: "proposal.wrong-state",
95
+ message: `${id} is already ${row.status}`,
96
+ });
97
+ continue;
98
+ }
99
+ const refusal = mayDecide(row, decision.by, policy);
100
+ if (refusal) {
101
+ refused.push(refusal);
102
+ continue;
103
+ }
104
+ decided.push(await kind.decide(id, chosen.length ? { ...decision, parts: chosen } : decision));
105
+ }
106
+ catch (error) {
107
+ refused.push(refusalOf(id, error));
108
+ }
109
+ }
110
+ return { decided, refused };
111
+ },
112
+ };
113
+ return registry;
114
+ };
115
+ /** Every proposal kind core knows, behind one check of who may decide. */
116
+ /** The policy is the store's, set when it was opened, so the registry and the store methods never disagree. */
117
+ export const proposalRegistry = (store) => {
118
+ const registry = emptyProposalRegistry(store);
119
+ for (const kind of [
120
+ relationshipKind(store),
121
+ keyDocumentKind(store),
122
+ factKind(store),
123
+ memoryKind(store),
124
+ findingsKind(store),
125
+ ])
126
+ registry.register(kind);
127
+ registry.register(actionKind(store, registry.claimed));
128
+ return registry;
129
+ };
@@ -0,0 +1,85 @@
1
+ import type { Decider, Proposer, Refusal } from "../../store/decisions.js";
2
+ export interface ProposalRow {
3
+ /** `17070` for a stored proposal; `fact:512`, `memory:88`, `link:17` and `key:9` for the kinds kept elsewhere. */
4
+ id: string;
5
+ kind: string;
6
+ status: "proposed" | "confirmed" | "rejected";
7
+ project: string | null;
8
+ /** The reference it is about. */
9
+ subject: string;
10
+ /** The reference it links to, when it links. */
11
+ object: string | null;
12
+ /** A fact's statement or a finding count; never message text. */
13
+ summary: string | null;
14
+ proposedBy: Proposer;
15
+ reason: string | null;
16
+ /** At most 5 references. */
17
+ evidence: string[];
18
+ score: number | null;
19
+ signals: {
20
+ scorer: string;
21
+ weight: number;
22
+ why: string;
23
+ }[] | null;
24
+ parts: {
25
+ id: string;
26
+ kind: string;
27
+ summary: string;
28
+ outcome: "accepted" | "rejected" | null;
29
+ }[] | null;
30
+ createdAt: string;
31
+ }
32
+ export interface ProposalFilter {
33
+ project?: string;
34
+ kinds?: string[];
35
+ by?: "agent" | "rule" | {
36
+ bot: string;
37
+ };
38
+ status?: ProposalRow["status"] | "all";
39
+ limit: number;
40
+ offset: number;
41
+ }
42
+ export interface Decision {
43
+ confirm: boolean;
44
+ by: Decider;
45
+ /** Why, kept with the decision; a memory has nowhere to keep it. */
46
+ reason?: string;
47
+ /** The findings of a batch to accept; the rest are rejected. */
48
+ parts?: string[];
49
+ }
50
+ export interface Decided {
51
+ id: string;
52
+ kind: string;
53
+ status: "confirmed" | "rejected";
54
+ /** References the decision made: `relationship:2203`, `fact:530`, `task:…`. */
55
+ made: string[];
56
+ }
57
+ /**
58
+ * One kind of proposal. `prefix` names its own id space (`fact` for `fact:512`); a kind stored in `proposed_actions`
59
+ * names the stored kinds it owns in `stored` and has bare number ids. A new kind registers itself; nothing else changes.
60
+ */
61
+ export interface ProposalKind {
62
+ readonly kind: string;
63
+ readonly prefix?: string;
64
+ readonly stored?: readonly string[];
65
+ /** Whether its proposals have parts decided on their own, as a findings batch has findings. */
66
+ readonly parts?: boolean;
67
+ /** Bounded: at most `offset + limit` rows, newest first. */
68
+ list(filter: ProposalFilter): Promise<ProposalRow[]>;
69
+ get(id: string): Promise<ProposalRow>;
70
+ /** Called only after `mayDecide` allowed it. */
71
+ decide(id: string, decision: Decision): Promise<Decided>;
72
+ }
73
+ export interface ProposalRegistry {
74
+ register(kind: ProposalKind): void;
75
+ list(filter: ProposalFilter): Promise<{
76
+ items: ProposalRow[];
77
+ hasMore: boolean;
78
+ }>;
79
+ get(id: string): Promise<ProposalRow>;
80
+ /** Each id on its own: one refusal never undoes the others. */
81
+ decide(ids: string[], decision: Decision): Promise<{
82
+ decided: Decided[];
83
+ refused: Refusal[];
84
+ }>;
85
+ }
@@ -0,0 +1 @@
1
+ export {};