pi-goosedump 0.12.45 → 0.12.47

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 (3) hide show
  1. package/README.md +9 -6
  2. package/dist/index.ts +141 -36
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -57,7 +57,8 @@ Use each tool's schema for full options. Typical flow:
57
57
  | `/goose-sessions` | List, resume, or delete project sessions |
58
58
  | `/goose-forget [id\|provider:id]` | Forget stored memory |
59
59
  | `/goose-memory-status` | Show memory storage and indexing status |
60
- | `/goose-settings` | Edit project compaction settings |
60
+ | `/goose-settings` | Edit goosedump settings |
61
+ | `/goosedump-settings` | Edit goosedump settings (alias) |
61
62
 
62
63
  Resume is available only for Pi sessions.
63
64
 
@@ -70,15 +71,17 @@ Project: `.pi/settings.json`
70
71
  {
71
72
  "goosedump": {
72
73
  "turnsBeforeCompact": 0,
73
- "summaryMaxTokens": 4096
74
+ "summaryMaxTokens": 4096,
75
+ "enableMemory": false
74
76
  }
75
77
  }
76
78
  ```
77
79
 
78
- | Key | Range | Default | Notes |
79
- | -------------------- | ------------- | ------: | -------------------------------------------------------------- |
80
- | `turnsBeforeCompact` | `0`–`255` | 0 | `0` disables; each completed model response counts as one turn |
81
- | `summaryMaxTokens` | `512`–`65536` | 4096 | Capped to 80% of `compaction.reserveTokens` when lower |
80
+ | Key | Range | Default | Notes |
81
+ | -------------------- | -------------- | ------: | -------------------------------------------------------------- |
82
+ | `turnsBeforeCompact` | `0`–`255` | 0 | `0` disables; each completed model response counts as one turn |
83
+ | `summaryMaxTokens` | `512`–`65536` | 4096 | Capped to 80% of `compaction.reserveTokens` when lower |
84
+ | `enableMemory` | `true`/`false` | false | Controls whether durable memory tools/commands/learning are on |
82
85
 
83
86
  The footer shows the remaining turn count in hexadecimal.
84
87
 
package/dist/index.ts CHANGED
@@ -25,7 +25,8 @@ var MEMORY_TEXT_TRUNCATED = `
25
25
  [Entry text truncated]`;
26
26
  var DEFAULT_GOOSEDUMP_SETTINGS = {
27
27
  turnsBeforeCompact: 0,
28
- summaryMaxTokens: 4096
28
+ summaryMaxTokens: 4096,
29
+ enableMemory: false
29
30
  };
30
31
  var TURNS_BEFORE_COMPACT_MIN = 0;
31
32
  var TURNS_BEFORE_COMPACT_MAX = 255;
@@ -475,11 +476,23 @@ import {
475
476
  import { homedir } from "node:os";
476
477
  import { dirname as dirname2, join as join2 } from "node:path";
477
478
  var GOOSEDUMP_SETTING_SPECS = {
478
- turnsBeforeCompact: { min: TURNS_BEFORE_COMPACT_MIN, max: TURNS_BEFORE_COMPACT_MAX },
479
- summaryMaxTokens: { min: SUMMARY_MAX_TOKENS_MIN, max: SUMMARY_MAX_TOKENS_MAX }
479
+ turnsBeforeCompact: {
480
+ kind: "number",
481
+ min: TURNS_BEFORE_COMPACT_MIN,
482
+ max: TURNS_BEFORE_COMPACT_MAX
483
+ },
484
+ summaryMaxTokens: {
485
+ kind: "number",
486
+ min: SUMMARY_MAX_TOKENS_MIN,
487
+ max: SUMMARY_MAX_TOKENS_MAX
488
+ },
489
+ enableMemory: { kind: "boolean" }
480
490
  };
491
+ var MEMORY_DISABLED_MESSAGE = "Durable memory is disabled. Set goosedump.enableMemory to true in settings (or /goose-settings).";
481
492
  function isValidGoosedumpSettingValue(key, value) {
482
493
  const spec = GOOSEDUMP_SETTING_SPECS[key];
494
+ if (spec.kind === "boolean")
495
+ return typeof value === "boolean";
483
496
  return typeof value === "number" && Number.isSafeInteger(value) && value >= spec.min && value <= spec.max;
484
497
  }
485
498
  function pickValidGoosedumpSettings(raw) {
@@ -488,11 +501,15 @@ function pickValidGoosedumpSettings(raw) {
488
501
  const settings = {};
489
502
  for (const key of Object.keys(GOOSEDUMP_SETTING_SPECS)) {
490
503
  const value = raw[key];
491
- if (isValidGoosedumpSettingValue(key, value))
492
- settings[key] = value;
504
+ if (!isValidGoosedumpSettingValue(key, value))
505
+ continue;
506
+ assignGoosedumpSetting(settings, key, value);
493
507
  }
494
508
  return settings;
495
509
  }
510
+ function assignGoosedumpSetting(settings, key, value) {
511
+ settings[key] = value;
512
+ }
496
513
  function piAgentDir() {
497
514
  return process.env.PI_CODING_AGENT_DIR ?? join2(homedir(), ".pi", "agent");
498
515
  }
@@ -566,6 +583,12 @@ function writeJsonObjectAtomically(path, value) {
566
583
  function readGoosedumpSettingsFrom(path) {
567
584
  return pickValidGoosedumpSettings(readJsonObject(path).goosedump);
568
585
  }
586
+ function goosedumpSettingValidationMessage(key) {
587
+ const spec = GOOSEDUMP_SETTING_SPECS[key];
588
+ if (spec.kind === "boolean")
589
+ return "a boolean";
590
+ return `an integer from ${spec.min} to ${spec.max}`;
591
+ }
569
592
  function validateGoosedumpSettings(path) {
570
593
  const root = readJsonObjectStrict(path);
571
594
  if (root.goosedump === undefined)
@@ -576,8 +599,7 @@ function validateGoosedumpSettings(path) {
576
599
  for (const key of Object.keys(GOOSEDUMP_SETTING_SPECS)) {
577
600
  const value = root.goosedump[key];
578
601
  if (value !== undefined && !isValidGoosedumpSettingValue(key, value)) {
579
- const spec = GOOSEDUMP_SETTING_SPECS[key];
580
- throw new Error(`${path}: goosedump.${key} must be an integer from ${spec.min} to ${spec.max}`);
602
+ throw new Error(`${path}: goosedump.${key} must be ${goosedumpSettingValidationMessage(key)}`);
581
603
  }
582
604
  }
583
605
  }
@@ -624,8 +646,14 @@ function readScopedGoosedumpSettings(cwd, scope) {
624
646
  return { ...DEFAULT_GOOSEDUMP_SETTINGS, ...pickValidGoosedumpSettings(raw) };
625
647
  }
626
648
  function writeScopedGoosedumpSetting(cwd, scope, key, value) {
649
+ if (!isValidGoosedumpSettingValue(key, value)) {
650
+ throw new Error(`goosedump.${key} must be ${goosedumpSettingValidationMessage(key)}`);
651
+ }
627
652
  updateScopedGoosedumpSetting(cwd, scope, (current) => ({ ...current, [key]: value }));
628
653
  }
654
+ function isMemoryEnabled(cwd, includeProject) {
655
+ return resolveGoosedumpSettings(cwd, includeProject).enableMemory;
656
+ }
629
657
  function clearScopedGoosedumpSetting(cwd, scope, key) {
630
658
  updateScopedGoosedumpSetting(cwd, scope, (current, existed) => {
631
659
  if (!existed)
@@ -1466,6 +1494,14 @@ import {
1466
1494
  truncateHead
1467
1495
  } from "@earendil-works/pi-coding-agent";
1468
1496
  import { Type } from "@sinclair/typebox";
1497
+ function memoryDisabledResult() {
1498
+ return textResult(MEMORY_DISABLED_MESSAGE);
1499
+ }
1500
+ function requireMemoryEnabled(ctx) {
1501
+ if (isMemoryEnabled(ctx.cwd, projectSettingsTrusted(ctx)))
1502
+ return null;
1503
+ return memoryDisabledResult();
1504
+ }
1469
1505
  function historyProviderParam() {
1470
1506
  return Type.Optional(Type.Union(GOOSE_PROVIDERS.map((provider) => Type.Literal(provider)), {
1471
1507
  default: "pi",
@@ -1549,6 +1585,9 @@ function defineGooseTool(name, parameters, handler) {
1549
1585
  function registerGooseTools(pi, deps) {
1550
1586
  const { nativeExecutablePath, enqueueRemember, requestCompaction } = deps;
1551
1587
  pi.registerTool(defineGooseTool("goose_remember", Type.Object({ ...historyTargetParams() }), async (params, _signal, ctx) => {
1588
+ const disabled = requireMemoryEnabled(ctx);
1589
+ if (disabled)
1590
+ return disabled;
1552
1591
  const resolved = resolveHistoryTarget(ctx, "goose_remember", params);
1553
1592
  if (!resolved.ok)
1554
1593
  return resolved.result;
@@ -1577,6 +1616,9 @@ function registerGooseTools(pi, deps) {
1577
1616
  description: PARAM_DESCRIPTIONS.memoryLimit
1578
1617
  }))
1579
1618
  }), async (params, signal, ctx) => {
1619
+ const disabled = requireMemoryEnabled(ctx);
1620
+ if (disabled)
1621
+ return disabled;
1580
1622
  const { hits, warning } = await goosedumpRecall(params.query, {
1581
1623
  path: params.allProjects ? undefined : ctx.cwd,
1582
1624
  type: params.type,
@@ -1593,14 +1635,22 @@ function registerGooseTools(pi, deps) {
1593
1635
  target: Type.String({
1594
1636
  description: PARAM_DESCRIPTIONS.forgetTarget
1595
1637
  })
1596
- }), async (params, signal) => {
1638
+ }), async (params, signal, ctx) => {
1639
+ const disabled = requireMemoryEnabled(ctx);
1640
+ if (disabled)
1641
+ return disabled;
1597
1642
  if (params.target.trim().length === 0) {
1598
1643
  return textResult("goose_forget error: target is required");
1599
1644
  }
1600
1645
  const res = await goosedumpMemoryForget(params.target.trim(), signal, nativeExecutablePath);
1601
1646
  return textResult(res);
1602
1647
  }));
1603
- pi.registerTool(defineGooseTool("goose_memory_status", Type.Object({}), async (_params, signal) => textResult(await goosedumpMemoryStatus(signal, nativeExecutablePath))));
1648
+ pi.registerTool(defineGooseTool("goose_memory_status", Type.Object({}), async (_params, signal, ctx) => {
1649
+ const disabled = requireMemoryEnabled(ctx);
1650
+ if (disabled)
1651
+ return disabled;
1652
+ return textResult(await goosedumpMemoryStatus(signal, nativeExecutablePath));
1653
+ }));
1604
1654
  pi.registerTool(defineGooseTool("goose_search", Type.Object({
1605
1655
  query: Type.String({ description: PARAM_DESCRIPTIONS.searchQuery }),
1606
1656
  ...historyBranchParams(),
@@ -1726,6 +1776,15 @@ import {
1726
1776
  wrapTextWithAnsi
1727
1777
  } from "@earendil-works/pi-tui";
1728
1778
  import { existsSync as existsSync3 } from "node:fs";
1779
+ function formatSettingDisplayValue(key, value) {
1780
+ if (key === "turnsBeforeCompact" && typeof value === "number") {
1781
+ return formatCompactSetting(value);
1782
+ }
1783
+ if (typeof value === "boolean")
1784
+ return value ? "true" : "false";
1785
+ return String(value);
1786
+ }
1787
+
1729
1788
  class NumberInputSubmenu {
1730
1789
  input = new Input;
1731
1790
  hint;
@@ -1846,11 +1905,12 @@ async function runSettingsEditor(ctx) {
1846
1905
  const OVERLAY_WIDTH = 84;
1847
1906
  const location = ctx.cwd;
1848
1907
  const projectTrusted = projectSettingsTrusted(ctx);
1849
- const specs = Object.entries(GOOSEDUMP_SETTING_SPECS).map(([key, spec]) => ({ key, min: spec.min, max: spec.max }));
1908
+ const specs = Object.entries(GOOSEDUMP_SETTING_SPECS).map(([key, spec]) => ({ key, spec }));
1850
1909
  const helpRows = [
1851
1910
  ["Tab", "Next tab"],
1852
1911
  ["↑ / ↓", "Select setting"],
1853
- ["Enter", "Edit setting or submit value"],
1912
+ ["Enter", "Edit number, toggle boolean, or submit"],
1913
+ ["Backspace", "Clear project setting (inherit global)"],
1854
1914
  ["Esc", "Cancel edit or close"],
1855
1915
  ["Ctrl+C", "Close"],
1856
1916
  ["Empty + Enter", "Clear project setting (inherit global value)"]
@@ -1881,19 +1941,50 @@ async function runSettingsEditor(ctx) {
1881
1941
  const message = error instanceof Error ? error.message : String(error);
1882
1942
  ctx.ui.notify(`goosedump settings error: ${message}`, "error");
1883
1943
  }
1944
+ function clearSelectedSetting(key) {
1945
+ try {
1946
+ clearScopedGoosedumpSetting(location, "project", key);
1947
+ ctx.ui.notify(`${settingLabel(key)} inherits Global`, "info");
1948
+ finishEdit();
1949
+ } catch (error) {
1950
+ reportSettingsError(error);
1951
+ }
1952
+ }
1953
+ function toggleBooleanSetting(key) {
1954
+ if (!projectTrusted) {
1955
+ ctx.ui.notify("Project settings require a trusted project", "warning");
1956
+ return;
1957
+ }
1958
+ const current = projectSettings[key] ?? globalSettings[key];
1959
+ if (typeof current !== "boolean")
1960
+ return;
1961
+ const next = !current;
1962
+ try {
1963
+ writeScopedGoosedumpSetting(location, "project", key, next);
1964
+ ctx.ui.notify(`${settingLabel(key)} = ${next}`, "info");
1965
+ reloadSettings();
1966
+ tui.requestRender();
1967
+ } catch (error) {
1968
+ reportSettingsError(error);
1969
+ }
1970
+ }
1884
1971
  function openEditor() {
1885
- const spec = specs[selectedSetting];
1886
- if (!spec)
1972
+ const entry = specs[selectedSetting];
1973
+ if (!entry)
1887
1974
  return;
1888
1975
  if (!projectTrusted) {
1889
1976
  ctx.ui.notify("Project settings require a trusted project", "warning");
1890
1977
  return;
1891
1978
  }
1892
- const currentValue = projectSettings[spec.key] ?? null;
1893
- editor = new NumberInputSubmenu(settingLabel(spec.key), currentValue, (value) => {
1979
+ if (entry.spec.kind === "boolean") {
1980
+ toggleBooleanSetting(entry.key);
1981
+ return;
1982
+ }
1983
+ const currentValue = typeof projectSettings[entry.key] === "number" ? projectSettings[entry.key] : null;
1984
+ editor = new NumberInputSubmenu(settingLabel(entry.key), currentValue, (value) => {
1894
1985
  try {
1895
- writeScopedGoosedumpSetting(location, "project", spec.key, value);
1896
- ctx.ui.notify(`${settingLabel(spec.key)} = ${spec.key === "turnsBeforeCompact" ? formatCompactSetting(value) : value}`, "info");
1986
+ writeScopedGoosedumpSetting(location, "project", entry.key, value);
1987
+ ctx.ui.notify(`${settingLabel(entry.key)} = ${formatSettingDisplayValue(entry.key, value)}`, "info");
1897
1988
  finishEdit();
1898
1989
  } catch (error) {
1899
1990
  reportSettingsError(error);
@@ -1902,17 +1993,9 @@ async function runSettingsEditor(ctx) {
1902
1993
  editor = undefined;
1903
1994
  tui.requestRender();
1904
1995
  }, {
1905
- min: spec.min,
1906
- max: spec.max,
1907
- onClear: () => {
1908
- try {
1909
- clearScopedGoosedumpSetting(location, "project", spec.key);
1910
- ctx.ui.notify(`${settingLabel(spec.key)} inherits Global`, "info");
1911
- finishEdit();
1912
- } catch (error) {
1913
- reportSettingsError(error);
1914
- }
1915
- },
1996
+ min: entry.spec.min,
1997
+ max: entry.spec.max,
1998
+ onClear: () => clearSelectedSetting(entry.key),
1916
1999
  onInvalid: (message) => ctx.ui.notify(message, "error")
1917
2000
  });
1918
2001
  }
@@ -1936,17 +2019,17 @@ async function runSettingsEditor(ctx) {
1936
2019
  const descriptionWidth = Math.max(1, innerW - shortcutWidth - 4);
1937
2020
  content.push(dim(` ${pad("Shortcut", shortcutWidth)} Description`), ...helpRows.map(([shortcut, description]) => ` ${accent(pad(shortcut, shortcutWidth))} ${text(truncateToWidth2(description, descriptionWidth))}`));
1938
2021
  } else {
1939
- for (const [index, spec] of specs.entries()) {
2022
+ for (const [index, entry] of specs.entries()) {
1940
2023
  const selected = index === selectedSetting;
1941
- const inherited = projectSettings[spec.key] === undefined;
1942
- const value = projectSettings[spec.key] ?? globalSettings[spec.key];
1943
- const valueText = `[ ${value} ]`;
2024
+ const inherited = projectSettings[entry.key] === undefined;
2025
+ const value = projectSettings[entry.key] ?? globalSettings[entry.key];
2026
+ const valueText = `[ ${formatSettingDisplayValue(entry.key, value)} ]`;
1944
2027
  const renderedValue = inherited ? dim(valueText) : selected ? accent(valueText) : text(valueText);
1945
2028
  const cursor = selected ? accent("›") : " ";
1946
- content.push(`${cursor} ${renderedValue} ${text(spec.key)}`);
2029
+ content.push(`${cursor} ${renderedValue} ${text(entry.key)}`);
1947
2030
  }
1948
2031
  const effective = resolveGoosedumpSettings(location, projectTrusted);
1949
- content.push("", dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)} · summary=${effectiveSummaryMaxTokens(location, projectTrusted)} tokens`));
2032
+ content.push("", dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)} · summary=${effectiveSummaryMaxTokens(location, projectTrusted)} tokens · memory=${effective.enableMemory ? "on" : "off"}`));
1950
2033
  }
1951
2034
  return [
1952
2035
  overlayHeader(colors, width, "Goosedump Settings"),
@@ -1979,6 +2062,16 @@ async function runSettingsEditor(ctx) {
1979
2062
  selectedSetting = selectedSetting === 0 ? specs.length - 1 : Math.max(0, selectedSetting - 1);
1980
2063
  } else if (matchesKey(data, Key.down)) {
1981
2064
  selectedSetting = (selectedSetting + 1) % specs.length;
2065
+ } else if (matchesKey(data, Key.backspace) || matchesKey(data, Key.delete)) {
2066
+ const entry = specs[selectedSetting];
2067
+ if (!entry)
2068
+ return;
2069
+ if (!projectTrusted) {
2070
+ ctx.ui.notify("Project settings require a trusted project", "warning");
2071
+ return;
2072
+ }
2073
+ clearSelectedSetting(entry.key);
2074
+ return;
1982
2075
  } else if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
1983
2076
  openEditor();
1984
2077
  } else {
@@ -2426,6 +2519,8 @@ function createGoosedumpIntegration(options = {}) {
2426
2519
  const rememberQueue = [];
2427
2520
  let rememberRunning = null;
2428
2521
  function enqueueRemember(target, ctx, reportError) {
2522
+ if (!isMemoryEnabled(ctx.cwd, projectSettingsTrusted(ctx)))
2523
+ return;
2429
2524
  for (let i = rememberQueue.length - 1;i >= 0; i -= 1) {
2430
2525
  if (rememberQueue[i]?.target === target)
2431
2526
  rememberQueue.splice(i, 1);
@@ -2524,11 +2619,13 @@ ${renderToolRoutingPolicy()}`
2524
2619
  }
2525
2620
  });
2526
2621
  };
2527
- registerGooseCommand("goose-settings", "Edit goosedump compaction settings", async (_args, ctx) => {
2622
+ const openSettingsEditor = async (_args, ctx) => {
2528
2623
  await runSettingsEditor(ctx);
2529
2624
  compaction.syncCompactCounters(ctx);
2530
2625
  compaction.updateStatus(ctx);
2531
- });
2626
+ };
2627
+ registerGooseCommand("goose-settings", "Edit goosedump settings", openSettingsEditor);
2628
+ registerGooseCommand("goosedump-settings", "Edit goosedump settings", openSettingsEditor);
2532
2629
  registerGooseCommand("goose-compact", "Compact the current session now; optionally keep the last N user turns", async (args, ctx) => {
2533
2630
  let keep = null;
2534
2631
  if (args.trim()) {
@@ -2562,6 +2659,10 @@ ${renderToolRoutingPolicy()}`
2562
2659
  await runSessionsPanel(ctx, nativeExecutablePath);
2563
2660
  });
2564
2661
  registerGooseCommand("goose-forget", "Remove a durable memory entry or remembered session", async (args, ctx) => {
2662
+ if (!isMemoryEnabled(ctx.cwd, projectSettingsTrusted(ctx))) {
2663
+ ctx.ui.notify(MEMORY_DISABLED_MESSAGE, "warning");
2664
+ return;
2665
+ }
2565
2666
  const target = args.trim() || (await ctx.ui.input("Memory target/ID to forget:", ""))?.trim() || "";
2566
2667
  if (target.length === 0)
2567
2668
  return;
@@ -2569,6 +2670,10 @@ ${renderToolRoutingPolicy()}`
2569
2670
  ctx.ui.notify(res, "info");
2570
2671
  });
2571
2672
  registerGooseCommand("goose-memory-status", "Show durable memory storage health and type counts", async (_args, ctx) => {
2673
+ if (!isMemoryEnabled(ctx.cwd, projectSettingsTrusted(ctx))) {
2674
+ ctx.ui.notify(MEMORY_DISABLED_MESSAGE, "warning");
2675
+ return;
2676
+ }
2572
2677
  const res = await goosedumpMemoryStatus(undefined, nativeExecutablePath);
2573
2678
  ctx.ui.notify(res, "info");
2574
2679
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goosedump",
3
- "version": "0.12.45",
3
+ "version": "0.12.47",
4
4
  "description": "Pi extension for goosedump session search, durable memory, session management, and compaction",
5
5
  "keywords": [
6
6
  "compaction",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@earendil-works/pi-tui": "^0.82.1",
41
- "@jarkkojs/goosedump": "^0.12.45",
41
+ "@jarkkojs/goosedump": "^0.12.47",
42
42
  "@sinclair/typebox": "^0.34.49"
43
43
  },
44
44
  "devDependencies": {