threadkeeper 0.9.1__tar.gz → 0.9.2__tar.gz

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 (148) hide show
  1. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/PKG-INFO +15 -13
  2. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/README.md +14 -12
  3. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/pyproject.toml +10 -1
  4. threadkeeper-0.9.2/tests/test_menubar_app.py +69 -0
  5. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/Info.plist +25 -0
  6. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/README.md +65 -0
  7. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/ThreadKeeperAgentStatus.swift +482 -0
  8. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/build.sh +27 -0
  9. threadkeeper-0.9.2/threadkeeper/assets/macos-agent-status/install.sh +58 -0
  10. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/menubar_app.py +28 -2
  11. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper.egg-info/PKG-INFO +15 -13
  12. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper.egg-info/SOURCES.txt +6 -0
  13. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/LICENSE +0 -0
  14. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/setup.cfg +0 -0
  15. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_adapters.py +0 -0
  16. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_agent_status.py +0 -0
  17. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_brief_footprint.py +0 -0
  18. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_brief_sections.py +0 -0
  19. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_candidate_reviewer.py +0 -0
  20. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_config_settings.py +0 -0
  21. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_core_memory.py +0 -0
  22. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_curator.py +0 -0
  23. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_dashboard.py +0 -0
  24. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_delegated_search.py +0 -0
  25. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_dialectic.py +0 -0
  26. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_dialectic_feed_tools.py +0 -0
  27. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_dialectic_miner.py +0 -0
  28. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_dialectic_observation_resolve.py +0 -0
  29. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_dialectic_recompute.py +0 -0
  30. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_dialectic_tier.py +0 -0
  31. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_dialectic_validator.py +0 -0
  32. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_error_paths.py +0 -0
  33. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_evolve_applier.py +0 -0
  34. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_evolve_apply_2.py +0 -0
  35. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_evolve_apply_3.py +0 -0
  36. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_evolve_daemon.py +0 -0
  37. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_extract_daemon.py +0 -0
  38. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_extract_dedup.py +0 -0
  39. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_i18n_multilang.py +0 -0
  40. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_identity.py +0 -0
  41. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_ingest_status.py +0 -0
  42. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_lessons.py +0 -0
  43. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_memory_guard.py +0 -0
  44. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_missed_spawns.py +0 -0
  45. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_nudges.py +0 -0
  46. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_onnx_embeddings.py +0 -0
  47. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_panel.py +0 -0
  48. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_probe_daemon.py +0 -0
  49. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_process_health.py +0 -0
  50. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_search_fts_punctuation.py +0 -0
  51. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_shadow_review.py +0 -0
  52. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_skill_hint.py +0 -0
  53. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_skill_passive_tier.py +0 -0
  54. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_skill_tier.py +0 -0
  55. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_skill_use_parser.py +0 -0
  56. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_skill_watcher.py +0 -0
  57. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_skills.py +0 -0
  58. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_spawn_budget.py +0 -0
  59. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_spawn_codex_stdin.py +0 -0
  60. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_spawn_config.py +0 -0
  61. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_spawn_hint.py +0 -0
  62. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_spawn_reap.py +0 -0
  63. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_spawn_slim.py +0 -0
  64. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_spawn_wrap.py +0 -0
  65. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_thread_janitor.py +0 -0
  66. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_threads.py +0 -0
  67. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_tools_smoke.py +0 -0
  68. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_validate_threads.py +0 -0
  69. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/tests/test_vec_search.py +0 -0
  70. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/__init__.py +0 -0
  71. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/_mcp.py +0 -0
  72. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/_setup.py +0 -0
  73. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/_spawn_wrap.py +0 -0
  74. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/adapters/__init__.py +0 -0
  75. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/adapters/_hook_helpers.py +0 -0
  76. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/adapters/base.py +0 -0
  77. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/adapters/claude_code.py +0 -0
  78. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/adapters/claude_desktop.py +0 -0
  79. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/adapters/codex.py +0 -0
  80. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/adapters/copilot.py +0 -0
  81. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/adapters/gemini.py +0 -0
  82. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/adapters/vscode.py +0 -0
  83. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/agent_status.py +0 -0
  84. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/brief.py +0 -0
  85. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/candidate_reviewer.py +0 -0
  86. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/config.py +0 -0
  87. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/curator.py +0 -0
  88. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/db.py +0 -0
  89. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/dialectic_miner.py +0 -0
  90. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/dialectic_validator.py +0 -0
  91. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/embeddings.py +0 -0
  92. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/evolve_applier.py +0 -0
  93. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/evolve_daemon.py +0 -0
  94. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/extract_daemon.py +0 -0
  95. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/helpers.py +0 -0
  96. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/i18n.py +0 -0
  97. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/identity.py +0 -0
  98. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/ingest.py +0 -0
  99. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/lessons.py +0 -0
  100. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/memory_guard.py +0 -0
  101. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/migrate_embeddings.py +0 -0
  102. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/nudges.py +0 -0
  103. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/probe_daemon.py +0 -0
  104. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/process_health.py +0 -0
  105. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/review_prompts.py +0 -0
  106. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/search_proxy.py +0 -0
  107. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/server.py +0 -0
  108. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/shadow_review.py +0 -0
  109. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/skill_watcher.py +0 -0
  110. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/spawn_budget.py +0 -0
  111. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/spawn_config.py +0 -0
  112. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/thread_janitor.py +0 -0
  113. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/__init__.py +0 -0
  114. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/agent_status.py +0 -0
  115. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/candidate_reviewer.py +0 -0
  116. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/concepts.py +0 -0
  117. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/consolidate.py +0 -0
  118. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/core_memory.py +0 -0
  119. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/correlation.py +0 -0
  120. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/curator.py +0 -0
  121. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/dashboard.py +0 -0
  122. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/dialectic.py +0 -0
  123. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/dialectic_feed.py +0 -0
  124. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/dialog.py +0 -0
  125. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/distill.py +0 -0
  126. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/evolve_applier.py +0 -0
  127. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/extract.py +0 -0
  128. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/graph.py +0 -0
  129. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/invariants.py +0 -0
  130. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/lessons.py +0 -0
  131. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/memory_guard.py +0 -0
  132. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/missed_spawns.py +0 -0
  133. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/panel.py +0 -0
  134. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/peers.py +0 -0
  135. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/pickup.py +0 -0
  136. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/probes.py +0 -0
  137. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/process_health.py +0 -0
  138. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/session.py +0 -0
  139. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/shadow_review.py +0 -0
  140. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/skills.py +0 -0
  141. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/spawn.py +0 -0
  142. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/style.py +0 -0
  143. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/threads.py +0 -0
  144. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper/tools/validate.py +0 -0
  145. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper.egg-info/dependency_links.txt +0 -0
  146. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper.egg-info/entry_points.txt +0 -0
  147. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper.egg-info/requires.txt +0 -0
  148. {threadkeeper-0.9.1 → threadkeeper-0.9.2}/threadkeeper.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: threadkeeper
3
- Version: 0.9.1
3
+ Version: 0.9.2
4
4
  Summary: Multi-agent shared brain across Claude Code/Desktop, Codex, Gemini, Copilot, VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server.
5
5
  Author: thread-keeper contributors
6
6
  License: MIT
@@ -211,18 +211,20 @@ tk-agent-status --json
211
211
  `apps/macos-agent-status/` contains a small macOS menu-bar app that polls this
212
212
  command every 5 seconds and shows every autonomous learning loop: enabled/off,
213
213
  running/idle/ready, last pass, backlog, and active child RSS when that loop has
214
- spawned a worker. Active loops are sorted first (`running`, then `ready`), so
215
- background work stays at the top of the panel. The app also requests macOS
216
- notification permission and sends a notification when a newly completed
217
- autonomous child task produces a useful result in `recent_results`; the first
218
- poll only marks existing results as seen, so old completions do not spam
219
- notifications. Probe backlog is due objective probes only, not every registered
220
- probe, so a healthy cooldown shows `0 due probes` instead of looking stuck. On
221
- macOS, `python -m threadkeeper.server` automatically installs and launches it
222
- on MCP startup. Set `THREADKEEPER_MENUBAR_AUTO_LAUNCH=0` to disable that
223
- behavior.
224
-
225
- Manual fallback:
214
+ spawned a worker. PyPI wheels and sdists also bundle the same Swift source under
215
+ `threadkeeper/assets/macos-agent-status/`, so a normal `pipx`/`uv tool` install
216
+ does not need a git checkout for the widget to build. Active loops are sorted
217
+ first (`running`, then `ready`), so background work stays at the top of the
218
+ panel. The app also requests macOS notification permission and sends a
219
+ notification when a newly completed autonomous child task produces a useful
220
+ result in `recent_results`; the first poll only marks existing results as seen,
221
+ so old completions do not spam notifications. Probe backlog is due objective
222
+ probes only, not every registered probe, so a healthy cooldown shows `0 due
223
+ probes` instead of looking stuck. On macOS, `python -m threadkeeper.server`
224
+ automatically installs and launches it on MCP startup. Set
225
+ `THREADKEEPER_MENUBAR_AUTO_LAUNCH=0` to disable that behavior.
226
+
227
+ Manual fallback from a source checkout:
226
228
 
227
229
  ```sh
228
230
  cd apps/macos-agent-status
@@ -170,18 +170,20 @@ tk-agent-status --json
170
170
  `apps/macos-agent-status/` contains a small macOS menu-bar app that polls this
171
171
  command every 5 seconds and shows every autonomous learning loop: enabled/off,
172
172
  running/idle/ready, last pass, backlog, and active child RSS when that loop has
173
- spawned a worker. Active loops are sorted first (`running`, then `ready`), so
174
- background work stays at the top of the panel. The app also requests macOS
175
- notification permission and sends a notification when a newly completed
176
- autonomous child task produces a useful result in `recent_results`; the first
177
- poll only marks existing results as seen, so old completions do not spam
178
- notifications. Probe backlog is due objective probes only, not every registered
179
- probe, so a healthy cooldown shows `0 due probes` instead of looking stuck. On
180
- macOS, `python -m threadkeeper.server` automatically installs and launches it
181
- on MCP startup. Set `THREADKEEPER_MENUBAR_AUTO_LAUNCH=0` to disable that
182
- behavior.
183
-
184
- Manual fallback:
173
+ spawned a worker. PyPI wheels and sdists also bundle the same Swift source under
174
+ `threadkeeper/assets/macos-agent-status/`, so a normal `pipx`/`uv tool` install
175
+ does not need a git checkout for the widget to build. Active loops are sorted
176
+ first (`running`, then `ready`), so background work stays at the top of the
177
+ panel. The app also requests macOS notification permission and sends a
178
+ notification when a newly completed autonomous child task produces a useful
179
+ result in `recent_results`; the first poll only marks existing results as seen,
180
+ so old completions do not spam notifications. Probe backlog is due objective
181
+ probes only, not every registered probe, so a healthy cooldown shows `0 due
182
+ probes` instead of looking stuck. On macOS, `python -m threadkeeper.server`
183
+ automatically installs and launches it on MCP startup. Set
184
+ `THREADKEEPER_MENUBAR_AUTO_LAUNCH=0` to disable that behavior.
185
+
186
+ Manual fallback from a source checkout:
185
187
 
186
188
  ```sh
187
189
  cd apps/macos-agent-status
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "threadkeeper"
7
- version = "0.9.1"
7
+ version = "0.9.2"
8
8
  description = "Multi-agent shared brain across Claude Code/Desktop, Codex, Gemini, Copilot, VS Code. Cross-session memory, self-improving skill loops, inter-agent signaling — one local MCP server."
9
9
  requires-python = ">=3.11"
10
10
  authors = [{ name = "thread-keeper contributors" }]
@@ -78,6 +78,15 @@ tk-agent-status = "threadkeeper.agent_status:main"
78
78
  include = ["threadkeeper*"]
79
79
  exclude = ["tests*", "scripts*"]
80
80
 
81
+ [tool.setuptools.package-data]
82
+ threadkeeper = [
83
+ "assets/macos-agent-status/Info.plist",
84
+ "assets/macos-agent-status/README.md",
85
+ "assets/macos-agent-status/ThreadKeeperAgentStatus.swift",
86
+ "assets/macos-agent-status/build.sh",
87
+ "assets/macos-agent-status/install.sh",
88
+ ]
89
+
81
90
  [tool.pytest.ini_options]
82
91
  testpaths = ["tests"]
83
92
  addopts = "-q --strict-markers"
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ from types import SimpleNamespace
5
+
6
+
7
+ def test_menubar_packaged_assets_are_available(fresh_mp):
8
+ import threadkeeper.menubar_app as menubar_app
9
+
10
+ src = menubar_app._package_source_dir()
11
+
12
+ assert src.name == "macos-agent-status"
13
+ assert (src / "ThreadKeeperAgentStatus.swift").exists()
14
+ assert (src / "Info.plist").exists()
15
+ assert (src / "build.sh").exists()
16
+
17
+
18
+ def test_menubar_source_falls_back_to_packaged_assets(fresh_mp, tmp_path, monkeypatch):
19
+ import threadkeeper.menubar_app as menubar_app
20
+
21
+ monkeypatch.setattr(menubar_app, "_dev_source_dir", lambda: tmp_path / "missing")
22
+
23
+ assert menubar_app._source_dir() == menubar_app._package_source_dir()
24
+
25
+
26
+ def test_install_app_builds_from_task_log_scratch_without_executable_bit(
27
+ fresh_mp,
28
+ tmp_path,
29
+ monkeypatch,
30
+ ):
31
+ import threadkeeper.menubar_app as menubar_app
32
+
33
+ src = tmp_path / "source"
34
+ src.mkdir()
35
+ (src / "ThreadKeeperAgentStatus.swift").write_text("// swift\n", encoding="utf-8")
36
+ (src / "Info.plist").write_text("<plist></plist>\n", encoding="utf-8")
37
+ build = src / "build.sh"
38
+ build.write_text("#!/usr/bin/env bash\n", encoding="utf-8")
39
+ build.chmod(0o644)
40
+
41
+ task_logs = tmp_path / "tasks"
42
+ monkeypatch.setattr(menubar_app, "TASK_LOG_DIR", task_logs)
43
+ monkeypatch.setattr(menubar_app, "_app_is_current", lambda src, app: False)
44
+ calls = []
45
+
46
+ def fake_run(args, timeout=60, cwd=None):
47
+ calls.append((args, timeout, Path(cwd)))
48
+ app_dir = Path(cwd) / "build" / menubar_app.APP_BUNDLE
49
+ bin_dir = app_dir / "Contents" / "MacOS"
50
+ bin_dir.mkdir(parents=True)
51
+ (app_dir / "Contents" / "Info.plist").write_text(
52
+ "<plist></plist>\n",
53
+ encoding="utf-8",
54
+ )
55
+ (bin_dir / menubar_app.APP_NAME).write_text("binary\n", encoding="utf-8")
56
+ return SimpleNamespace(returncode=0, stdout=f"{app_dir}\n")
57
+
58
+ monkeypatch.setattr(menubar_app, "_run", fake_run)
59
+
60
+ installed = tmp_path / "Applications" / menubar_app.APP_BUNDLE
61
+ assert menubar_app._install_app(src, installed) is True
62
+
63
+ assert calls
64
+ assert calls[0][0][0] == "/bin/bash"
65
+ assert calls[0][0][1] == str(task_logs / "menubar-build" / "source" / "build.sh")
66
+ assert calls[0][2] == task_logs / "menubar-build" / "source"
67
+ assert (installed / "Contents" / "Info.plist").exists()
68
+ assert (installed / "Contents" / "MacOS" / menubar_app.APP_NAME).exists()
69
+ assert not (src / "build").exists()
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
3
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4
+ <plist version="1.0">
5
+ <dict>
6
+ <key>CFBundleExecutable</key>
7
+ <string>ThreadKeeperAgentStatus</string>
8
+ <key>CFBundleIdentifier</key>
9
+ <string>local.threadkeeper.agent-status</string>
10
+ <key>CFBundleName</key>
11
+ <string>ThreadKeeperAgentStatus</string>
12
+ <key>CFBundlePackageType</key>
13
+ <string>APPL</string>
14
+ <key>CFBundleShortVersionString</key>
15
+ <string>0.1.0</string>
16
+ <key>CFBundleVersion</key>
17
+ <string>1</string>
18
+ <key>LSMinimumSystemVersion</key>
19
+ <string>13.0</string>
20
+ <key>LSUIElement</key>
21
+ <true/>
22
+ <key>NSHighResolutionCapable</key>
23
+ <true/>
24
+ </dict>
25
+ </plist>
@@ -0,0 +1,65 @@
1
+ # ThreadKeeper Agent Status
2
+
3
+ Small macOS menu-bar app for live thread-keeper autonomous learning loop status.
4
+
5
+ It polls `tk-agent-status --json` every 5 seconds and shows:
6
+
7
+ - running/enabled loop count in the menu bar,
8
+ - every autonomous learning loop,
9
+ - a stable role description for what each loop/agent is responsible for,
10
+ - running / idle / ready / off state,
11
+ - active loops first (`running`, then `ready`),
12
+ - last pass summary,
13
+ - backlog count,
14
+ - active spawned-child RSS when a loop has a worker running,
15
+ - macOS notifications for newly completed autonomous child tasks that produced
16
+ a useful result.
17
+
18
+ The first poll primes the seen-result list, so the app does not notify for old
19
+ completed tasks that existed before it started.
20
+
21
+ ## Automatic startup
22
+
23
+ On macOS, `python -m threadkeeper.server` installs and launches this app
24
+ automatically when the MCP server starts. The startup hook is idempotent: it
25
+ rebuilds only when the installed app is missing or older than the source,
26
+ registers the LaunchAgent, and opens the app if it is not already running.
27
+
28
+ Disable automatic startup with:
29
+
30
+ ```sh
31
+ THREADKEEPER_MENUBAR_AUTO_LAUNCH=0
32
+ ```
33
+
34
+ ## Build
35
+
36
+ ```sh
37
+ ./build.sh
38
+ open build/ThreadKeeperAgentStatus.app
39
+ ```
40
+
41
+ ## Install at login
42
+
43
+ ```sh
44
+ ./install.sh
45
+ ```
46
+
47
+ The app is installed to `~/Applications/ThreadKeeperAgentStatus.app` and a
48
+ LaunchAgent is registered at
49
+ `~/Library/LaunchAgents/local.threadkeeper.agent-status.plist`.
50
+
51
+ If `tk-agent-status` is not already installed on PATH, `install.sh` creates a
52
+ small fallback wrapper at `~/.local/bin/tk-agent-status` that runs the local
53
+ repo module through `.venv/bin/python`.
54
+
55
+ ## Command lookup
56
+
57
+ The app looks for `tk-agent-status` in:
58
+
59
+ - `/opt/homebrew/bin/tk-agent-status`
60
+ - `/usr/local/bin/tk-agent-status`
61
+ - `~/.local/bin/tk-agent-status`
62
+ - `PATH` via `/usr/bin/env`
63
+
64
+ Set `THREADKEEPER_AGENT_STATUS_COMMAND` when launching the app if the command
65
+ lives somewhere else.