hippo-memory 1.72.0 → 2.0.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.
- package/README.md +6 -4
- package/dist/agent-memories/apply.js +5 -5
- package/dist/api/assemble.js +2 -1
- package/dist/api/audit.d.ts +7 -7
- package/dist/api/audit.js +19 -17
- package/dist/api/auth.d.ts +5 -6
- package/dist/api/auth.js +28 -33
- package/dist/api/conflicts.js +1 -1
- package/dist/api/context-auto.js +4 -2
- package/dist/api/context-select.js +8 -20
- package/dist/api/context.js +12 -10
- package/dist/api/drill-down.js +2 -1
- package/dist/api/forget.d.ts +2 -2
- package/dist/api/forget.js +6 -7
- package/dist/api/graph.js +2 -1
- package/dist/api/learn.d.ts +1 -1
- package/dist/api/learn.js +45 -44
- package/dist/api/memories.d.ts +4 -2
- package/dist/api/memories.js +16 -5
- package/dist/api/objects.js +2 -1
- package/dist/api/outcome.d.ts +5 -5
- package/dist/api/outcome.js +16 -15
- package/dist/api/predictions.js +2 -1
- package/dist/api/promote.d.ts +3 -3
- package/dist/api/promote.js +25 -27
- package/dist/api/prompt-hook.d.ts +3 -1
- package/dist/api/prompt-hook.js +1 -0
- package/dist/api/quarantine.js +2 -1
- package/dist/api/recall-core.js +8 -3
- package/dist/api/recall-pipeline.js +17 -17
- package/dist/api/recall-types.d.ts +3 -1
- package/dist/api/recall.js +32 -29
- package/dist/api/remember-local.d.ts +5 -2
- package/dist/api/remember-local.js +14 -6
- package/dist/api/remember.d.ts +5 -2
- package/dist/api/remember.js +16 -14
- package/dist/api/session-end.d.ts +24 -0
- package/dist/api/session-end.js +31 -1
- package/dist/api/sleep-run.js +5 -4
- package/dist/api/sleep.js +4 -3
- package/dist/api/tokens.js +1 -1
- package/dist/api/types.d.ts +1 -7
- package/dist/capture/command.js +1 -1
- package/dist/capture/compaction-spool.d.ts +3 -5
- package/dist/capture/compaction-spool.js +41 -46
- package/dist/capture/session-end-caller.js +1 -1
- package/dist/capture/session-worker.js +19 -6
- package/dist/cli/audit.js +10 -10
- package/dist/cli/auth.js +4 -4
- package/dist/cli/client.d.ts +1 -2
- package/dist/cli/client.js +13 -13
- package/dist/cli/context.js +1 -1
- package/dist/cli/curate.d.ts +1 -1
- package/dist/cli/curate.js +9 -9
- package/dist/cli/decisions.js +4 -5
- package/dist/cli/hook-runtime.d.ts +3 -1
- package/dist/cli/hook-runtime.js +2 -2
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.js +9 -9
- package/dist/cli/object-verbs.d.ts +2 -2
- package/dist/cli/object-verbs.js +4 -4
- package/dist/cli/predictions.js +2 -3
- package/dist/cli/quality-repair-once.d.ts +1 -1
- package/dist/cli/quality-repair-once.js +2 -2
- package/dist/cli/refine-llm.js +2 -1
- package/dist/cli/remember.js +5 -5
- package/dist/cli/serve.js +9 -4
- package/dist/cli/session-hooks.d.ts +0 -4
- package/dist/cli/session-hooks.js +57 -66
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +2 -2
- package/dist/cli/shared.d.ts +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli/sleep.js +2 -2
- package/dist/cli/transfer.d.ts +3 -3
- package/dist/cli/transfer.js +34 -15
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/usage.js +1 -1
- package/dist/cli/verb-row.d.ts +2 -0
- package/dist/cli/verbs/health.js +2 -0
- package/dist/cli/verbs/memory.js +43 -51
- package/dist/cli/verbs/objects.js +6 -3
- package/dist/cli/verbs/sessions.js +4 -2
- package/dist/cli.js +1 -2
- package/dist/connectors/dlq.js +2 -1
- package/dist/connectors/github/deletion.js +2 -1
- package/dist/connectors/github/ingest.js +2 -1
- package/dist/connectors/github/tenant-routing.js +2 -1
- package/dist/connectors/github/webhook.js +4 -2
- package/dist/connectors/ingest.js +2 -1
- package/dist/connectors/slack/deletion.js +2 -1
- package/dist/connectors/slack/ingest.js +2 -1
- package/dist/connectors/slack/tenant-routing.js +2 -1
- package/dist/consolidate/conflicts.js +1 -1
- package/dist/consolidate/dag.js +14 -9
- package/dist/consolidate/dedupe.js +1 -1
- package/dist/consolidate/memory-value.js +1 -1
- package/dist/consolidate/merge.js +6 -6
- package/dist/consolidate/sleep.js +1 -1
- package/dist/consolidate/traces.js +2 -2
- package/dist/core/config.js +15 -16
- package/dist/core/markdown.js +2 -2
- package/dist/core/memory.d.ts +4 -4
- package/dist/core/memory.js +10 -5
- package/dist/core/physics.d.ts +3 -1
- package/dist/core/physics.js +17 -2
- package/dist/core/project-identity.js +2 -1
- package/dist/core/search-types.d.ts +1 -1
- package/dist/dashboard/dashboard-actions.d.ts +2 -2
- package/dist/dashboard/dashboard-actions.js +5 -5
- package/dist/dashboard/dashboard-queries.js +2 -7
- package/dist/dashboard/dashboard-snapshot.js +1 -3
- package/dist/dashboard/dashboard.js +16 -5
- package/dist/db/connect.d.ts +2 -1
- package/dist/db/connect.js +2 -1
- package/dist/db/migrate.js +14 -7
- package/dist/db/migrations/v11.js +1 -0
- package/dist/db/migrations/v14.js +1 -0
- package/dist/db/physics-state.d.ts +0 -4
- package/dist/db/physics-state.js +5 -31
- package/dist/db/request-stores.d.ts +1 -0
- package/dist/db/request-stores.js +2 -1
- package/dist/db/vector-store.js +2 -1
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.js +7 -7
- package/dist/embeddings/local.js +2 -2
- package/dist/embeddings/provider.js +1 -10
- package/dist/entry/store-kit.d.ts +37 -0
- package/dist/entry/store-kit.js +25 -0
- package/dist/entry/testing.d.ts +6 -0
- package/dist/entry/testing.js +7 -0
- package/dist/eval/judgment.js +1 -1
- package/dist/hooks/json-hooks.js +6 -2
- package/dist/importers/core.js +3 -3
- package/dist/importers/vault.d.ts +1 -1
- package/dist/importers/vault.js +18 -18
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/learn/autolearn.d.ts +6 -2
- package/dist/learn/autolearn.js +8 -6
- package/dist/learn/extract.js +1 -1
- package/dist/learn/invalidation.d.ts +2 -1
- package/dist/learn/invalidation.js +10 -5
- package/dist/mcp/memory-tools.d.ts +1 -1
- package/dist/mcp/memory-tools.js +6 -9
- package/dist/mcp/request.js +20 -19
- package/dist/mcp/stdio.js +13 -8
- package/dist/rerankers/cross-encoder.js +10 -3
- package/dist/search/embed-memories.d.ts +5 -2
- package/dist/search/embed-memories.js +40 -28
- package/dist/search/engine.d.ts +8 -0
- package/dist/search/engine.js +10 -0
- package/dist/search/vector.js +5 -5
- package/dist/server/auth.d.ts +1 -1
- package/dist/server/auth.js +7 -8
- package/dist/server/boot.d.ts +2 -1
- package/dist/server/boot.js +28 -19
- package/dist/server/client-ip.d.ts +9 -4
- package/dist/server/client-ip.js +35 -17
- package/dist/server/route-table.js +5 -6
- package/dist/server/routes/memories.js +4 -2
- package/dist/server/sleep-offload.js +3 -3
- package/dist/server/tls.d.ts +7 -2
- package/dist/server/tls.js +17 -5
- package/dist/server/types.d.ts +7 -2
- package/dist/server.d.ts +4 -41
- package/dist/server.js +3 -30
- package/dist/sharing/global-store.d.ts +4 -5
- package/dist/sharing/global-store.js +6 -11
- package/dist/sharing/global-sync.js +1 -1
- package/dist/sharing/search-both.js +9 -2
- package/dist/sharing/share.js +2 -4
- package/dist/store/ambient.js +2 -1
- package/dist/store/candidates.js +18 -8
- package/dist/store/cards.js +5 -4
- package/dist/store/compactions-record.js +8 -7
- package/dist/store/conflicts.d.ts +7 -1
- package/dist/store/conflicts.js +6 -5
- package/dist/store/connectors/dlq.js +2 -1
- package/dist/store/connectors/event-log.d.ts +13 -0
- package/dist/store/connectors/event-log.js +24 -0
- package/dist/store/connectors/github.js +6 -8
- package/dist/store/connectors/slack.js +6 -10
- package/dist/store/dashboard-reads.js +1 -0
- package/dist/store/delete-and-batch.js +5 -4
- package/dist/store/delivery-recorder.d.ts +2 -2
- package/dist/store/delivery-recorder.js +1 -6
- package/dist/store/descendant-page.js +2 -1
- package/dist/store/dormant.js +7 -3
- package/dist/store/entry-reads.d.ts +25 -3
- package/dist/store/entry-reads.js +43 -12
- package/dist/store/entry-row.js +9 -2
- package/dist/store/entry-writes.d.ts +10 -7
- package/dist/store/entry-writes.js +23 -11
- package/dist/store/goals.js +3 -2
- package/dist/store/graph-reads.js +5 -4
- package/dist/store/graph-view-rows.js +2 -1
- package/dist/store/half-life.js +2 -2
- package/dist/store/handoffs.d.ts +9 -3
- package/dist/store/handoffs.js +4 -3
- package/dist/store/index.d.ts +2 -2
- package/dist/store/index.js +1 -1
- package/dist/store/mirrors.js +9 -4
- package/dist/store/open.d.ts +2 -1
- package/dist/store/open.js +2 -1
- package/dist/store/port.d.ts +59 -3
- package/dist/store/quality-repair.js +1 -1
- package/dist/store/recall-trace.js +2 -1
- package/dist/store/rejected-values.js +14 -9
- package/dist/store/rows.js +2 -2
- package/dist/store/rule-sql.d.ts +2 -0
- package/dist/store/rule-sql.js +12 -6
- package/dist/store/search-rows.d.ts +5 -5
- package/dist/store/search-rows.js +9 -12
- package/dist/store/set-aside.js +1 -1
- package/dist/store/sqlite/connector-writes-group.js +3 -1
- package/dist/store/sqlite/entry-writes-group.d.ts +3 -3
- package/dist/store/sqlite/entry-writes-group.js +1 -1
- package/dist/store/sqlite/held-connection.d.ts +2 -0
- package/dist/store/sqlite/held-connection.js +1 -1
- package/dist/store/sqlite/last-recall-group.d.ts +13 -0
- package/dist/store/sqlite/last-recall-group.js +57 -0
- package/dist/store/sqlite/local.d.ts +0 -17
- package/dist/store/sqlite/local.js +0 -31
- package/dist/store/sqlite/maintenance-groups.d.ts +6 -0
- package/dist/store/sqlite/maintenance-groups.js +60 -0
- package/dist/store/sqlite/objects-group.js +14 -6
- package/dist/store/sqlite/store.d.ts +1 -8
- package/dist/store/sqlite/store.js +13 -24
- package/dist/store/summaries.d.ts +7 -0
- package/dist/store/summaries.js +49 -7
- package/dist/store/token-ledger-rows.js +3 -2
- package/dist/store/vector-index.js +5 -2
- package/dist/util/anthropic-messages.d.ts +3 -1
- package/dist/util/anthropic-messages.js +4 -2
- package/dist/util/atomic-write.d.ts +5 -2
- package/dist/util/atomic-write.js +12 -8
- package/dist/util/env.d.ts +10 -1
- package/dist/util/env.js +10 -1
- package/dist/util/format.d.ts +2 -0
- package/dist/util/format.js +4 -0
- package/dist/util/request-scope.d.ts +6 -2
- package/dist/util/request-scope.js +7 -3
- package/dist/util/spawn-argv.d.ts +9 -0
- package/dist/util/spawn-argv.js +57 -0
- package/dist/util/sql.d.ts +3 -0
- package/dist/util/sql.js +6 -0
- package/dist/util/vector-codec.d.ts +4 -0
- package/dist/util/vector-codec.js +15 -0
- package/dist/util/version.d.ts +1 -1
- package/dist/util/version.js +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -1
- package/dist/api/on-store.d.ts +0 -16
- package/dist/api/on-store.js +0 -38
- package/dist/cli/lenient-id.d.ts +0 -2
- package/dist/cli/lenient-id.js +0 -13
package/dist/server/client-ip.js
CHANGED
|
@@ -6,15 +6,15 @@ import { HttpError } from '../util/http-util.js';
|
|
|
6
6
|
const IPV6_PREFIX_GROUPS = 4;
|
|
7
7
|
/**
|
|
8
8
|
* Rate-limit key: the socket's remote address, which behind a TLS proxy collapses into one bucket unauthenticated traffic can drain.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
9
|
+
* HIPPO_CLIENT_IP_HEADER (fly-client-ip on Fly) is read only from a peer in HIPPO_TRUSTED_PROXIES (IPs or CIDRs, or `*` for any peer),
|
|
10
|
+
* so a caller that reaches the port past the proxy cannot pick its own bucket. In a comma-joined chain the key is the rightmost hop no trusted proxy added. */
|
|
11
11
|
export function clientIpForRateLimit(req) {
|
|
12
12
|
const socketIp = req.socket.remoteAddress ?? 'unknown';
|
|
13
13
|
const header = envClientIpHeader();
|
|
14
14
|
if (!header)
|
|
15
15
|
return socketIp;
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
16
|
+
const trust = proxyTrust(envTrustedProxies());
|
|
17
|
+
if (trust === undefined || (trust !== ANY_PEER && !isTrustedProxy(trust, socketIp)))
|
|
18
18
|
return socketIp;
|
|
19
19
|
const raw = req.headers[header];
|
|
20
20
|
const hops = (Array.isArray(raw) ? raw.join(',') : raw ?? '')
|
|
@@ -22,20 +22,25 @@ export function clientIpForRateLimit(req) {
|
|
|
22
22
|
.map((hop) => hop.trim())
|
|
23
23
|
.filter((hop) => hop.length > 0);
|
|
24
24
|
for (let i = hops.length - 1; i >= 0; i--) {
|
|
25
|
-
if (
|
|
25
|
+
if (trust === ANY_PEER || !isTrustedProxy(trust, hops[i]))
|
|
26
26
|
return hops[i];
|
|
27
27
|
}
|
|
28
28
|
return hops[0] ?? socketIp;
|
|
29
29
|
}
|
|
30
|
+
/** HIPPO_TRUSTED_PROXIES=*: every peer is the proxy, and the hop it appended is the client. */
|
|
31
|
+
const ANY_PEER = '*';
|
|
30
32
|
let trustedProxyCache;
|
|
31
|
-
/** Parses HIPPO_TRUSTED_PROXIES once per distinct value; undefined when unset, so
|
|
32
|
-
function
|
|
33
|
-
|
|
33
|
+
/** Parses HIPPO_TRUSTED_PROXIES once per distinct value; undefined when unset, so no peer's header is read. */
|
|
34
|
+
function proxyTrust(raw) {
|
|
35
|
+
const value = raw?.trim();
|
|
36
|
+
if (!value)
|
|
34
37
|
return undefined;
|
|
35
|
-
if (
|
|
38
|
+
if (value === ANY_PEER)
|
|
39
|
+
return ANY_PEER;
|
|
40
|
+
if (trustedProxyCache?.raw === value)
|
|
36
41
|
return trustedProxyCache.list;
|
|
37
42
|
const list = new BlockList();
|
|
38
|
-
for (const entry of
|
|
43
|
+
for (const entry of value.split(',').map((e) => e.trim()).filter((e) => e.length > 0)) {
|
|
39
44
|
const [addr = '', prefix] = entry.split('/');
|
|
40
45
|
const family = isIP(addr) === 6 ? 'ipv6' : 'ipv4';
|
|
41
46
|
const bits = prefix === undefined ? undefined : Number(prefix);
|
|
@@ -49,17 +54,30 @@ function trustedProxyList(raw) {
|
|
|
49
54
|
else
|
|
50
55
|
list.addSubnet(addr, bits, family);
|
|
51
56
|
}
|
|
52
|
-
trustedProxyCache = { raw, list };
|
|
57
|
+
trustedProxyCache = { raw: value, list };
|
|
53
58
|
return list;
|
|
54
59
|
}
|
|
55
|
-
/**
|
|
56
|
-
export
|
|
60
|
+
/** serve() refused HIPPO_CLIENT_IP_HEADER with no HIPPO_TRUSTED_PROXIES: the header would be read from no one, so the setting is a mistake. */
|
|
61
|
+
export class UntrustedClientIpHeaderError extends Error {
|
|
62
|
+
header;
|
|
63
|
+
constructor(header) {
|
|
64
|
+
super(`hippo serve: HIPPO_CLIENT_IP_HEADER is ${header} but HIPPO_TRUSTED_PROXIES is unset, so no peer may set that header. ` +
|
|
65
|
+
"Set HIPPO_TRUSTED_PROXIES to the proxy's addresses or CIDRs, or to * when the proxy is the only peer that can reach this port.");
|
|
66
|
+
this.header = header;
|
|
67
|
+
this.name = 'UntrustedClientIpHeaderError';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/** Checked at boot: a client-IP header needs a trusted-proxy list, and `*` is said once, since it believes any peer. */
|
|
71
|
+
export function assertClientIpHeaderPinned() {
|
|
57
72
|
const header = envClientIpHeader();
|
|
58
|
-
if (!header
|
|
73
|
+
if (!header)
|
|
59
74
|
return;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
const trust = proxyTrust(envTrustedProxies());
|
|
76
|
+
if (trust === undefined)
|
|
77
|
+
throw new UntrustedClientIpHeaderError(header);
|
|
78
|
+
if (trust === ANY_PEER) {
|
|
79
|
+
log.warn(`serve: HIPPO_TRUSTED_PROXIES is *, so ${header} is read from any peer; keep the proxy the only route to this port.`);
|
|
80
|
+
}
|
|
63
81
|
}
|
|
64
82
|
/** The key for one subscriber: an IPv6 address's /64, since one host may hold the whole /64; any other key as is. */
|
|
65
83
|
function subscriberKey(ip) {
|
|
@@ -50,7 +50,7 @@ export const V1_ROUTES = [
|
|
|
50
50
|
{ method: 'GET', path: '/v1/quarantine', auth: 'admin', storeReady: 'quarantine', loop: 'off', handler: handleListQuarantine },
|
|
51
51
|
{ method: 'POST', pattern: '/v1/quarantine/:id/approve', auth: 'admin', storeReady: 'quarantine', loop: 'off', handler: handleApproveQuarantine },
|
|
52
52
|
{ method: 'POST', pattern: '/v1/quarantine/:id/reject', auth: 'admin', storeReady: 'quarantine', loop: 'off', handler: handleRejectQuarantine },
|
|
53
|
-
{ method: 'GET', path: '/v1/audit', auth: '
|
|
53
|
+
{ method: 'GET', path: '/v1/audit', auth: 'admin', storeReady: 'auditLog', loop: 'off', handler: handleListAudit },
|
|
54
54
|
{ method: 'POST', path: '/v1/predictions', auth: 'caller', storeReady: 'predictions', loop: 'off', handler: handleCreatePrediction },
|
|
55
55
|
{ method: 'GET', path: '/v1/predictions', auth: 'caller', storeReady: 'predictions', loop: 'off', handler: handleListPredictions },
|
|
56
56
|
{ method: 'GET', path: '/v1/predictions/stats', auth: 'caller', storeReady: 'predictions', loop: 'off', handler: handlePredictionStats },
|
|
@@ -188,10 +188,9 @@ export function dispatchPublicJson({ req, res, opts }, method, path) {
|
|
|
188
188
|
res.end(text);
|
|
189
189
|
return true;
|
|
190
190
|
}
|
|
191
|
-
/**
|
|
192
|
-
function assertRoutePorted(opts, group) {
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
throw new HttpError(501, STORE_NOT_PORTED_MESSAGE);
|
|
191
|
+
/** A route answers 501 without running when the store lacks its group; a route that names none, a sqliteOnly one among them, needs hippo.db's local files. */
|
|
192
|
+
function assertRoutePorted(opts, group = 'localFiles') {
|
|
193
|
+
if (!hasGroup(opts.store, group))
|
|
194
|
+
throw new HttpError(501, STORE_NOT_PORTED_MESSAGE);
|
|
196
195
|
}
|
|
197
196
|
//# sourceMappingURL=route-table.js.map
|
|
@@ -4,6 +4,7 @@ import { archiveRaw, forget, outcome, outcomeForLastRecall, promote, remember, s
|
|
|
4
4
|
import { MEMORY_KINDS } from '../../core/memory.js';
|
|
5
5
|
import { graphModelOf } from '../../graph/view.js';
|
|
6
6
|
import { MAX_ENTITY_NAME_LEN } from '../../store/graph-rows.js';
|
|
7
|
+
import { hasGroup } from '../../store/index.js';
|
|
7
8
|
import { HttpError, sendJson } from '../../util/http-util.js';
|
|
8
9
|
import { sleepInChild } from '../sleep-offload.js';
|
|
9
10
|
import { getCallerProject, getString, getStringArray, isSetMember, parseJsonBody, parseListLimit, validateIdSegment } from '../validation.js';
|
|
@@ -115,9 +116,10 @@ export async function handleApplyOutcome({ req, res, ctx }) {
|
|
|
115
116
|
sendJson(res, 200, { applied });
|
|
116
117
|
}
|
|
117
118
|
else {
|
|
118
|
-
//
|
|
119
|
-
if (ctx.store !== undefined && ctx.store
|
|
119
|
+
// A store without the group keeps no last recall, so its client must send the ids its recall returned.
|
|
120
|
+
if (ctx.store !== undefined && !hasGroup(ctx.store, 'lastRecall')) {
|
|
120
121
|
throw new HttpError(501, 'this store keeps no last recall: send the ids your recall returned');
|
|
122
|
+
}
|
|
121
123
|
const result = await outcomeForLastRecall(ctx, good);
|
|
122
124
|
sendJson(res, 200, result);
|
|
123
125
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { sleep } from '../api/index.js';
|
|
5
|
-
import { StoreBusyError } from '../store/port.js';
|
|
5
|
+
import { hasGroup, StoreBusyError } from '../store/port.js';
|
|
6
6
|
import { envSleepTimeoutMs } from '../util/env.js';
|
|
7
7
|
import { HttpError } from '../util/http-util.js';
|
|
8
8
|
const DEFAULT_SLEEP_DEADLINE_MS = 600_000;
|
|
@@ -67,8 +67,8 @@ function inTurn(job) {
|
|
|
67
67
|
}
|
|
68
68
|
/** `sleep` for the HTTP route: same result and errors, run outside the serving process and stopped at the deadline (10 minutes, or HIPPO_SLEEP_TIMEOUT_MS). */
|
|
69
69
|
export function sleepInChild(ctx, opts) {
|
|
70
|
-
// The child reopens the store by path, which only
|
|
71
|
-
if (ctx.store && ctx.store
|
|
70
|
+
// The child reopens the store by path, which only a store of local files has; any other store keeps sleep's own refusal.
|
|
71
|
+
if (ctx.store && !hasGroup(ctx.store, 'localFiles'))
|
|
72
72
|
return sleep(ctx, opts);
|
|
73
73
|
return inTurn({ kind: 'sleep', hippoRoot: ctx.hippoRoot, tenantId: ctx.tenantId, actor: ctx.actor, opts });
|
|
74
74
|
}
|
package/dist/server/tls.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { type RequestListener, type Server } from 'node:http';
|
|
2
2
|
import type { ServeOpts } from './types.js';
|
|
3
3
|
export declare function createListener(tls: ServeOpts['tls'], onRequest: RequestListener): Server;
|
|
4
|
-
/**
|
|
5
|
-
export declare
|
|
4
|
+
/** serve() refused a non-loopback bind without TLS: every key and memory would cross the network as plain text. */
|
|
5
|
+
export declare class CleartextBindError extends Error {
|
|
6
|
+
readonly host: string;
|
|
7
|
+
constructor(host: string);
|
|
8
|
+
}
|
|
9
|
+
/** Throws CleartextBindError for a non-loopback bind without TLS unless the operator allowed cleartext, which is then said once at boot. */
|
|
10
|
+
export declare function assertTlsOrCleartextAllowed(host: string, tls: ServeOpts['tls'], loopbackHosts: ReadonlySet<string>, allowCleartext: boolean): void;
|
|
6
11
|
//# sourceMappingURL=tls.d.ts.map
|
package/dist/server/tls.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The serve() listener: node:https when a certificate is given, else node:http
|
|
1
|
+
// The serve() listener: node:https when a certificate is given, else node:http, which a non-loopback bind must opt into.
|
|
2
2
|
import { createServer as createHttpServer } from 'node:http';
|
|
3
3
|
import { createServer as createHttpsServer } from 'node:https';
|
|
4
4
|
import { errorMessage, log } from '../util/log.js';
|
|
@@ -14,11 +14,23 @@ export function createListener(tls, onRequest) {
|
|
|
14
14
|
throw new Error(`hippo serve: the TLS certificate or key was refused: ${errorMessage(err)}`, { cause: err });
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
18
|
-
export
|
|
17
|
+
/** serve() refused a non-loopback bind without TLS: every key and memory would cross the network as plain text. */
|
|
18
|
+
export class CleartextBindError extends Error {
|
|
19
|
+
host;
|
|
20
|
+
constructor(host) {
|
|
21
|
+
super(`hippo serve: refusing to listen on ${host} without TLS, since API keys and memory text would travel in cleartext. ` +
|
|
22
|
+
'Pass --tls-cert and --tls-key (or set HIPPO_TLS_CERT and HIPPO_TLS_KEY) to serve HTTPS. If a TLS-terminating proxy sits in front, ' +
|
|
23
|
+
'start with --allow-cleartext, set HIPPO_ALLOW_CLEARTEXT=1, or pass allowCleartext: true to serve().');
|
|
24
|
+
this.host = host;
|
|
25
|
+
this.name = 'CleartextBindError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** Throws CleartextBindError for a non-loopback bind without TLS unless the operator allowed cleartext, which is then said once at boot. */
|
|
29
|
+
export function assertTlsOrCleartextAllowed(host, tls, loopbackHosts, allowCleartext) {
|
|
19
30
|
if (tls || loopbackHosts.has(host))
|
|
20
31
|
return;
|
|
21
|
-
|
|
22
|
-
|
|
32
|
+
if (!allowCleartext)
|
|
33
|
+
throw new CleartextBindError(host);
|
|
34
|
+
log.warn(`serve: listening on ${host} without TLS, as allowed; API keys and memory text are cleartext until the proxy in front adds TLS.`);
|
|
23
35
|
}
|
|
24
36
|
//# sourceMappingURL=tls.js.map
|
package/dist/server/types.d.ts
CHANGED
|
@@ -66,11 +66,16 @@ export interface ServeOpts {
|
|
|
66
66
|
cert: string | Buffer;
|
|
67
67
|
key: string | Buffer;
|
|
68
68
|
};
|
|
69
|
+
/** Lets a non-loopback bind start without `tls`, for TLS that ends at a proxy in front; else serve() refuses it. Also HIPPO_ALLOW_CLEARTEXT=1. */
|
|
70
|
+
allowCleartext?: boolean;
|
|
71
|
+
/** The role a keyless local request (HIPPO_ALLOW_KEYLESS_LOCAL=1) gets; defaults to HIPPO_KEYLESS_LOCAL_ROLE, else 'member'. Only 'admin' is host admin. */
|
|
72
|
+
keylessLocalRole?: 'member' | 'admin';
|
|
69
73
|
/** Static JSON served to anyone at GET <path>; built once at boot and never authenticated, so it must hold nothing secret. */
|
|
70
74
|
publicJson?: Readonly<Record<string, JsonValue>>;
|
|
71
|
-
/** Request limits: perCaller after auth
|
|
75
|
+
/** Request limits: perCaller after auth, on by default (HIPPO_CALLER_RPS, else 20 per second, burst 40); perAddress replaces HIPPO_V1_RPS when set;
|
|
76
|
+
* 'off' disables either; failedAuthPerAddress guards scrypt. */
|
|
72
77
|
rateLimits?: {
|
|
73
|
-
perCaller?: RateLimitSpec;
|
|
78
|
+
perCaller?: RateLimitSpec | 'off';
|
|
74
79
|
perAddress?: RateLimitSpec | 'off';
|
|
75
80
|
failedAuthPerAddress?: RateLimitSpec;
|
|
76
81
|
};
|
package/dist/server.d.ts
CHANGED
|
@@ -1,54 +1,17 @@
|
|
|
1
|
-
import { type Actor, authCreateSelf, type AuthCreateSelfOpts, type AuthCreateSelfResult, authRevoke, type
|
|
2
|
-
import { ForbiddenError, NotFoundError } from './core/api-errors.js';
|
|
1
|
+
import { type Actor, authCreateSelf, type AuthCreateSelfOpts, type AuthCreateSelfResult, authRevoke, type AuthRevokeResult, type Context } from './api/index.js';
|
|
2
|
+
import { BadRequestError, ConflictError, ForbiddenError, NotFoundError } from './core/api-errors.js';
|
|
3
3
|
export { serve } from './server/boot.js';
|
|
4
|
-
export { authCreateSelf, authRevoke, ForbiddenError, NotFoundError, type AuthCreateSelfOpts, type AuthCreateSelfResult, type
|
|
4
|
+
export { authCreateSelf, authRevoke, BadRequestError, ConflictError, ForbiddenError, NotFoundError, type AuthCreateSelfOpts, type AuthCreateSelfResult, type AuthRevokeResult, type Context, type Actor, };
|
|
5
5
|
export { isCrossSite, LOOPBACK_HOST_HEADER } from './util/http-util.js';
|
|
6
|
-
export { __resetSessionRecallHistoryHttp } from './server/routes/recall.js';
|
|
7
6
|
export { clientIpForRateLimit } from './server/client-ip.js';
|
|
8
7
|
export { isLoopback, isReservedActor } from './server/auth.js';
|
|
9
8
|
export type { AddonCall, AddonRoute, AuthResolver, RateLimitSpec, ResolvedBearer, ServeOpts, ServerHandle } from './server/types.js';
|
|
9
|
+
export type { HippoStore } from './store/index.js';
|
|
10
10
|
export { HttpError } from './util/http-util.js';
|
|
11
11
|
export { promptHookContext, type CallerProject } from './api/prompt-hook.js';
|
|
12
12
|
export type { JsonValue } from './util/json.js';
|
|
13
13
|
export { captureSessionTexts, type SessionCaptureRequest, type SessionCaptureResult } from './capture/session-texts.js';
|
|
14
|
-
export { hasGroup, sqliteStore, type AmbientCandidateRequest, type ContextReads, type EntryRemoval, type EntryTarget, type EntryWrite, type EntryWrites, type HippoStore, type KeyAudit, type KeyListQuery, type KeyMint, type KeyRevoke, type KeyWrites, type OutcomeWrite, type RawArchive, type RecallSearchArgs, type RecallWrites, type SelfKeyMint, type StoreGroup, type StoreGroups, type SupersedeWrite, type VectorReads, type VectorBackfillQuery, type VectorRowWrite, type VectorWrite, type VectorWriteResult, type VectorWrites, } from './store/index.js';
|
|
15
|
-
export { entryAfterOutcome } from './core/memory.js';
|
|
16
|
-
export { rejectionDigest } from './store/rejection.js';
|
|
17
|
-
export { RejectedValueError } from './core/api-errors.js';
|
|
18
|
-
export { ownScopeTouches } from './core/recall-scope.js';
|
|
19
|
-
export { BadRequestError, ConflictError } from './core/api-errors.js';
|
|
20
|
-
export type { HippoDbContext, StoreReply } from './api/types.js';
|
|
21
|
-
export type { ApiKeyListItem, ApiKeyListRow, ApiKeyRecord, ListApiKeysOpts, NewApiKey } from './store/auth.js';
|
|
22
|
-
export type { KeysetPosition } from './util/keyset.js';
|
|
23
|
-
export type { AppendAuditOpts, AuditEvent, ListAuditAfterOpts } from './store/audit.js';
|
|
24
|
-
export type { ContinuityBlock } from './api/recall-types.js';
|
|
25
|
-
export type { ActiveGoals, GetActiveGoalsOpts, Goal, GoalRecallLogRow, RetrievalPolicy } from './store/goals.js';
|
|
26
|
-
export type { MemoryEntry } from './core/memory.js';
|
|
27
|
-
export type { ClassResolution, PlanningFallacyEvidence } from './store/planning-fallacy-evidence.js';
|
|
28
|
-
export type { PredictionBaserate } from './store/predictions.js';
|
|
29
|
-
export type { RecallTraceInput } from './store/recall-trace.js';
|
|
30
|
-
export type { StrengthenOptions } from './store/entry-writes.js';
|
|
31
|
-
export type { OriginFilter, RecallScopeFilter, VectorCandidateSpec } from './store/search-rows.js';
|
|
32
|
-
export type { ContinuityKey } from './store/sessions.js';
|
|
33
|
-
export type { SessionEvent, TaskSnapshot } from './store/rows.js';
|
|
34
|
-
export type { SessionHandoff } from './core/handoff.js';
|
|
35
|
-
export type { AmbientLoadResult, AmbientRecallRequest, ContextCandidateFilter, RecentOrigins } from './store/candidates.js';
|
|
36
|
-
export type { AmbientStoreFilter } from './store/ambient.js';
|
|
37
|
-
export type { AmbientTallies } from './core/ambient.js';
|
|
38
|
-
export type { TokenUse } from './store/token-ledger.js';
|
|
39
|
-
export type { EmbeddingIndexState } from './store/vector-index.js';
|
|
40
|
-
export type { PhysicsParticle } from './core/physics.js';
|
|
41
|
-
export { StoreBusyError } from './store/port.js';
|
|
42
|
-
export { decodeVector, EMBEDDING_MODEL_META_KEY, encodeVector, rankVectorRows, type VectorMatch, type VectorRow } from './db/vector-store.js';
|
|
43
|
-
export { bufferToFloat32, float32ToBuffer } from './db/physics-state.js';
|
|
44
|
-
export { replacesIndex } from './store/vector-index.js';
|
|
45
|
-
export { passesScopeFilterForRecall, RECALL_DEFAULT_DENY_SCOPES } from './core/recall-scope.js';
|
|
46
|
-
export { SECRET_TAGS } from './util/secret-detect.js';
|
|
47
|
-
export { tallyAmbientEntries } from './core/ambient.js';
|
|
48
|
-
export { ftsTermParts, rarestFtsQuery } from './core/prompt-recall.js';
|
|
49
|
-
export { OTHER_STORE_MARKER, OtherStoreFolderError, withSqliteAllowed } from './db/index.js';
|
|
50
14
|
export { authCreate, type AuthCreateOpts, type AuthCreateResult } from './api/index.js';
|
|
51
|
-
export { isSharedStore } from './core/config.js';
|
|
52
15
|
export { ownerOrSubject } from './api/index.js';
|
|
53
16
|
export { bindSessionOwner } from './api/session-owners.js';
|
|
54
17
|
export { preCompactForCaller, type CallerHookOutput, type CallerPreCompactRequest } from './capture/pre-compact-caller.js';
|
package/dist/server.js
CHANGED
|
@@ -1,45 +1,18 @@
|
|
|
1
1
|
import { authCreateSelf, authRevoke, } from './api/index.js';
|
|
2
|
-
import { ForbiddenError, NotFoundError } from './core/api-errors.js';
|
|
2
|
+
import { BadRequestError, ConflictError, ForbiddenError, NotFoundError } from './core/api-errors.js';
|
|
3
3
|
export { serve } from './server/boot.js';
|
|
4
4
|
// Add-on packages mint and revoke keys through these without importing the whole api surface.
|
|
5
|
-
export { authCreateSelf, authRevoke, ForbiddenError, NotFoundError, };
|
|
6
|
-
// Published on the hippo-memory/server subpath before they moved to http-util.ts, so they stay exported here.
|
|
5
|
+
export { authCreateSelf, authRevoke, BadRequestError, ConflictError, ForbiddenError, NotFoundError, };
|
|
7
6
|
export { isCrossSite, LOOPBACK_HOST_HEADER } from './util/http-util.js';
|
|
8
|
-
// The code behind these lives in src/server/; this subpath keeps exporting them.
|
|
9
|
-
export { __resetSessionRecallHistoryHttp } from './server/routes/recall.js';
|
|
10
7
|
export { clientIpForRateLimit } from './server/client-ip.js';
|
|
11
8
|
export { isLoopback, isReservedActor } from './server/auth.js';
|
|
12
|
-
//
|
|
13
|
-
// for a caller that renders the prompt hook elsewhere, JsonValue for its body.
|
|
9
|
+
// An add-on route replies 4xx with HttpError, renders the prompt hook elsewhere with promptHookContext, and types its body as JsonValue.
|
|
14
10
|
export { HttpError } from './util/http-util.js';
|
|
15
11
|
export { promptHookContext } from './api/prompt-hook.js';
|
|
16
12
|
// A session-end route stores the turns its caller read from a transcript on the caller's own machine.
|
|
17
13
|
export { captureSessionTexts } from './capture/session-texts.js';
|
|
18
|
-
// An add-on serves from another database by passing serve() its own HippoStore.
|
|
19
|
-
export { hasGroup, sqliteStore, } from './store/index.js';
|
|
20
|
-
// An add-on store's entry writes apply an outcome, guard tombstones and check reach exactly as hippo.db does.
|
|
21
|
-
export { entryAfterOutcome } from './core/memory.js';
|
|
22
|
-
export { rejectionDigest } from './store/rejection.js';
|
|
23
|
-
export { RejectedValueError } from './core/api-errors.js';
|
|
24
|
-
export { ownScopeTouches } from './core/recall-scope.js';
|
|
25
|
-
export { BadRequestError, ConflictError } from './core/api-errors.js';
|
|
26
|
-
export { StoreBusyError } from './store/port.js';
|
|
27
|
-
// An add-on store encodes, decodes and ranks vectors and particles with hippo.db's own code, and drops the index by its rule,
|
|
28
|
-
// so both stores keep the same bytes and return the same ids in the same order.
|
|
29
|
-
export { decodeVector, EMBEDDING_MODEL_META_KEY, encodeVector, rankVectorRows } from './db/vector-store.js';
|
|
30
|
-
export { bufferToFloat32, float32ToBuffer } from './db/physics-state.js';
|
|
31
|
-
export { replacesIndex } from './store/vector-index.js';
|
|
32
|
-
// An add-on's ContextReads applies hippo.db's scope, secret, tally and rarest-term rules with the same code.
|
|
33
|
-
export { passesScopeFilterForRecall, RECALL_DEFAULT_DENY_SCOPES } from './core/recall-scope.js';
|
|
34
|
-
export { SECRET_TAGS } from './util/secret-detect.js';
|
|
35
|
-
export { tallyAmbientEntries } from './core/ambient.js';
|
|
36
|
-
export { ftsTermParts, rarestFtsQuery } from './core/prompt-recall.js';
|
|
37
|
-
// store copy --db writes the marker and reads the old hippo.db under the waiver.
|
|
38
|
-
export { OTHER_STORE_MARKER, OtherStoreFolderError, withSqliteAllowed } from './db/index.js';
|
|
39
14
|
// An add-on's install step mints the first admin key into a store folder it names, which `hippo auth create` cannot reach.
|
|
40
15
|
export { authCreate } from './api/index.js';
|
|
41
|
-
// An add-on that serves a team store checks the flag before it starts.
|
|
42
|
-
export { isSharedStore } from './core/config.js';
|
|
43
16
|
export { ownerOrSubject } from './api/index.js';
|
|
44
17
|
// A route that writes for a session binds it to the caller's owner first.
|
|
45
18
|
export { bindSessionOwner } from './api/session-owners.js';
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
import { MemoryEntry } from '../core/memory.js';
|
|
2
|
-
import type { DatabaseSyncLike } from '../db/index.js';
|
|
3
2
|
/** Returns the path to the global Hippo store.
|
|
4
3
|
* Resolution order: $HIPPO_HOME > $XDG_DATA_HOME/hippo > ~/.hippo/ */
|
|
5
4
|
export declare function getGlobalRoot(): string;
|
|
6
5
|
/** Ensure the global store exists. */
|
|
7
6
|
export declare function initGlobal(): void;
|
|
8
|
-
|
|
9
|
-
export declare function promoteToGlobal(localRoot: string, id: string, opts?: {
|
|
7
|
+
export interface PromoteOptions {
|
|
10
8
|
actor?: string;
|
|
11
9
|
tenantId?: string;
|
|
12
|
-
afterWrite?: (db: DatabaseSyncLike, globalId: string) => void;
|
|
13
10
|
/** Also writes one `promote` audit row for the copy ({sourceId}), in the copy's own transaction. */
|
|
14
11
|
auditAs?: {
|
|
15
12
|
tenantId: string;
|
|
16
13
|
actor: string;
|
|
17
14
|
};
|
|
18
|
-
}
|
|
15
|
+
}
|
|
16
|
+
/** Copies a local memory to the global store and returns the copy, which gets a new `g_` id so it cannot collide. */
|
|
17
|
+
export declare function promoteToGlobal(localRoot: string, id: string, opts?: PromoteOptions): MemoryEntry;
|
|
19
18
|
//# sourceMappingURL=global-store.d.ts.map
|
|
@@ -3,7 +3,7 @@ import { BadRequestError, NotFoundError } from '../core/api-errors.js';
|
|
|
3
3
|
import * as fs from 'fs';
|
|
4
4
|
import { generateId } from '../core/memory.js';
|
|
5
5
|
import { initStore } from '../store/open.js';
|
|
6
|
-
import {
|
|
6
|
+
import { writeEntryWithHook } from '../store/entry-writes.js';
|
|
7
7
|
import { readEntry } from '../store/entry-reads.js';
|
|
8
8
|
import { isPersonalScope } from '../core/recall-scope.js';
|
|
9
9
|
import { fallbackOrigin, resolveGlobalRootDir } from '../core/project-identity.js';
|
|
@@ -30,15 +30,6 @@ export function initGlobal() {
|
|
|
30
30
|
initStore(globalRoot);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
/** The copy's write hook: the `promote` row when asked for, then the caller's own hook, both in the copy's transaction. */
|
|
34
|
-
function promoteHook(sourceId, auditAs, afterWrite) {
|
|
35
|
-
if (!auditAs)
|
|
36
|
-
return afterWrite;
|
|
37
|
-
return (db, globalId) => {
|
|
38
|
-
appendAuditEvent(db, { tenantId: auditAs.tenantId, actor: auditAs.actor, op: 'promote', targetId: globalId, metadata: { sourceId } });
|
|
39
|
-
afterWrite?.(db, globalId);
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
33
|
/** Copies a local memory to the global store and returns the copy, which gets a new `g_` id so it cannot collide. */
|
|
43
34
|
export function promoteToGlobal(localRoot, id, opts) {
|
|
44
35
|
const entry = readEntry(localRoot, id, opts?.tenantId);
|
|
@@ -67,7 +58,11 @@ export function promoteToGlobal(localRoot, id, opts) {
|
|
|
67
58
|
source: isSharedStore(localRoot) ? `shared::${new Date().toISOString()}` : `promoted:${localRoot}`,
|
|
68
59
|
origin_project: entry.origin_project ?? fallbackOrigin(localRoot),
|
|
69
60
|
};
|
|
70
|
-
|
|
61
|
+
const { auditAs } = opts ?? {};
|
|
62
|
+
const promoteRow = auditAs && ((db, globalId) => {
|
|
63
|
+
appendAuditEvent(db, { tenantId: auditAs.tenantId, actor: auditAs.actor, op: 'promote', targetId: globalId, metadata: { sourceId: id } });
|
|
64
|
+
});
|
|
65
|
+
writeEntryWithHook(globalRoot, globalEntry, { actor: opts?.actor, afterWrite: promoteRow });
|
|
71
66
|
// Fire-and-forget: embedMemory gates on availability and never rejects.
|
|
72
67
|
void embedMemory(globalRoot, globalEntry);
|
|
73
68
|
return globalEntry;
|
|
@@ -24,7 +24,7 @@ export function syncGlobalToLocal(localRoot, globalRoot, opts = {}) {
|
|
|
24
24
|
return 0;
|
|
25
25
|
return withRequestStoresSync(() => copyMissingRows(localRoot, globalRoot, opts.includeCrossProject === true));
|
|
26
26
|
}
|
|
27
|
-
const textKey = (e) => `${e.
|
|
27
|
+
const textKey = (e) => `${e.tenant_id}\n${e.content}`;
|
|
28
28
|
/** One open per store and one transaction: id, tenant and text decide which rows to read whole, and the copies commit together. */
|
|
29
29
|
function copyMissingRows(localRoot, globalRoot, includeCrossProject) {
|
|
30
30
|
// Host-wide read: the global union is copied into a tenant-scoped local store, and each copy keeps its own tenant.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Search across the local and global stores, merged into one ranked, budgeted list.
|
|
2
2
|
import { compareScoresDesc } from '../core/compare.js';
|
|
3
3
|
import * as fs from 'fs';
|
|
4
|
-
import { loadSearchEntries,
|
|
4
|
+
import { loadSearchEntries, loadRecallEntries, recallScopeFilter } from '../store/search-rows.js';
|
|
5
5
|
import { passesScopeFilterForRecall, passesCliRecallScopeFilter } from '../core/recall-scope.js';
|
|
6
6
|
import { search } from '../search/bm25-search.js';
|
|
7
7
|
import { hybridSearch } from '../search/hybrid.js';
|
|
@@ -79,7 +79,14 @@ export async function searchBothHybrid(query, localRoot, globalRoot, options = {
|
|
|
79
79
|
if (!fs.existsSync(root))
|
|
80
80
|
return [];
|
|
81
81
|
return recallScope
|
|
82
|
-
?
|
|
82
|
+
? loadRecallEntries(root, query, {
|
|
83
|
+
limit: searchWindow,
|
|
84
|
+
tenantId,
|
|
85
|
+
requestedScope: recallScope.requested,
|
|
86
|
+
explicitScopeMode: recallScope.additive ? 'additive' : 'exact',
|
|
87
|
+
includeSuperseded: Boolean(includeSuperseded) || Boolean(asOf),
|
|
88
|
+
ownScope: recallScope.ownScope,
|
|
89
|
+
})
|
|
83
90
|
: loadSearchEntries(root, query, searchWindow, tenantId);
|
|
84
91
|
};
|
|
85
92
|
const passesScope = (e) => !recallScope || (recallScope.additive
|
package/dist/sharing/share.js
CHANGED
|
@@ -5,7 +5,7 @@ import * as path from 'path';
|
|
|
5
5
|
import { generateId, COMPACTION_MEMORY_TAG } from '../core/memory.js';
|
|
6
6
|
import { AGENT_MEMORY_SOURCE_PREFIX, AGENT_MEMORY_TAGS } from '../core/agent-memory-tools.js';
|
|
7
7
|
import { writeEntriesSkippingRejected, writeEntry } from '../store/entry-writes.js';
|
|
8
|
-
import { loadAllEntries, readEntry } from '../store/entry-reads.js';
|
|
8
|
+
import { loadAllEntries, loadEntryTextKeys, readEntry } from '../store/entry-reads.js';
|
|
9
9
|
import { tallySources } from '../store/candidates.js';
|
|
10
10
|
import { isPersonalScope } from '../core/recall-scope.js';
|
|
11
11
|
import { fallbackOrigin } from '../core/project-identity.js';
|
|
@@ -214,9 +214,7 @@ export function autoShare(localRoot, options = {}) {
|
|
|
214
214
|
const globalRoot = getGlobalRoot();
|
|
215
215
|
// Host-wide read. The global store IS the union across all tenants;
|
|
216
216
|
// per-tenant filtering on the global root would defeat the purpose.
|
|
217
|
-
const
|
|
218
|
-
// Build set of global content hashes to avoid duplicates
|
|
219
|
-
const globalContentSet = storedTextKeys(globalEntries);
|
|
217
|
+
const globalContentSet = storedTextKeys(loadEntryTextKeys(globalRoot));
|
|
220
218
|
const candidates = localEntries.filter((entry) => isAutoShareCandidate(entry, globalContentSet, minScore, options.stats));
|
|
221
219
|
if (dryRun)
|
|
222
220
|
return candidates;
|
package/dist/store/ambient.js
CHANGED
|
@@ -5,6 +5,7 @@ import { onHandle, openStore } from './open.js';
|
|
|
5
5
|
import { jsonList } from './candidates.js';
|
|
6
6
|
import { originInSql } from '../core/project-identity.js';
|
|
7
7
|
import { AMBIENT_FRESH_WINDOW_MS, HIGH_SCHEMA_FIT_ABOVE, isErrorTagged } from '../core/ambient.js';
|
|
8
|
+
import { placeholders } from '../util/sql.js';
|
|
8
9
|
// SHORTCUT: mirrors loadContextCandidates' WHERE (request-path snapshots pin the match); share it once store.ts is split.
|
|
9
10
|
function contextRowsWhere(tenantId, filter) {
|
|
10
11
|
const where = ['tenant_id = ?', `COALESCE(superseded_by, '') = ''`, `COALESCE(kind, '') != 'archived'`];
|
|
@@ -29,7 +30,7 @@ function secretTaggedSql() {
|
|
|
29
30
|
const tags = [...SECRET_TAGS];
|
|
30
31
|
return {
|
|
31
32
|
sql: `((${tags.map(() => 'tags_json LIKE ?').join(' OR ')})
|
|
32
|
-
AND EXISTS (SELECT 1 FROM json_each(${jsonList('tags_json')}) WHERE lower(value) IN (${tags.
|
|
33
|
+
AND EXISTS (SELECT 1 FROM json_each(${jsonList('tags_json')}) WHERE lower(value) IN (${placeholders(tags.length)})))`,
|
|
33
34
|
params: [...tags.map((t) => `%"${t}"%`), ...tags],
|
|
34
35
|
};
|
|
35
36
|
}
|
package/dist/store/candidates.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FALLBACK_HALF_LIFE_DAYS, Layer, calculateStrength, facetsOf, schemaFitFrom } from '../core/memory.js';
|
|
2
2
|
import { strengthSql } from './rule-sql.js';
|
|
3
3
|
import { withReadSnapshot } from '../db/index.js';
|
|
4
|
-
import { scopeAdmitSql } from './rule-sql.js';
|
|
4
|
+
import { rewardFactorSql, scopeAdmitSql } from './rule-sql.js';
|
|
5
5
|
import { MEMORY_SELECT_COLUMNS, rowToEntry, parseJsonArray } from './rows.js';
|
|
6
6
|
import { onHandle, openStore } from './open.js';
|
|
7
7
|
import { originInSql } from '../core/project-identity.js';
|
|
@@ -89,10 +89,10 @@ export function loadAmbientCandidates(hippoRoot, tenantId, recentNeeded, admit,
|
|
|
89
89
|
}, openStore);
|
|
90
90
|
}
|
|
91
91
|
// calculateStrength's decay exponent with its reward factor, pins first: the strongest rows, without scoring each one in JS.
|
|
92
|
-
|
|
92
|
+
// A function, not a constant, so the outcome-slow ablation is read per query as strengthSql reads it.
|
|
93
|
+
const decayRankSql = () => `pinned DESC,
|
|
93
94
|
CASE WHEN half_life_days > 0 THEN (julianday(?) - julianday(last_retrieved))
|
|
94
|
-
/ (half_life_days * (
|
|
95
|
-
/ (COALESCE(outcome_positive, 0) + COALESCE(outcome_negative, 0) + 1.0))) END ASC NULLS LAST,
|
|
95
|
+
/ (half_life_days * ${rewardFactorSql()}) END ASC NULLS LAST,
|
|
96
96
|
id ASC`;
|
|
97
97
|
/** Live tenant rows passing `filter`, at most `filter.cap`, in loadAllEntries' order; below the cap, every such row. */
|
|
98
98
|
export function loadContextCandidates(hippoRoot, tenantId, filter) {
|
|
@@ -111,11 +111,21 @@ export function loadContextCandidates(hippoRoot, tenantId, filter) {
|
|
|
111
111
|
where.push(`(origin_project = '' OR ${originInSql(filter.project)})`);
|
|
112
112
|
params.push(...filter.project);
|
|
113
113
|
}
|
|
114
|
+
const cap = Math.max(0, Math.trunc(filter.cap));
|
|
115
|
+
const whereSql = where.join(' AND ');
|
|
114
116
|
return onHandle(hippoRoot, (db) => {
|
|
115
|
-
// SAFETY: the
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
117
|
+
// SAFETY: the count row has the one column named.
|
|
118
|
+
const matched = db.prepare(`SELECT COUNT(*) AS n FROM memories WHERE ${whereSql}`).get(...params).n;
|
|
119
|
+
// Under the cap every match is returned, so the per-row decay math and the sort are skipped.
|
|
120
|
+
// SHORTCUT: past the cap the rank still scans the tenant; a stored decay rank column needs a schema migration, held until one is approved.
|
|
121
|
+
const ranked = matched > cap;
|
|
122
|
+
const sql = ranked
|
|
123
|
+
? `SELECT ${MEMORY_SELECT_COLUMNS} FROM memories WHERE id IN (
|
|
124
|
+
SELECT id FROM memories WHERE ${whereSql} ORDER BY ${decayRankSql()} LIMIT ?
|
|
125
|
+
) ORDER BY created ASC, id ASC`
|
|
126
|
+
: `SELECT ${MEMORY_SELECT_COLUMNS} FROM memories WHERE ${whereSql} ORDER BY created ASC, id ASC`;
|
|
127
|
+
// SAFETY: both SELECTs name exactly MEMORY_SELECT_COLUMNS, matching MemoryRow's field set; the rank sort carries ids only.
|
|
128
|
+
const rows = db.prepare(sql).all(...params, ...(ranked ? [filter.now.toISOString(), cap] : []));
|
|
119
129
|
return rows.map(rowToEntry);
|
|
120
130
|
}, openStore);
|
|
121
131
|
}
|
package/dist/store/cards.js
CHANGED
|
@@ -6,6 +6,7 @@ import { assertTenantId } from './tenant.js';
|
|
|
6
6
|
import { onHandle, openStore } from './open.js';
|
|
7
7
|
import { chunked } from '../util/chunked.js';
|
|
8
8
|
import { HANDOFF_COLUMNS } from './handoffs.js';
|
|
9
|
+
import { placeholders } from '../util/sql.js';
|
|
9
10
|
const CARD_COLUMNS = 'id, title, status, assignee_runtime, repo, contract, budget, lease_until, heartbeat_at, created_at, updated_at, tenant_id, scope';
|
|
10
11
|
function rowToCard(row) {
|
|
11
12
|
return {
|
|
@@ -88,10 +89,10 @@ function transitionCards(db, tenantId, cardIds, options) {
|
|
|
88
89
|
const now = new Date().toISOString();
|
|
89
90
|
// Lease columns follow status: set on the move to running, cleared on every other move (rule 15).
|
|
90
91
|
const lease = to === 'running' ? [leaseUntilFrom(now), now] : [null, null];
|
|
91
|
-
const fromPlaceholders = from.
|
|
92
|
+
const fromPlaceholders = placeholders(from.length);
|
|
92
93
|
const sql = `
|
|
93
94
|
UPDATE cards SET status = ?, updated_at = ?, lease_until = ?, heartbeat_at = ?${extra?.setSql ? `, ${extra.setSql}` : ''}
|
|
94
|
-
WHERE id IN (${cardIds.
|
|
95
|
+
WHERE id IN (${placeholders(cardIds.length)}) AND tenant_id = ? AND status IN (${fromPlaceholders})${extra?.whereSql ? ` AND ${extra.whereSql}` : ''}
|
|
95
96
|
`;
|
|
96
97
|
const params = [to, now, ...lease, ...(extra?.params ?? []), ...cardIds, tenantId, ...from];
|
|
97
98
|
const result = db.prepare(sql).run(...params);
|
|
@@ -134,9 +135,9 @@ export function createCard(hippoRoot, tenantId, input) {
|
|
|
134
135
|
function everyParentDone(db, tenantId, dependsOn) {
|
|
135
136
|
if (dependsOn.length === 0)
|
|
136
137
|
return true;
|
|
137
|
-
const
|
|
138
|
+
const marks = placeholders(dependsOn.length);
|
|
138
139
|
// SAFETY: rows' shape matches the two columns named in the SELECT below.
|
|
139
|
-
const rows = db.prepare(`SELECT id, status FROM cards WHERE tenant_id = ? AND id IN (${
|
|
140
|
+
const rows = db.prepare(`SELECT id, status FROM cards WHERE tenant_id = ? AND id IN (${marks})`).all(tenantId, ...dependsOn);
|
|
140
141
|
const found = new Map(rows.map((r) => [r.id, r.status]));
|
|
141
142
|
// Pre-check before any write: a typo'd --depends-on can never commit a card row.
|
|
142
143
|
for (const parentId of dependsOn) {
|
|
@@ -334,24 +334,25 @@ function spoolRecorder(db, hippoRoot, log) {
|
|
|
334
334
|
saveItems(db, hippoRoot, itemContext(spooled.tenantId, record), log);
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
|
-
/** Runs `step` over each
|
|
338
|
-
|
|
337
|
+
/** Runs `step` over each row until the deadline; a row that will not map or a step that throws is logged and the rest still run.
|
|
338
|
+
* Returns how many `step` finished. */
|
|
339
|
+
function replayEach(run, rows, what, step) {
|
|
339
340
|
let finished = 0;
|
|
340
|
-
for (const
|
|
341
|
+
for (const row of rows) {
|
|
341
342
|
if (Date.now() > run.deadline)
|
|
342
343
|
break;
|
|
343
344
|
try {
|
|
344
|
-
if (step(
|
|
345
|
+
if (step(toRecord(row)))
|
|
345
346
|
finished++;
|
|
346
347
|
}
|
|
347
348
|
catch (err) {
|
|
348
|
-
run.log(`${what} of ${
|
|
349
|
+
run.log(`${what} of ${row.id} failed: ${errorMessage(err)}`);
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
return finished;
|
|
352
353
|
}
|
|
353
354
|
function replayStalled(run) {
|
|
354
|
-
const stalled = stalledSummarisedRows(run.db, run.tenantId, new Date(run.now - REPLAY_AFTER_MS).toISOString())
|
|
355
|
+
const stalled = stalledSummarisedRows(run.db, run.tenantId, new Date(run.now - REPLAY_AFTER_MS).toISOString());
|
|
355
356
|
return replayEach(run, stalled, 'replay', (record) => {
|
|
356
357
|
saveItems(run.db, run.hippoRoot, itemContext(run.tenantId, record), run.log);
|
|
357
358
|
return true;
|
|
@@ -389,7 +390,7 @@ function fillFromTranscript(run, record) {
|
|
|
389
390
|
return true;
|
|
390
391
|
}
|
|
391
392
|
function replayOpen(run) {
|
|
392
|
-
const open = openTranscriptRows(run.db, run.tenantId, new Date(run.now - REPLAY_AFTER_MS).toISOString(), new Date(run.now - TRANSCRIPT_FILL_WINDOW_MS).toISOString())
|
|
393
|
+
const open = openTranscriptRows(run.db, run.tenantId, new Date(run.now - REPLAY_AFTER_MS).toISOString(), new Date(run.now - TRANSCRIPT_FILL_WINDOW_MS).toISOString());
|
|
393
394
|
return replayEach(run, open, 'transcript fill', (record) => fillFromTranscript(run, record));
|
|
394
395
|
}
|
|
395
396
|
/** Finishes what a killed hook or a busy store left: `summarised` records, spool files, and `started` records the transcript can fill.
|