sentinelayer-cli 0.41.0 → 0.42.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 (44) hide show
  1. package/README.md +18 -0
  2. package/package.json +3 -2
  3. package/src/auth/admission-scope.js +34 -0
  4. package/src/auth/gate.js +6 -0
  5. package/src/auth/http.js +4 -0
  6. package/src/auth/service.js +6 -0
  7. package/src/board/done-gate.js +155 -0
  8. package/src/board/evidence-resolvers.js +138 -0
  9. package/src/cli.js +24 -1
  10. package/src/commands/board.js +116 -0
  11. package/src/commands/session.js +483 -47
  12. package/src/engram/build-state.js +183 -0
  13. package/src/engram/canonical.js +75 -0
  14. package/src/engram/document.js +190 -0
  15. package/src/engram/index.js +11 -2
  16. package/src/engram/ingest-document.js +107 -0
  17. package/src/engram/store.js +6 -1
  18. package/src/engram/tools.js +6 -0
  19. package/src/events/schema.js +18 -0
  20. package/src/legacy-cli.js +2 -2
  21. package/src/net/test-egress-guard.js +158 -0
  22. package/src/review/investor-dd-orchestrator.js +14 -2
  23. package/src/session/admission-auth.js +312 -0
  24. package/src/session/admission-preimage.js +90 -0
  25. package/src/session/admission.js +620 -0
  26. package/src/session/checkpoints.js +25 -1
  27. package/src/session/codebase-context.js +54 -0
  28. package/src/session/coordination-guidance.js +1 -1
  29. package/src/session/daemon-spawn.js +20 -2
  30. package/src/session/daemon.js +7 -1
  31. package/src/session/event-identity.js +9 -2
  32. package/src/session/first-message.js +11 -3
  33. package/src/session/listener.js +3 -0
  34. package/src/session/live-source.js +47 -16
  35. package/src/session/message-edits.js +89 -0
  36. package/src/session/recap.js +26 -4
  37. package/src/session/remote-hydrate.js +7 -19
  38. package/src/session/store.js +13 -14
  39. package/src/session/stream.js +23 -3
  40. package/src/session/sync.js +128 -11
  41. package/src/session/tickets.js +316 -0
  42. package/src/session/title-sync.js +2 -1
  43. package/src/session/usage-report.js +41 -2
  44. package/src/session/wake/resolve-target.js +10 -5
@@ -64,6 +64,45 @@ function optionalUsdCell(value, hasValue) {
64
64
  return hasValue ? usdCell(value) : "-";
65
65
  }
66
66
 
67
+ function booleanFlag(value) {
68
+ if (typeof value === "boolean") return value;
69
+ if (typeof value === "number" && Number.isFinite(value)) return value !== 0;
70
+ const text = normalize(value).toLowerCase();
71
+ if (!text) return null;
72
+ if (["1", "true", "t", "yes", "y"].includes(text)) return true;
73
+ if (["0", "false", "f", "no", "n"].includes(text)) return false;
74
+ return null;
75
+ }
76
+
77
+ function hasHostedCustomerCost(bag) {
78
+ const camelFlag = booleanFlag(bag.hasCustomerCost);
79
+ if (camelFlag != null) return camelFlag;
80
+ const snakeFlag = booleanFlag(bag.has_customer_cost);
81
+ if (snakeFlag != null) return snakeFlag;
82
+ return [
83
+ bag.customerCostUsd,
84
+ bag.customer_cost_usd,
85
+ bag.billableCostUsd,
86
+ bag.billable_cost_usd,
87
+ ].some((value) => value != null && value !== "");
88
+ }
89
+
90
+ function rollupCostSummary(rollup = {}) {
91
+ const parts = [`provider ${usdCell(rollup.providerCostUsd)}`];
92
+ if (rollup.customerCostUsd != null) {
93
+ parts.push(`customer ${usdCell(rollup.customerCostUsd)}`);
94
+ }
95
+ const unpriced = nonNegativeInt(rollup.unpriced);
96
+ if (unpriced > 0) {
97
+ parts.push(`unpriced ${intCell(unpriced)}`);
98
+ }
99
+ const estimatedEntries = nonNegativeInt(rollup.estimatedEntries);
100
+ if (estimatedEntries > 0) {
101
+ parts.push(`estimated ${intCell(estimatedEntries)}`);
102
+ }
103
+ return parts.join(", ");
104
+ }
105
+
67
106
  function timestampOnly(iso) {
68
107
  if (!iso) return "";
69
108
  const date = new Date(iso);
@@ -122,7 +161,7 @@ function rollupToJson(rollup) {
122
161
 
123
162
  function hostedRollupToJson(rollup, fallbackLabel = "") {
124
163
  const bag = object(rollup);
125
- const hasCustomerCost = Boolean(bag.hasCustomerCost ?? bag.has_customer_cost ?? bag.customerCostUsd != null ?? bag.customer_cost_usd != null);
164
+ const hasCustomerCost = hasHostedCustomerCost(bag);
126
165
  return {
127
166
  label: normalize(bag.label ?? bag.agentId ?? bag.agent_id ?? bag.action ?? fallbackLabel) || "unknown",
128
167
  entries: nonNegativeInt(bag.entries ?? bag.entryCount ?? bag.entry_count ?? bag.count ?? bag.interactions),
@@ -412,7 +451,7 @@ export function renderSessionUsageSummary(report = {}) {
412
451
  lines.push("Per agent:");
413
452
  for (const agent of agents) {
414
453
  lines.push(
415
- `- ${agent.label}: ${intCell(agent.totalTokens)} tokens, ${intCell(agent.entries)} entries, provider ${usdCell(agent.providerCostUsd)}`,
454
+ `- ${agent.label}: ${intCell(agent.totalTokens)} tokens, ${intCell(agent.entries)} entries, ${rollupCostSummary(agent)}`,
416
455
  );
417
456
  }
418
457
  }
@@ -16,8 +16,10 @@
16
16
  // transitive graph (auth/sync/`open`/...) into the wake daemon. The matcher
17
17
  // here is small enough to own.
18
18
 
19
+ import { messageRevision, sessionMessageEditSnapshot, sessionMessageRoutingEvent } from "../message-edits.js";
20
+
19
21
  const BROADCAST_RECIPIENTS = new Set(["*", "all", "broadcast", "everyone", "anyone", "agents", "all-agents"]);
20
- const DEFAULT_WAKE_EVENT_TYPES = new Set(["session_message", "help_request"]);
22
+ const DEFAULT_WAKE_EVENT_TYPES = new Set(["session_message", "help_request", "session_message_edited"]);
21
23
  const MAX_WAKE_MESSAGE_CHARS = 16_000;
22
24
 
23
25
  function requireNonEmptyString(value, label) {
@@ -98,7 +100,7 @@ function defaultFormatMessage(event, agentId) {
98
100
  const author = agentIdOf(event) || "unknown";
99
101
  const text = event?.payload?.message ?? event?.message ?? "";
100
102
  const body = typeof text === "string" ? text : "";
101
- const head = `Senti wake for ${agentId}: new message from ${author}.`;
103
+ const head = `Senti wake for ${agentId}: ${event.event === "session_message_edited" ? "edited message" : "new message"} from ${author}.`;
102
104
  const combined = body ? `${head}\n\n${body}` : head;
103
105
  return combined.length > MAX_WAKE_MESSAGE_CHARS ? combined.slice(0, MAX_WAKE_MESSAGE_CHARS) : combined;
104
106
  }
@@ -128,18 +130,21 @@ export function createResolveTarget({
128
130
  // Only wake on real message events; acks/reactions/views/system are skips.
129
131
  const type = eventTypeOf(event);
130
132
  if (!type || !wakeEventTypes.has(type)) return null;
133
+ if (type === "session_message" && messageRevision(event) > 1) return null;
134
+ if (type === "session_message_edited" && !sessionMessageEditSnapshot(event)) return null;
135
+ const routed = sessionMessageRoutingEvent(event);
131
136
 
132
137
  // No self-wake: never wake the agent on its own message. Normalize both
133
138
  // sides (handles @-prefix / casing / punctuation) so the loop-guard can't
134
139
  // be slipped by a non-canonical author id.
135
- const author = agentIdOf(event);
140
+ const author = agentIdOf(routed);
136
141
  if (author && normalizeComparableId(author) === selfLower) return null;
137
142
 
138
143
  // Routing: directed-to-me / broadcast / untargeted-room wakes us; a message
139
144
  // aimed at a different specific agent is intentionally unroutable.
140
- if (!matchesAgent(event, selfLower)) return null;
145
+ if (!matchesAgent(routed, selfLower)) return null;
141
146
 
142
- return { host: hostName, sessionId: resumeId, message: formatMessage(event, selfId) };
147
+ return { host: hostName, sessionId: resumeId, message: formatMessage({ ...routed, event: type }, selfId) };
143
148
  };
144
149
  }
145
150