hippo-memory 0.36.0 → 0.38.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 +62 -254
- package/dist/api.d.ts +20 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +23 -3
- package/dist/api.js.map +1 -1
- package/dist/benchmarks/e1.3/incident-recall-eval.js +74 -0
- package/dist/benchmarks/e1.3/incident-recall-eval.js.map +1 -0
- package/dist/benchmarks/e1.3/scenarios.json +2587 -0
- package/dist/benchmarks/e1.3/slack-1000-event-smoke.js +102 -0
- package/dist/benchmarks/e1.3/slack-1000-event-smoke.js.map +1 -0
- package/dist/cli.js +449 -0
- package/dist/cli.js.map +1 -1
- package/dist/connectors/slack/backfill.d.ts +42 -0
- package/dist/connectors/slack/backfill.d.ts.map +1 -0
- package/dist/connectors/slack/backfill.js +76 -0
- package/dist/connectors/slack/backfill.js.map +1 -0
- package/dist/connectors/slack/deletion.d.ts +14 -0
- package/dist/connectors/slack/deletion.d.ts.map +1 -0
- package/dist/connectors/slack/deletion.js +46 -0
- package/dist/connectors/slack/deletion.js.map +1 -0
- package/dist/connectors/slack/dlq.d.ts +21 -0
- package/dist/connectors/slack/dlq.d.ts.map +1 -0
- package/dist/connectors/slack/dlq.js +23 -0
- package/dist/connectors/slack/dlq.js.map +1 -0
- package/dist/connectors/slack/idempotency.d.ts +5 -0
- package/dist/connectors/slack/idempotency.d.ts.map +1 -0
- package/dist/connectors/slack/idempotency.js +13 -0
- package/dist/connectors/slack/idempotency.js.map +1 -0
- package/dist/connectors/slack/ingest.d.ts +27 -0
- package/dist/connectors/slack/ingest.d.ts.map +1 -0
- package/dist/connectors/slack/ingest.js +48 -0
- package/dist/connectors/slack/ingest.js.map +1 -0
- package/dist/connectors/slack/ratelimit.d.ts +9 -0
- package/dist/connectors/slack/ratelimit.d.ts.map +1 -0
- package/dist/connectors/slack/ratelimit.js +18 -0
- package/dist/connectors/slack/ratelimit.js.map +1 -0
- package/dist/connectors/slack/scope.d.ts +16 -0
- package/dist/connectors/slack/scope.d.ts.map +1 -0
- package/dist/connectors/slack/scope.js +13 -0
- package/dist/connectors/slack/scope.js.map +1 -0
- package/dist/connectors/slack/signature.d.ts +12 -0
- package/dist/connectors/slack/signature.d.ts.map +1 -0
- package/dist/connectors/slack/signature.js +20 -0
- package/dist/connectors/slack/signature.js.map +1 -0
- package/dist/connectors/slack/tenant-routing.d.ts +13 -0
- package/dist/connectors/slack/tenant-routing.d.ts.map +1 -0
- package/dist/connectors/slack/tenant-routing.js +17 -0
- package/dist/connectors/slack/tenant-routing.js.map +1 -0
- package/dist/connectors/slack/transform.d.ts +20 -0
- package/dist/connectors/slack/transform.d.ts.map +1 -0
- package/dist/connectors/slack/transform.js +31 -0
- package/dist/connectors/slack/transform.js.map +1 -0
- package/dist/connectors/slack/types.d.ts +35 -0
- package/dist/connectors/slack/types.d.ts.map +1 -0
- package/dist/connectors/slack/types.js +23 -0
- package/dist/connectors/slack/types.js.map +1 -0
- package/dist/connectors/slack/web-client.d.ts +12 -0
- package/dist/connectors/slack/web-client.d.ts.map +1 -0
- package/dist/connectors/slack/web-client.js +43 -0
- package/dist/connectors/slack/web-client.js.map +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +105 -1
- package/dist/db.js.map +1 -1
- package/dist/goals.d.ts +73 -0
- package/dist/goals.d.ts.map +1 -0
- package/dist/goals.js +227 -0
- package/dist/goals.js.map +1 -0
- package/dist/importers.js +3 -3
- package/dist/importers.js.map +1 -1
- package/dist/mcp/server.js +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +174 -2
- package/dist/server.js.map +1 -1
- package/dist/src/ambient.js +147 -0
- package/dist/src/ambient.js.map +1 -0
- package/dist/src/api.js +343 -0
- package/dist/src/api.js.map +1 -0
- package/dist/src/audit.js +152 -0
- package/dist/src/audit.js.map +1 -0
- package/dist/src/auth.js +65 -0
- package/dist/src/auth.js.map +1 -0
- package/dist/src/autolearn.js +143 -0
- package/dist/src/autolearn.js.map +1 -0
- package/dist/src/capture.js +512 -0
- package/dist/src/capture.js.map +1 -0
- package/dist/src/cli.js +5338 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/client.js +181 -0
- package/dist/src/client.js.map +1 -0
- package/dist/src/config.js +108 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/connectors/slack/backfill.js +76 -0
- package/dist/src/connectors/slack/backfill.js.map +1 -0
- package/dist/src/connectors/slack/deletion.js +46 -0
- package/dist/src/connectors/slack/deletion.js.map +1 -0
- package/dist/src/connectors/slack/dlq.js +23 -0
- package/dist/src/connectors/slack/dlq.js.map +1 -0
- package/dist/src/connectors/slack/idempotency.js +13 -0
- package/dist/src/connectors/slack/idempotency.js.map +1 -0
- package/dist/src/connectors/slack/ingest.js +48 -0
- package/dist/src/connectors/slack/ingest.js.map +1 -0
- package/dist/src/connectors/slack/ratelimit.js +18 -0
- package/dist/src/connectors/slack/ratelimit.js.map +1 -0
- package/dist/src/connectors/slack/scope.js +13 -0
- package/dist/src/connectors/slack/scope.js.map +1 -0
- package/dist/src/connectors/slack/signature.js +20 -0
- package/dist/src/connectors/slack/signature.js.map +1 -0
- package/dist/src/connectors/slack/tenant-routing.js +17 -0
- package/dist/src/connectors/slack/tenant-routing.js.map +1 -0
- package/dist/src/connectors/slack/transform.js +31 -0
- package/dist/src/connectors/slack/transform.js.map +1 -0
- package/dist/src/connectors/slack/types.js +23 -0
- package/dist/src/connectors/slack/types.js.map +1 -0
- package/dist/src/connectors/slack/web-client.js +43 -0
- package/dist/src/connectors/slack/web-client.js.map +1 -0
- package/dist/src/consolidate.js +517 -0
- package/dist/src/consolidate.js.map +1 -0
- package/dist/src/dag.js +104 -0
- package/dist/src/dag.js.map +1 -0
- package/dist/src/dashboard.js +409 -0
- package/dist/src/dashboard.js.map +1 -0
- package/dist/src/db.js +643 -0
- package/dist/src/db.js.map +1 -0
- package/dist/src/embeddings.js +344 -0
- package/dist/src/embeddings.js.map +1 -0
- package/dist/src/eval-suite.js +289 -0
- package/dist/src/eval-suite.js.map +1 -0
- package/dist/src/eval.js +187 -0
- package/dist/src/eval.js.map +1 -0
- package/dist/src/extract.js +87 -0
- package/dist/src/extract.js.map +1 -0
- package/dist/src/goals.js +227 -0
- package/dist/src/goals.js.map +1 -0
- package/dist/src/handoff.js +30 -0
- package/dist/src/handoff.js.map +1 -0
- package/dist/src/hooks.js +582 -0
- package/dist/src/hooks.js.map +1 -0
- package/dist/src/importers.js +399 -0
- package/dist/src/importers.js.map +1 -0
- package/dist/src/index.js +25 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/invalidation.js +94 -0
- package/dist/src/invalidation.js.map +1 -0
- package/dist/src/mcp/framing.js +45 -0
- package/dist/src/mcp/framing.js.map +1 -0
- package/dist/src/mcp/server.js +510 -0
- package/dist/src/mcp/server.js.map +1 -0
- package/dist/src/memory.js +280 -0
- package/dist/src/memory.js.map +1 -0
- package/dist/src/multihop.js +32 -0
- package/dist/src/multihop.js.map +1 -0
- package/dist/src/path-context.js +32 -0
- package/dist/src/path-context.js.map +1 -0
- package/dist/src/physics-config.js +26 -0
- package/dist/src/physics-config.js.map +1 -0
- package/dist/src/physics-state.js +163 -0
- package/dist/src/physics-state.js.map +1 -0
- package/dist/src/physics.js +361 -0
- package/dist/src/physics.js.map +1 -0
- package/dist/src/postinstall.js +68 -0
- package/dist/src/postinstall.js.map +1 -0
- package/dist/src/raw-archive.js +72 -0
- package/dist/src/raw-archive.js.map +1 -0
- package/dist/src/refine-llm.js +147 -0
- package/dist/src/refine-llm.js.map +1 -0
- package/dist/src/replay.js +117 -0
- package/dist/src/replay.js.map +1 -0
- package/dist/src/salience.js +74 -0
- package/dist/src/salience.js.map +1 -0
- package/dist/src/scheduler.js +67 -0
- package/dist/src/scheduler.js.map +1 -0
- package/dist/src/scope.js +35 -0
- package/dist/src/scope.js.map +1 -0
- package/dist/src/search.js +801 -0
- package/dist/src/search.js.map +1 -0
- package/dist/src/server-detect.js +70 -0
- package/dist/src/server-detect.js.map +1 -0
- package/dist/src/server.js +784 -0
- package/dist/src/server.js.map +1 -0
- package/dist/src/shared.js +309 -0
- package/dist/src/shared.js.map +1 -0
- package/dist/src/sso.js +22 -0
- package/dist/src/sso.js.map +1 -0
- package/dist/src/store.js +1390 -0
- package/dist/src/store.js.map +1 -0
- package/dist/src/tenant.js +17 -0
- package/dist/src/tenant.js.map +1 -0
- package/dist/src/trace.js +64 -0
- package/dist/src/trace.js.map +1 -0
- package/dist/src/working-memory.js +149 -0
- package/dist/src/working-memory.js.map +1 -0
- package/dist/src/yaml.js +98 -0
- package/dist/src/yaml.js.map +1 -0
- package/dist/store.d.ts +9 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +30 -2
- package/dist/store.js.map +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 +2 -2
- package/dist/import.d.ts +0 -31
- package/dist/import.d.ts.map +0 -1
- package/dist/import.js +0 -307
- package/dist/import.js.map +0 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E1.3 1000-event Slack smoke benchmark.
|
|
3
|
+
*
|
|
4
|
+
* ROADMAP success criterion: ingest 1000 synthetic Slack events, replay each
|
|
5
|
+
* one twice, and prove (a) exactly `count` unique memories are written
|
|
6
|
+
* (idempotency holds), (b) zero outbound HTTP calls escape the connector
|
|
7
|
+
* layer, (c) per-call latency stays under 500ms.
|
|
8
|
+
*
|
|
9
|
+
* The no-outbound-HTTP invariant is proven (review patch #5) by replacing
|
|
10
|
+
* `globalThis.fetch` with a function that THROWS on any call. If a future
|
|
11
|
+
* code path inside the connector ever reaches for fetch (e.g. a user
|
|
12
|
+
* resolution shortcut), this smoke fails loud rather than silently lying
|
|
13
|
+
* with a hardcoded 0. The original fetch is restored in `finally` so the
|
|
14
|
+
* spy never poisons later tests.
|
|
15
|
+
*/
|
|
16
|
+
import { ingestMessage } from '../../src/connectors/slack/ingest.js';
|
|
17
|
+
import { loadAllEntries } from '../../src/store.js';
|
|
18
|
+
export async function runSlackSmoke(opts) {
|
|
19
|
+
const ctx = {
|
|
20
|
+
hippoRoot: opts.hippoRoot,
|
|
21
|
+
tenantId: 'default',
|
|
22
|
+
actor: 'connector:slack',
|
|
23
|
+
};
|
|
24
|
+
const start = Date.now();
|
|
25
|
+
let calls = 0;
|
|
26
|
+
// Review patch #5: spy globalThis.fetch with a throwing function. The smoke
|
|
27
|
+
// fails LOUD if anything in the ingest path ever calls fetch().
|
|
28
|
+
const origFetch = globalThis.fetch;
|
|
29
|
+
let outboundHttp = 0;
|
|
30
|
+
globalThis.fetch = ((..._args) => {
|
|
31
|
+
outboundHttp++;
|
|
32
|
+
throw new Error('outbound HTTP forbidden during slack smoke');
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
for (let pass = 0; pass < opts.replay; pass++) {
|
|
36
|
+
for (let i = 0; i < opts.count; i++) {
|
|
37
|
+
ingestMessage(ctx, {
|
|
38
|
+
teamId: 'T1',
|
|
39
|
+
channel: { id: 'C1', is_private: false },
|
|
40
|
+
message: {
|
|
41
|
+
type: 'message',
|
|
42
|
+
channel: 'C1',
|
|
43
|
+
user: 'U1',
|
|
44
|
+
// Use a content body well over the 3-char minimum enforced by
|
|
45
|
+
// memory.ts so synthetic events survive validation.
|
|
46
|
+
text: `slack synthetic event ${i}`,
|
|
47
|
+
ts: `${1700000000 + i}.000100`,
|
|
48
|
+
},
|
|
49
|
+
eventId: `Ev-${i}`,
|
|
50
|
+
});
|
|
51
|
+
calls++;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
globalThis.fetch = origFetch;
|
|
57
|
+
}
|
|
58
|
+
const uniqueMemories = loadAllEntries(opts.hippoRoot).filter((e) => e.tags.includes('source:slack')).length;
|
|
59
|
+
return {
|
|
60
|
+
uniqueMemories,
|
|
61
|
+
totalIngestCalls: calls,
|
|
62
|
+
outboundHttp,
|
|
63
|
+
durationMs: Date.now() - start,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
// Standalone CLI: 1000 events, replay=2, fail loud on regression.
|
|
67
|
+
// Use pathToFileURL for cross-platform `import.meta.url` matching (Windows
|
|
68
|
+
// emits `file:///C:/...` which `file://${process.argv[1]}` cannot match).
|
|
69
|
+
const { pathToFileURL } = await import('url');
|
|
70
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
71
|
+
const { mkdtempSync, rmSync } = await import('fs');
|
|
72
|
+
const { tmpdir } = await import('os');
|
|
73
|
+
const { join } = await import('path');
|
|
74
|
+
const { initStore } = await import('../../src/store.js');
|
|
75
|
+
const root = mkdtempSync(join(tmpdir(), 'hippo-slack-smoke-cli-'));
|
|
76
|
+
try {
|
|
77
|
+
initStore(root);
|
|
78
|
+
const r = await runSlackSmoke({ hippoRoot: root, count: 1000, replay: 2 });
|
|
79
|
+
console.log(JSON.stringify(r, null, 2));
|
|
80
|
+
if (r.uniqueMemories !== 1000) {
|
|
81
|
+
console.error('FAIL: uniqueMemories !== 1000');
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
if (r.outboundHttp !== 0) {
|
|
85
|
+
console.error('FAIL: outboundHttp !== 0');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
if (r.totalIngestCalls !== 2000) {
|
|
89
|
+
console.error('FAIL: totalIngestCalls !== 2000');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
if (r.durationMs / r.totalIngestCalls > 500) {
|
|
93
|
+
console.error('FAIL: per-call latency >500ms');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
console.log('PASS');
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
rmSync(root, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=slack-1000-event-smoke.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack-1000-event-smoke.js","sourceRoot":"","sources":["../../../benchmarks/e1.3/slack-1000-event-smoke.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -41,6 +41,8 @@ import { loadPhysicsState, resetAllPhysicsState } from './physics-state.js';
|
|
|
41
41
|
import { computeSystemEnergy, vecNorm } from './physics.js';
|
|
42
42
|
import { loadConfig } from './config.js';
|
|
43
43
|
import { openHippoDb, closeHippoDb } from './db.js';
|
|
44
|
+
import { getActiveGoalsWithDb, MAX_FINAL_MULTIPLIER, pushGoal, getActiveGoals, completeGoal, suspendGoal, resumeGoal } from './goals.js';
|
|
45
|
+
import { rowToGoal } from './goals.js';
|
|
44
46
|
import { captureError, extractLessons, deduplicateLesson, runWatched, fetchGitLog, isGitRepo, } from './autolearn.js';
|
|
45
47
|
import { extractInvalidationTarget, invalidateMatching } from './invalidation.js';
|
|
46
48
|
import { extractPathTags } from './path-context.js';
|
|
@@ -62,6 +64,9 @@ import { wmPush, wmRead, wmClear, wmFlush } from './working-memory.js';
|
|
|
62
64
|
import { multihopSearch } from './multihop.js';
|
|
63
65
|
import { computeSalience } from './salience.js';
|
|
64
66
|
import { computeAmbientState, renderAmbientSummary } from './ambient.js';
|
|
67
|
+
import { listDlq } from './connectors/slack/dlq.js';
|
|
68
|
+
import { backfillChannel } from './connectors/slack/backfill.js';
|
|
69
|
+
import { slackHistoryFetcher } from './connectors/slack/web-client.js';
|
|
65
70
|
// ---------------------------------------------------------------------------
|
|
66
71
|
// Helpers
|
|
67
72
|
// ---------------------------------------------------------------------------
|
|
@@ -817,6 +822,142 @@ async function cmdRecall(hippoRoot, query, flags) {
|
|
|
817
822
|
.map((r) => (r.entry.tags?.includes(goalTag) ? { ...r, score: r.score * 1.5 } : r))
|
|
818
823
|
.sort((a, b) => b.score - a.score);
|
|
819
824
|
}
|
|
825
|
+
// dlPFC depth (B3, v0.38). When HIPPO_SESSION_ID is set (env or
|
|
826
|
+
// --session-id flag) and the (tenant, session) has active goals, boost
|
|
827
|
+
// memories whose tags overlap any active goal's name. Final multiplier is
|
|
828
|
+
// hard-capped at MAX_FINAL_MULTIPLIER (3.0x). Each boosted (memory, goal)
|
|
829
|
+
// pair is logged into goal_recall_log for outcome propagation.
|
|
830
|
+
//
|
|
831
|
+
// Runs AFTER the explicit `--goal <tag>` block so an explicit flag always
|
|
832
|
+
// wins: if the user passed `--goal X`, this block is skipped entirely
|
|
833
|
+
// (gated on `goalTag === ''`).
|
|
834
|
+
//
|
|
835
|
+
// db-handle note (plan-eng-review fix #5): the surrounding cmdRecall path
|
|
836
|
+
// does NOT keep an open db handle in scope at this point — earlier search
|
|
837
|
+
// helpers (loadSearchEntries, hybridSearch, ...) each open and close their
|
|
838
|
+
// own short-lived handles. Reusing isn't practical here; we open a fresh
|
|
839
|
+
// short-lived handle for this block, mirroring the existing CLI pattern
|
|
840
|
+
// (e.g. emitCliAudit). Closed in `finally`.
|
|
841
|
+
const sessionId = (flags['session-id'] !== undefined
|
|
842
|
+
? String(flags['session-id'])
|
|
843
|
+
: process.env.HIPPO_SESSION_ID ?? '').trim();
|
|
844
|
+
if (sessionId && goalTag === '') {
|
|
845
|
+
// Use the same tenant as the recall path — see cmdRecall:778.
|
|
846
|
+
const tenantIdForGoals = tenantId;
|
|
847
|
+
const dbForGoals = openHippoDb(hippoRoot);
|
|
848
|
+
try {
|
|
849
|
+
const active = getActiveGoalsWithDb(dbForGoals, {
|
|
850
|
+
sessionId,
|
|
851
|
+
tenantId: tenantIdForGoals,
|
|
852
|
+
});
|
|
853
|
+
if (active.length > 0) {
|
|
854
|
+
const goalsByTag = new Map(active.map((g) => [g.goalName, g]));
|
|
855
|
+
// Task 7: load retrieval_policy rows for active goals so per-policy
|
|
856
|
+
// multipliers can compose onto the base goal-tag boost. The composed
|
|
857
|
+
// result is hard-capped at MAX_FINAL_MULTIPLIER (3.0x) BEFORE applying
|
|
858
|
+
// to score — even an `errorPriority: 9.0` policy cannot exceed 3.0x.
|
|
859
|
+
const policiesByGoalId = new Map();
|
|
860
|
+
for (const g of active) {
|
|
861
|
+
if (!g.retrievalPolicyId)
|
|
862
|
+
continue;
|
|
863
|
+
const row = dbForGoals.prepare(`
|
|
864
|
+
SELECT id, goal_id, policy_type, weight_schema_fit, weight_recency, weight_outcome, error_priority
|
|
865
|
+
FROM retrieval_policy WHERE id = ?
|
|
866
|
+
`).get(g.retrievalPolicyId);
|
|
867
|
+
if (row) {
|
|
868
|
+
policiesByGoalId.set(g.id, {
|
|
869
|
+
id: row.id,
|
|
870
|
+
goalId: row.goal_id,
|
|
871
|
+
policyType: row.policy_type,
|
|
872
|
+
weightSchemaFit: row.weight_schema_fit,
|
|
873
|
+
weightRecency: row.weight_recency,
|
|
874
|
+
weightOutcome: row.weight_outcome,
|
|
875
|
+
errorPriority: row.error_priority,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
results = results
|
|
880
|
+
.map((r) => {
|
|
881
|
+
const tags = r.entry.tags ?? [];
|
|
882
|
+
const matches = tags.filter((t) => goalsByTag.has(t));
|
|
883
|
+
if (matches.length === 0)
|
|
884
|
+
return r;
|
|
885
|
+
// Base 2.0x for first match, +0.5x per additional, capped at 3.0x.
|
|
886
|
+
let multiplier = Math.min(2.0 + 0.5 * (matches.length - 1), MAX_FINAL_MULTIPLIER);
|
|
887
|
+
// Compose per-policy multipliers per matched tag.
|
|
888
|
+
for (const tag of matches) {
|
|
889
|
+
const goal = goalsByTag.get(tag);
|
|
890
|
+
const policy = policiesByGoalId.get(goal.id);
|
|
891
|
+
if (!policy)
|
|
892
|
+
continue;
|
|
893
|
+
if (policy.policyType === 'error-prioritized' && tags.includes('error')) {
|
|
894
|
+
multiplier *= policy.errorPriority;
|
|
895
|
+
}
|
|
896
|
+
else if (policy.policyType === 'schema-fit-biased') {
|
|
897
|
+
// Linearly weight schema_fit in [0,1] up to (weightSchemaFit)x.
|
|
898
|
+
// Default 1.0 is a no-op.
|
|
899
|
+
multiplier *=
|
|
900
|
+
1.0 +
|
|
901
|
+
Math.max(0, policy.weightSchemaFit - 1.0) *
|
|
902
|
+
(r.entry.schema_fit ?? 0.5);
|
|
903
|
+
}
|
|
904
|
+
else if (policy.policyType === 'recency-first') {
|
|
905
|
+
multiplier *= policy.weightRecency;
|
|
906
|
+
}
|
|
907
|
+
else if (policy.policyType === 'hybrid') {
|
|
908
|
+
multiplier *= policy.weightOutcome;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
// Hard cap AFTER all composition.
|
|
912
|
+
multiplier = Math.min(multiplier, MAX_FINAL_MULTIPLIER);
|
|
913
|
+
return {
|
|
914
|
+
...r,
|
|
915
|
+
score: r.score * multiplier,
|
|
916
|
+
_goalMatches: matches,
|
|
917
|
+
};
|
|
918
|
+
})
|
|
919
|
+
.sort((a, b) => b.score - a.score);
|
|
920
|
+
// Filter to local memories only — global memory IDs aren't in this
|
|
921
|
+
// DB's memories table, so the FK on goal_recall_log.memory_id would
|
|
922
|
+
// fail. dlPFC depth's outcome propagation is session-scoped to local;
|
|
923
|
+
// boost on ranking still applies to global results, just no log row
|
|
924
|
+
// -> no propagation.
|
|
925
|
+
const topKIds = results.slice(0, limit).map((r) => r.entry.id);
|
|
926
|
+
const localIds = new Set();
|
|
927
|
+
if (topKIds.length > 0) {
|
|
928
|
+
const placeholders = topKIds.map(() => '?').join(',');
|
|
929
|
+
const localRows = dbForGoals.prepare(`SELECT id FROM memories WHERE id IN (${placeholders})`).all(...topKIds);
|
|
930
|
+
for (const row of localRows)
|
|
931
|
+
localIds.add(row.id);
|
|
932
|
+
}
|
|
933
|
+
// Log top-K boosted recalls. INSERT OR IGNORE because
|
|
934
|
+
// UNIQUE(memory_id, goal_id) means a re-recall during the same goal
|
|
935
|
+
// life is a no-op for outcome attribution.
|
|
936
|
+
const recalledAt = new Date().toISOString();
|
|
937
|
+
const insertLog = dbForGoals.prepare(`
|
|
938
|
+
INSERT OR IGNORE INTO goal_recall_log
|
|
939
|
+
(goal_id, memory_id, tenant_id, session_id, recalled_at, score)
|
|
940
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
941
|
+
`);
|
|
942
|
+
for (const r of results.slice(0, limit)) {
|
|
943
|
+
if (!localIds.has(r.entry.id))
|
|
944
|
+
continue; // global -> skip log insert
|
|
945
|
+
const matches = r._goalMatches;
|
|
946
|
+
if (!matches || matches.length === 0)
|
|
947
|
+
continue;
|
|
948
|
+
for (const tag of matches) {
|
|
949
|
+
const goal = goalsByTag.get(tag);
|
|
950
|
+
if (!goal)
|
|
951
|
+
continue;
|
|
952
|
+
insertLog.run(goal.id, r.entry.id, tenantIdForGoals, sessionId, recalledAt, r.score);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
finally {
|
|
958
|
+
closeHippoDb(dbForGoals);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
820
961
|
// Pineal salience MVP (RESEARCH.md §"AI Pineal Gland — Intuition and Awareness
|
|
821
962
|
// Module"). When --salience-threshold T is set (T > 0), memories whose
|
|
822
963
|
// retrieval_count is below T are downweighted: score *= max(0.5, count / T).
|
|
@@ -4027,6 +4168,211 @@ function cmdAuditLog(hippoRoot, args, flags) {
|
|
|
4027
4168
|
console.error(`Unknown audit subcommand: ${sub}. Expected: list.`);
|
|
4028
4169
|
process.exit(1);
|
|
4029
4170
|
}
|
|
4171
|
+
// ---------------------------------------------------------------------------
|
|
4172
|
+
// `hippo goal <push|list|complete|suspend|resume>` — B3 dlPFC depth (Task 10)
|
|
4173
|
+
// ---------------------------------------------------------------------------
|
|
4174
|
+
const GOAL_POLICY_TYPES = [
|
|
4175
|
+
'schema-fit-biased',
|
|
4176
|
+
'error-prioritized',
|
|
4177
|
+
'recency-first',
|
|
4178
|
+
'hybrid',
|
|
4179
|
+
];
|
|
4180
|
+
function sanitizeGoalName(s) {
|
|
4181
|
+
// Strip C0 control chars + DEL to prevent terminal escape injection.
|
|
4182
|
+
return s.replace(/[\x00-\x1f\x7f]/g, '?');
|
|
4183
|
+
}
|
|
4184
|
+
function resolveGoalSession(flags) {
|
|
4185
|
+
const sessionId = (flags['session-id'] !== undefined
|
|
4186
|
+
? String(flags['session-id'])
|
|
4187
|
+
: process.env.HIPPO_SESSION_ID ?? '').trim();
|
|
4188
|
+
if (!sessionId) {
|
|
4189
|
+
console.error('session id required (set HIPPO_SESSION_ID or pass --session-id)');
|
|
4190
|
+
process.exit(1);
|
|
4191
|
+
}
|
|
4192
|
+
const tenantId = (flags['tenant-id'] !== undefined
|
|
4193
|
+
? String(flags['tenant-id'])
|
|
4194
|
+
: process.env.HIPPO_TENANT ?? 'default').trim() || 'default';
|
|
4195
|
+
return { sessionId, tenantId };
|
|
4196
|
+
}
|
|
4197
|
+
function cmdGoalPush(hippoRoot, args, flags) {
|
|
4198
|
+
const rawName = args.join(' ').trim();
|
|
4199
|
+
if (!rawName) {
|
|
4200
|
+
console.error('Usage: hippo goal push <name> [--policy <type>] [--success "<condition>"] [--level N] [--parent <goalId>]');
|
|
4201
|
+
process.exit(1);
|
|
4202
|
+
}
|
|
4203
|
+
// Sanitize at WRITE time so corrupt names never enter the DB.
|
|
4204
|
+
const name = sanitizeGoalName(rawName);
|
|
4205
|
+
if (name !== rawName) {
|
|
4206
|
+
console.error('note: stripped control characters from goal name');
|
|
4207
|
+
}
|
|
4208
|
+
const { sessionId, tenantId } = resolveGoalSession(flags);
|
|
4209
|
+
let policy;
|
|
4210
|
+
const policyRaw = flags['policy'];
|
|
4211
|
+
if (policyRaw === true) {
|
|
4212
|
+
console.error('--policy requires a value (e.g., --policy error-prioritized)');
|
|
4213
|
+
process.exit(1);
|
|
4214
|
+
}
|
|
4215
|
+
if (typeof policyRaw === 'string') {
|
|
4216
|
+
if (!GOAL_POLICY_TYPES.includes(policyRaw)) {
|
|
4217
|
+
console.error(`Unknown --policy '${policyRaw}'. Expected one of: ${GOAL_POLICY_TYPES.join(' | ')}.`);
|
|
4218
|
+
process.exit(1);
|
|
4219
|
+
}
|
|
4220
|
+
policy = { policyType: policyRaw };
|
|
4221
|
+
}
|
|
4222
|
+
const successRaw = flags['success'];
|
|
4223
|
+
if (successRaw === true) {
|
|
4224
|
+
console.error('--success requires a value (e.g., --success "<condition>")');
|
|
4225
|
+
process.exit(1);
|
|
4226
|
+
}
|
|
4227
|
+
const successCondition = typeof successRaw === 'string' ? successRaw : undefined;
|
|
4228
|
+
const levelRaw = flags['level'];
|
|
4229
|
+
let level;
|
|
4230
|
+
if (levelRaw === true) {
|
|
4231
|
+
console.error('--level requires a value (e.g., --level 1)');
|
|
4232
|
+
process.exit(1);
|
|
4233
|
+
}
|
|
4234
|
+
if (levelRaw !== undefined) {
|
|
4235
|
+
const parsed = Number(levelRaw);
|
|
4236
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 2 || !Number.isInteger(parsed)) {
|
|
4237
|
+
console.error('--level must be an integer in [0, 2]');
|
|
4238
|
+
process.exit(1);
|
|
4239
|
+
}
|
|
4240
|
+
level = parsed;
|
|
4241
|
+
}
|
|
4242
|
+
const parentRaw = flags['parent'];
|
|
4243
|
+
if (parentRaw === true) {
|
|
4244
|
+
console.error('--parent requires a value (e.g., --parent <goalId>)');
|
|
4245
|
+
process.exit(1);
|
|
4246
|
+
}
|
|
4247
|
+
const parentGoalId = typeof parentRaw === 'string' ? parentRaw : undefined;
|
|
4248
|
+
const goal = pushGoal(hippoRoot, {
|
|
4249
|
+
sessionId,
|
|
4250
|
+
tenantId,
|
|
4251
|
+
goalName: name,
|
|
4252
|
+
level,
|
|
4253
|
+
parentGoalId,
|
|
4254
|
+
successCondition,
|
|
4255
|
+
policy,
|
|
4256
|
+
});
|
|
4257
|
+
console.log(goal.id);
|
|
4258
|
+
}
|
|
4259
|
+
function listAllGoals(hippoRoot, sessionId, tenantId) {
|
|
4260
|
+
const db = openHippoDb(hippoRoot);
|
|
4261
|
+
try {
|
|
4262
|
+
const rows = db.prepare(`
|
|
4263
|
+
SELECT id, session_id, tenant_id, goal_name, level, parent_goal_id, status,
|
|
4264
|
+
success_condition, retrieval_policy_id, created_at, completed_at, outcome_score
|
|
4265
|
+
FROM goal_stack
|
|
4266
|
+
WHERE tenant_id = ? AND session_id = ?
|
|
4267
|
+
ORDER BY created_at ASC
|
|
4268
|
+
`).all(tenantId, sessionId);
|
|
4269
|
+
return rows.map(rowToGoal);
|
|
4270
|
+
}
|
|
4271
|
+
finally {
|
|
4272
|
+
closeHippoDb(db);
|
|
4273
|
+
}
|
|
4274
|
+
}
|
|
4275
|
+
function cmdGoalList(hippoRoot, flags) {
|
|
4276
|
+
const { sessionId, tenantId } = resolveGoalSession(flags);
|
|
4277
|
+
const showAll = Boolean(flags['all']);
|
|
4278
|
+
const goals = showAll
|
|
4279
|
+
? listAllGoals(hippoRoot, sessionId, tenantId)
|
|
4280
|
+
: getActiveGoals(hippoRoot, { sessionId, tenantId });
|
|
4281
|
+
if (goals.length === 0) {
|
|
4282
|
+
console.log('(no goals)');
|
|
4283
|
+
return;
|
|
4284
|
+
}
|
|
4285
|
+
// 4-column table: id, status, goal_name, outcome. Plan calls it a "2-column"
|
|
4286
|
+
// table but the assertion list (id, status, goal_name, outcome) needs four;
|
|
4287
|
+
// tests check for substrings ('active', '0.9', name) so column count is
|
|
4288
|
+
// observably four but not asserted.
|
|
4289
|
+
const rows = goals.map(g => ({
|
|
4290
|
+
id: g.id,
|
|
4291
|
+
status: g.status,
|
|
4292
|
+
name: sanitizeGoalName(g.goalName),
|
|
4293
|
+
outcome: g.outcomeScore !== undefined ? g.outcomeScore.toString() : '-',
|
|
4294
|
+
}));
|
|
4295
|
+
const widths = {
|
|
4296
|
+
id: Math.max(2, ...rows.map(r => r.id.length)),
|
|
4297
|
+
status: Math.max(6, ...rows.map(r => r.status.length)),
|
|
4298
|
+
name: Math.max(4, ...rows.map(r => r.name.length)),
|
|
4299
|
+
outcome: Math.max(7, ...rows.map(r => r.outcome.length)),
|
|
4300
|
+
};
|
|
4301
|
+
const pad = (s, w) => s + ' '.repeat(Math.max(0, w - s.length));
|
|
4302
|
+
console.log(`${pad('id', widths.id)} ${pad('status', widths.status)} ${pad('name', widths.name)} ${pad('outcome', widths.outcome)}`);
|
|
4303
|
+
for (const r of rows) {
|
|
4304
|
+
console.log(`${pad(r.id, widths.id)} ${pad(r.status, widths.status)} ${pad(r.name, widths.name)} ${pad(r.outcome, widths.outcome)}`);
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
function cmdGoalComplete(hippoRoot, args, flags) {
|
|
4308
|
+
const id = args[0];
|
|
4309
|
+
if (!id) {
|
|
4310
|
+
console.error('Usage: hippo goal complete <id> [--outcome <0..1>]');
|
|
4311
|
+
process.exit(1);
|
|
4312
|
+
}
|
|
4313
|
+
let outcomeScore;
|
|
4314
|
+
const outcomeRaw = flags['outcome'];
|
|
4315
|
+
if (outcomeRaw === true) {
|
|
4316
|
+
console.error('--outcome requires a value (e.g., --outcome 0.9)');
|
|
4317
|
+
process.exit(1);
|
|
4318
|
+
}
|
|
4319
|
+
if (outcomeRaw !== undefined) {
|
|
4320
|
+
const parsed = Number(outcomeRaw);
|
|
4321
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
4322
|
+
console.error('--outcome must be a number in [0, 1]');
|
|
4323
|
+
process.exit(1);
|
|
4324
|
+
}
|
|
4325
|
+
outcomeScore = parsed;
|
|
4326
|
+
}
|
|
4327
|
+
completeGoal(hippoRoot, id, { outcomeScore });
|
|
4328
|
+
console.log('ok');
|
|
4329
|
+
}
|
|
4330
|
+
function cmdGoalSuspend(hippoRoot, args) {
|
|
4331
|
+
const id = args[0];
|
|
4332
|
+
if (!id) {
|
|
4333
|
+
console.error('Usage: hippo goal suspend <id>');
|
|
4334
|
+
process.exit(1);
|
|
4335
|
+
}
|
|
4336
|
+
suspendGoal(hippoRoot, id);
|
|
4337
|
+
console.log('ok');
|
|
4338
|
+
}
|
|
4339
|
+
function cmdGoalResume(hippoRoot, args) {
|
|
4340
|
+
const id = args[0];
|
|
4341
|
+
if (!id) {
|
|
4342
|
+
console.error('Usage: hippo goal resume <id>');
|
|
4343
|
+
process.exit(1);
|
|
4344
|
+
}
|
|
4345
|
+
resumeGoal(hippoRoot, id);
|
|
4346
|
+
console.log('ok');
|
|
4347
|
+
}
|
|
4348
|
+
function cmdGoal(hippoRoot, args, flags) {
|
|
4349
|
+
const sub = args[0];
|
|
4350
|
+
if (!sub) {
|
|
4351
|
+
console.error('Usage: hippo goal <push|list|complete|suspend|resume> [args]');
|
|
4352
|
+
process.exit(1);
|
|
4353
|
+
}
|
|
4354
|
+
const subArgs = args.slice(1);
|
|
4355
|
+
switch (sub) {
|
|
4356
|
+
case 'push':
|
|
4357
|
+
cmdGoalPush(hippoRoot, subArgs, flags);
|
|
4358
|
+
return;
|
|
4359
|
+
case 'list':
|
|
4360
|
+
cmdGoalList(hippoRoot, flags);
|
|
4361
|
+
return;
|
|
4362
|
+
case 'complete':
|
|
4363
|
+
cmdGoalComplete(hippoRoot, subArgs, flags);
|
|
4364
|
+
return;
|
|
4365
|
+
case 'suspend':
|
|
4366
|
+
cmdGoalSuspend(hippoRoot, subArgs);
|
|
4367
|
+
return;
|
|
4368
|
+
case 'resume':
|
|
4369
|
+
cmdGoalResume(hippoRoot, subArgs);
|
|
4370
|
+
return;
|
|
4371
|
+
default:
|
|
4372
|
+
console.error(`Unknown goal subcommand: ${sub}. Expected: push | list | complete | suspend | resume.`);
|
|
4373
|
+
process.exit(1);
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4030
4376
|
function cmdAuth(hippoRoot, args, flags) {
|
|
4031
4377
|
const sub = args[0];
|
|
4032
4378
|
if (!sub) {
|
|
@@ -4055,6 +4401,82 @@ function cmdAuth(hippoRoot, args, flags) {
|
|
|
4055
4401
|
process.exit(1);
|
|
4056
4402
|
}
|
|
4057
4403
|
}
|
|
4404
|
+
// ---------------------------------------------------------------------------
|
|
4405
|
+
// Slack subcommands (E1.3 — `hippo slack backfill` / `hippo slack dlq list`)
|
|
4406
|
+
// ---------------------------------------------------------------------------
|
|
4407
|
+
function printSlackBackfillUsage() {
|
|
4408
|
+
console.log('hippo slack backfill --channel <id> [--since ISO]');
|
|
4409
|
+
console.log(' --channel Slack channel id (required, e.g. C0123ABC)');
|
|
4410
|
+
console.log(' --since backfill from ISO timestamp (default: cursor)');
|
|
4411
|
+
}
|
|
4412
|
+
function cmdSlackBackfill(hippoRoot, flags) {
|
|
4413
|
+
// M3: detect --help BEFORE token check so operators can read usage in
|
|
4414
|
+
// environments without SLACK_BOT_TOKEN configured.
|
|
4415
|
+
if (flags['help']) {
|
|
4416
|
+
printSlackBackfillUsage();
|
|
4417
|
+
return;
|
|
4418
|
+
}
|
|
4419
|
+
const channel = typeof flags['channel'] === 'string' ? flags['channel'] : undefined;
|
|
4420
|
+
if (!channel) {
|
|
4421
|
+
printSlackBackfillUsage();
|
|
4422
|
+
process.exit(1);
|
|
4423
|
+
}
|
|
4424
|
+
// Real fetcher requires SLACK_BOT_TOKEN with channels:history scope.
|
|
4425
|
+
const token = process.env.SLACK_BOT_TOKEN;
|
|
4426
|
+
if (!token) {
|
|
4427
|
+
console.error('SLACK_BOT_TOKEN is not set. Backfill requires a Slack bot token with channels:history scope.');
|
|
4428
|
+
process.exit(2);
|
|
4429
|
+
}
|
|
4430
|
+
// --since is advisory in V1: the slack_cursors row drives resume, so the
|
|
4431
|
+
// backfill loop always picks up where it last left off. Honoured-by-cursor
|
|
4432
|
+
// semantics keep idempotency clean.
|
|
4433
|
+
const sinceIso = flags['since'];
|
|
4434
|
+
void sinceIso;
|
|
4435
|
+
const fetcher = slackHistoryFetcher(token);
|
|
4436
|
+
const ctx = {
|
|
4437
|
+
hippoRoot,
|
|
4438
|
+
tenantId: process.env.HIPPO_TENANT ?? 'default',
|
|
4439
|
+
actor: 'cli:slack-backfill',
|
|
4440
|
+
};
|
|
4441
|
+
backfillChannel(ctx, {
|
|
4442
|
+
teamId: process.env.SLACK_TEAM_ID ?? 'T_UNKNOWN',
|
|
4443
|
+
channel: { id: channel, is_private: false },
|
|
4444
|
+
fetcher,
|
|
4445
|
+
})
|
|
4446
|
+
.then((r) => {
|
|
4447
|
+
console.log(`backfill ${channel}: ${r.ingested} new messages across ${r.pages} pages`);
|
|
4448
|
+
})
|
|
4449
|
+
.catch((e) => {
|
|
4450
|
+
console.error('backfill failed:', e.message);
|
|
4451
|
+
process.exit(3);
|
|
4452
|
+
});
|
|
4453
|
+
}
|
|
4454
|
+
function cmdSlackDlqList(hippoRoot, _flags) {
|
|
4455
|
+
const db = openHippoDb(hippoRoot);
|
|
4456
|
+
try {
|
|
4457
|
+
const tenantId = process.env.HIPPO_TENANT ?? 'default';
|
|
4458
|
+
const items = listDlq(db, { tenantId });
|
|
4459
|
+
for (const it of items) {
|
|
4460
|
+
console.log(`${it.id}\t${it.receivedAt}\t${it.error}`);
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
finally {
|
|
4464
|
+
closeHippoDb(db);
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
function cmdSlack(hippoRoot, args, flags) {
|
|
4468
|
+
const sub = args[0];
|
|
4469
|
+
if (sub === 'backfill') {
|
|
4470
|
+
cmdSlackBackfill(hippoRoot, flags);
|
|
4471
|
+
return;
|
|
4472
|
+
}
|
|
4473
|
+
if (sub === 'dlq' && args[1] === 'list') {
|
|
4474
|
+
cmdSlackDlqList(hippoRoot, flags);
|
|
4475
|
+
return;
|
|
4476
|
+
}
|
|
4477
|
+
console.error('Usage: hippo slack <backfill|dlq list> [...]');
|
|
4478
|
+
process.exit(1);
|
|
4479
|
+
}
|
|
4058
4480
|
function printUsage() {
|
|
4059
4481
|
console.log(`
|
|
4060
4482
|
Hippo - biologically-inspired memory system for AI agents
|
|
@@ -4106,6 +4528,12 @@ Commands:
|
|
|
4106
4528
|
--goal <tag> dlPFC goal-conditioned recall: memories tagged with
|
|
4107
4529
|
the goal tag get a 1.5x score boost and results are
|
|
4108
4530
|
re-sorted. Default off. RESEARCH.md §PFC.dlPFC.
|
|
4531
|
+
--session-id <id> Session identifier for dlPFC goal-stack boost.
|
|
4532
|
+
Defaults to \$HIPPO_SESSION_ID. When set and the
|
|
4533
|
+
(tenant, session) has active goals (see
|
|
4534
|
+
'hippo goal push'), recall auto-boosts memories
|
|
4535
|
+
whose tags match an active goal name. Boost stacks
|
|
4536
|
+
on top of base BM25 score, capped at 3.0x.
|
|
4109
4537
|
--salience-threshold <n>
|
|
4110
4538
|
Pineal salience: down-weight memories whose
|
|
4111
4539
|
retrieval_count is below n. score *= max(0.5,
|
|
@@ -4291,6 +4719,21 @@ Commands:
|
|
|
4291
4719
|
dashboard Open web dashboard for memory health
|
|
4292
4720
|
--port <n> Port to serve on (default: 3333)
|
|
4293
4721
|
mcp Start MCP server (stdio transport)
|
|
4722
|
+
goal <sub> dlPFC goal stack (B3) — scoped per session
|
|
4723
|
+
goal push <name> Push a new active goal; prints the new goal id
|
|
4724
|
+
--policy <type> schema-fit-biased | error-prioritized |
|
|
4725
|
+
recency-first | hybrid
|
|
4726
|
+
--success "<cond>" Optional success condition text
|
|
4727
|
+
--level <n> Goal level (default: 0)
|
|
4728
|
+
--parent <goalId> Parent goal id (for sub-goals)
|
|
4729
|
+
--session-id <s> Override session (defaults to HIPPO_SESSION_ID)
|
|
4730
|
+
--tenant-id <t> Override tenant (defaults to HIPPO_TENANT)
|
|
4731
|
+
goal list Show active goals as a table
|
|
4732
|
+
--all Include suspended/completed goals
|
|
4733
|
+
goal complete <id> Mark a goal completed
|
|
4734
|
+
--outcome <0..1> Outcome score; >=0.7 boosts, <0.3 decays recalled mems
|
|
4735
|
+
goal suspend <id> Move an active goal to suspended
|
|
4736
|
+
goal resume <id> Move a suspended goal back to active (depth-capped)
|
|
4294
4737
|
auth <sub> Manage API keys (A5 stub auth)
|
|
4295
4738
|
auth create Mint a new API key (plaintext shown ONCE)
|
|
4296
4739
|
--label <s> Optional human label
|
|
@@ -4488,6 +4931,12 @@ async function main() {
|
|
|
4488
4931
|
case 'auth':
|
|
4489
4932
|
cmdAuth(hippoRoot, args, flags);
|
|
4490
4933
|
break;
|
|
4934
|
+
case 'goal':
|
|
4935
|
+
cmdGoal(hippoRoot, args, flags);
|
|
4936
|
+
break;
|
|
4937
|
+
case 'slack':
|
|
4938
|
+
cmdSlack(hippoRoot, args, flags);
|
|
4939
|
+
break;
|
|
4491
4940
|
case 'audit': {
|
|
4492
4941
|
// `audit list` -> A5 audit-log viewer. Other forms (no sub, --fix) keep
|
|
4493
4942
|
// the existing memory-quality auditor for backwards compatibility.
|