lazycodex-ai 5.0.1 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.ja.md +2 -1
  2. package/README.ko.md +2 -1
  3. package/README.md +2 -1
  4. package/README.ru.md +2 -1
  5. package/README.zh-cn.md +2 -1
  6. package/dist/cli/index.js +241 -144
  7. package/dist/cli-node/index.js +241 -144
  8. package/package.json +6 -1
  9. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  10. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +26 -3
  11. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  12. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  13. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  14. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  15. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  16. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  17. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  18. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  19. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  20. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  21. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  22. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  23. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  25. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  27. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  29. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/ulw-execute-continuation/AGENTS.md +1 -1
  31. package/packages/omo-codex/plugin/components/ulw-execute-continuation/dist/cli.js +42 -32
  32. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  33. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/ulw-execute-continuation/src/plan-checklist.ts +55 -29
  35. package/packages/omo-codex/plugin/components/ulw-execute-continuation/test/boulder-reader.test.ts +51 -0
  36. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  37. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  38. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  39. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  40. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  41. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  42. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  43. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  44. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  45. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  46. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  47. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  48. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  49. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  50. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  51. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  52. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  53. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  54. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  55. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  56. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  57. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  58. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  59. package/packages/omo-codex/plugin/package-lock.json +12 -12
  60. package/packages/omo-codex/plugin/package.json +1 -1
  61. package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +1 -1
  62. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +1 -1
  63. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/aside_scanner.py +3 -0
  64. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +4 -0
  65. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/file_scanners.py +6 -2
  66. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +7 -1
  67. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/transcript.py +3 -1
  68. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/types.py +3 -1
  69. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +4 -0
  70. package/packages/omo-codex/scripts/install-dist/install-local.mjs +37 -14
  71. package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +1 -1
  72. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +1 -1
  73. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/aside_scanner.py +3 -0
  74. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +4 -0
  75. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/file_scanners.py +6 -2
  76. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +7 -1
  77. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/transcript.py +3 -1
  78. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/types.py +3 -1
  79. package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +4 -0
@@ -226,6 +226,10 @@ def _match_reasons(item: Session, query: str) -> list[JsonMap]:
226
226
  for field, value in _search_fields(item):
227
227
  if needle in value.lower():
228
228
  reasons.append({"query": query, "platform": item.platform, "field": field, "snippet": _snippet(value, needle)})
229
+ if not reasons:
230
+ prompt = next((message for message in item.user_messages or () if needle in message.lower()), "")
231
+ if prompt:
232
+ reasons.append({"query": query, "platform": item.platform, "field": "user_message", "snippet": _snippet(prompt, needle)})
229
233
  return reasons
230
234
 
231
235
 
@@ -80,6 +80,7 @@ def scan_aider(extra_roots: tuple[Path, ...], workers: int) -> list[Session]:
80
80
  def _droid_session(path: Path) -> Session:
81
81
  sid = path.stem
82
82
  cwd = model = first_user = last_user = None
83
+ prompts: list[str] = []
83
84
  created = updated = None
84
85
  settings = as_map(read_json(path.with_suffix(".settings.json"))) or {}
85
86
  for data in iter_jsonl(path):
@@ -94,7 +95,8 @@ def _droid_session(path: Path) -> Session:
94
95
  if prompt:
95
96
  first_user = first_user or prompt
96
97
  last_user = prompt
97
- return Session("droid", sid, str(path), cwd, created or file_time(path), updated or created or file_time(path), None, text(settings.get("model")) or model, first_user or "", _usage(settings), last_user_message=last_user or "")
98
+ prompts.append(prompt)
99
+ return Session("droid", sid, str(path), cwd, created or file_time(path), updated or created or file_time(path), None, text(settings.get("model")) or model, first_user or "", _usage(settings), last_user_message=last_user or "", user_messages=tuple(prompts))
98
100
 
99
101
 
100
102
  def _amp_sessions(path: Path) -> list[Session]:
@@ -136,6 +138,7 @@ def _gemini_sessions(path: Path) -> list[Session]:
136
138
 
137
139
  def _kimi_session(path: Path) -> Session:
138
140
  first_user = last_user = ""
141
+ prompts: list[str] = []
139
142
  created = updated = None
140
143
  for data in iter_jsonl(path):
141
144
  stamp = data.get("timestamp")
@@ -146,7 +149,8 @@ def _kimi_session(path: Path) -> Session:
146
149
  if prompt:
147
150
  first_user = first_user or prompt
148
151
  last_user = prompt
149
- return Session("kimi", path.parent.name, str(path), None, created or file_time(path), updated or file_time(path), "moonshot", None, first_user, {}, last_user_message=last_user)
152
+ prompts.append(prompt)
153
+ return Session("kimi", path.parent.name, str(path), None, created or file_time(path), updated or file_time(path), "moonshot", None, first_user, {}, last_user_message=last_user, user_messages=tuple(prompts))
150
154
 
151
155
 
152
156
  def _codebuff_sessions(path: Path) -> list[Session]:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from dataclasses import replace
4
5
  from pathlib import Path
5
6
  from typing import Callable, TypeAlias
6
7
 
@@ -95,8 +96,13 @@ def _dedupe(sessions: list[Session]) -> list[Session]:
95
96
  for session in sessions:
96
97
  key = (session.platform, session.id)
97
98
  current = found.get(key)
98
- if current is None or _linkage_score(session) > _linkage_score(current):
99
+ if current is None:
99
100
  found[key] = session
101
+ continue
102
+ winner, twin = (session, current) if _linkage_score(session) > _linkage_score(current) else (current, session)
103
+ if winner.user_messages is None and twin.user_messages is not None:
104
+ winner = replace(winner, user_messages=twin.user_messages)
105
+ found[key] = winner
100
106
  return list(found.values())
101
107
 
102
108
 
@@ -39,6 +39,7 @@ def jsonl_session(platform: str, path: Path, fallback_id: str) -> Session:
39
39
  sid = fallback_id
40
40
  cwd = provider = model = first_user = parent = agent = None
41
41
  last_user = ""
42
+ prompts: list[str] = []
42
43
  created = updated = None
43
44
  usage: JsonMap = {}
44
45
  for data in iter_jsonl(path):
@@ -65,8 +66,9 @@ def jsonl_session(platform: str, path: Path, fallback_id: str) -> Session:
65
66
  if prompt:
66
67
  first_user = first_user or prompt
67
68
  last_user = prompt
69
+ prompts.append(prompt)
68
70
  merge_usage(usage, as_map(message.get("usage")) or as_map(data.get("usage")))
69
- return Session(platform, sid, str(path), cwd, created or file_time(path), updated or created or file_time(path), provider, model, first_user or "", usage, parent, agent, last_user)
71
+ return Session(platform, sid, str(path), cwd, created or file_time(path), updated or created or file_time(path), provider, model, first_user or "", usage, parent, agent, last_user, tuple(prompts))
70
72
 
71
73
 
72
74
  def spawn_info(source: Json | None) -> tuple[str | None, str | None]:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass
3
+ from dataclasses import dataclass, field
4
4
  from pathlib import Path
5
5
  from typing import TypeAlias
6
6
 
@@ -38,6 +38,8 @@ class Session:
38
38
  parent_id: str | None = None
39
39
  agent: str | None = None
40
40
  last_user_message: str = ""
41
+ # Every user prompt the scanner already parsed, kept for search only; None when the scanner did not read the whole transcript.
42
+ user_messages: tuple[str, ...] | None = field(default=None, compare=False, repr=False)
41
43
 
42
44
  def to_json(self) -> JsonMap:
43
45
  return {
@@ -16,4 +16,8 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
16
16
 
17
17
 
18
18
  if __name__ == "__main__":
19
+ if sys.version_info < (3, 11):
20
+ found = ".".join(str(part) for part in sys.version_info[:3])
21
+ _ = sys.stderr.write(f"find-agent-sessions requires Python 3.11 or newer (found {found}); run it with python3.11+ or `uv run`.\n")
22
+ raise SystemExit(2)
19
23
  _ = runpy.run_module("agent_sessions.cli", run_name="__main__")
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // omo-codex-install:b2e4c7f3670cc0381ff35cedeeb6fc7400c2b8ec9c7935a6a3c842ec4e0a959e:b6d1361ea554e4356a111e565ac8f74e64cc03e66f920a9d7f9dbcc5f87829bf
2
+ // omo-codex-install:b9d191455f51ef378c5201a64f2f635add28e3674b310a6c023afe2078a8d7ae:c91b7a106107d893ab54d3e73fd8f13083a16bbdb540770fe430c20aae9a113c
3
3
  var __esm = (fn, res, err) => () => {
4
4
  if (fn)
5
5
  try {
@@ -6394,7 +6394,7 @@ class SyncSpanContextManager {
6394
6394
  var init_context = () => {};
6395
6395
 
6396
6396
  // node_modules/.bun/@posthog+core@1.55.0/node_modules/@posthog/core/dist/traces/config.mjs
6397
- function positiveInteger(value, fallback) {
6397
+ function positiveInteger2(value, fallback) {
6398
6398
  return typeof value == "number" && Number.isInteger(value) && value >= 1 ? value : fallback;
6399
6399
  }
6400
6400
  function withUsableIdentityKeys(attributes) {
@@ -6429,22 +6429,22 @@ function resolveTracesConfig(config, hostResourceAttributes, logger) {
6429
6429
  const resourceAttributes = assignUserAttributes({
6430
6430
  ...hostResourceAttributes
6431
6431
  }, withUsableIdentityKeys(config?.resourceAttributes));
6432
- const maxExportBatchSize = positiveInteger(config?.maxExportBatchSize, DEFAULT_MAX_EXPORT_BATCH_SIZE);
6432
+ const maxExportBatchSize = positiveInteger2(config?.maxExportBatchSize, DEFAULT_MAX_EXPORT_BATCH_SIZE);
6433
6433
  return {
6434
6434
  serviceName: resourceAttributes?.["service.name"] ?? config?.serviceName,
6435
6435
  serviceVersion: resourceAttributes?.["service.version"] ?? config?.serviceVersion,
6436
6436
  environment: resourceAttributes?.["deployment.environment"] ?? config?.environment,
6437
6437
  resourceAttributes,
6438
6438
  beforeSpanSend: resolveBeforeSpanSend(config?.beforeSpanSend, logger),
6439
- maxAttributesPerSpan: positiveInteger(config?.maxAttributesPerSpan, DEFAULT_MAX_ATTRIBUTES_PER_SPAN),
6440
- maxEventsPerSpan: positiveInteger(config?.maxEventsPerSpan, DEFAULT_MAX_EVENTS_PER_SPAN),
6439
+ maxAttributesPerSpan: positiveInteger2(config?.maxAttributesPerSpan, DEFAULT_MAX_ATTRIBUTES_PER_SPAN),
6440
+ maxEventsPerSpan: positiveInteger2(config?.maxEventsPerSpan, DEFAULT_MAX_EVENTS_PER_SPAN),
6441
6441
  maxAttributesPerEvent: DEFAULT_MAX_ATTRIBUTES_PER_EVENT,
6442
- maxAttributeValueLength: positiveInteger(config?.maxAttributeValueLength, DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH),
6443
- flushIntervalMs: positiveInteger(config?.flushIntervalMs, DEFAULT_FLUSH_INTERVAL_MS2),
6442
+ maxAttributeValueLength: positiveInteger2(config?.maxAttributeValueLength, DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH),
6443
+ flushIntervalMs: positiveInteger2(config?.flushIntervalMs, DEFAULT_FLUSH_INTERVAL_MS2),
6444
6444
  maxExportBatchSize,
6445
- maxQueueSize: Math.max(positiveInteger(config?.maxQueueSize, DEFAULT_MAX_QUEUE_SIZE), maxExportBatchSize),
6446
- maxLiveSpans: positiveInteger(config?.maxLiveSpans, DEFAULT_MAX_LIVE_SPANS),
6447
- maxSpanAgeMs: positiveInteger(config?.maxSpanAgeMs, DEFAULT_MAX_SPAN_AGE_MS)
6445
+ maxQueueSize: Math.max(positiveInteger2(config?.maxQueueSize, DEFAULT_MAX_QUEUE_SIZE), maxExportBatchSize),
6446
+ maxLiveSpans: positiveInteger2(config?.maxLiveSpans, DEFAULT_MAX_LIVE_SPANS),
6447
+ maxSpanAgeMs: positiveInteger2(config?.maxSpanAgeMs, DEFAULT_MAX_SPAN_AGE_MS)
6448
6448
  };
6449
6449
  }
6450
6450
  var DEFAULT_FLUSH_INTERVAL_MS2 = 5000, DEFAULT_MAX_EXPORT_BATCH_SIZE = 512, DEFAULT_MAX_QUEUE_SIZE = 2048, DEFAULT_MAX_ATTRIBUTES_PER_SPAN = 128, DEFAULT_MAX_EVENTS_PER_SPAN = 128, DEFAULT_MAX_ATTRIBUTES_PER_EVENT = 128, DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH = 8192, DEFAULT_MAX_LIVE_SPANS = 1e4, DEFAULT_MAX_SPAN_AGE_MS = 3600000, IDENTITY_KEYS;
@@ -9984,7 +9984,7 @@ var package_default;
9984
9984
  var init_package = __esm(() => {
9985
9985
  package_default = {
9986
9986
  name: "@oh-my-opencode/omo-codex",
9987
- version: "5.0.1",
9987
+ version: "5.1.0",
9988
9988
  type: "module",
9989
9989
  private: true,
9990
9990
  description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
@@ -16502,6 +16502,25 @@ var OmoCategoryConfigObjectSchema = object({
16502
16502
  var OmoCategoryConfigSchema = preprocess((value) => isRecord3(value) ? normalizeLegacyModelFields(value) : value, OmoCategoryConfigObjectSchema);
16503
16503
  var OmoCategoriesConfigSchema = record(string2(), OmoCategoryConfigSchema);
16504
16504
 
16505
+ // packages/omo-config-core/src/schema/computer.ts
16506
+ var positiveInteger = number2().int().positive();
16507
+ var nonNegativeInteger = number2().int().nonnegative();
16508
+ var OmoComputerSettingsLayerSchema = object({
16509
+ enabled: boolean2().describe("Experimental: register the computer tool in OmO Native sessions (default: on where the host is supported; false leaves it unregistered)"),
16510
+ display: string2().min(1),
16511
+ max_width: positiveInteger,
16512
+ max_height: positiveInteger,
16513
+ screenshot_max_bytes: positiveInteger,
16514
+ stop_hotkey: string2().min(1),
16515
+ allow_host_relay_only_stop: boolean2(),
16516
+ macos_canary: _enum(["session", "off"]),
16517
+ audit_log: object({ enabled: boolean2() }).partial().strict(),
16518
+ screenshot_gc: object({ enabled: boolean2(), stale_ms: nonNegativeInteger, scan_interval_ms: nonNegativeInteger }).partial().strict(),
16519
+ engine_path: string2().min(1),
16520
+ cua_adapter: boolean2()
16521
+ }).partial().strict().describe("Experimental computer use in OmO Native: screenshots, windows, accessibility trees and native mouse and keyboard input. Every key is optional; defaults depend on the host.");
16522
+ var OmoComputerSettingsSchema = OmoComputerSettingsLayerSchema;
16523
+
16505
16524
  // packages/omo-config-core/src/schema/git-master.ts
16506
16525
  var OmoGitMasterSettingsShape = {
16507
16526
  commit_footer: union([boolean2(), string2()]),
@@ -16747,7 +16766,7 @@ var OmoModelProfilesLayerSchema = record(string2(), OmoModelProfileLayerSchema);
16747
16766
 
16748
16767
  // packages/omo-config-core/src/schema/task.ts
16749
16768
  import { availableParallelism } from "node:os";
16750
- var DEFAULT_RESIDENCY_MAX_CHILDREN = 16;
16769
+ var DEFAULT_RESIDENCY_MAX_CHILDREN = "unlimited";
16751
16770
  var ResidencyMaxChildrenInputSchema = union([number2().int().nonnegative(), literal("unlimited")]);
16752
16771
  var OmoTaskWaitSchema = object({
16753
16772
  min_ms: number2().int().positive().default(5000),
@@ -16808,7 +16827,7 @@ var OmoTaskSettingsSchema = object({
16808
16827
  provider_concurrency: record(string2(), number2().int().nonnegative()).optional(),
16809
16828
  model_concurrency: record(string2(), number2().int().nonnegative()).optional(),
16810
16829
  max_depth: number2().int().nonnegative().default(1),
16811
- residency_max_children: ResidencyMaxChildrenInputSchema.default(8),
16830
+ residency_max_children: ResidencyMaxChildrenInputSchema.default(DEFAULT_RESIDENCY_MAX_CHILDREN),
16812
16831
  resident_idle_timeout_ms: number2().int().positive().max(Number.MAX_SAFE_INTEGER).default(900000),
16813
16832
  ttl_ms: number2().int().positive().default(86400000),
16814
16833
  state_dir: string2().optional(),
@@ -16872,7 +16891,7 @@ function resolveOmoTaskSettings(input, resolveParallelism = availableParallelism
16872
16891
  const record2 = record(string2(), unknown()).parse(input);
16873
16892
  return OmoTaskSettingsSchema.parse({
16874
16893
  ...record2,
16875
- residency_max_children: record2["residency_max_children"] ?? Math.min(DEFAULT_RESIDENCY_MAX_CHILDREN, Math.max(8, resolveParallelism() * 2)),
16894
+ residency_max_children: record2["residency_max_children"] ?? DEFAULT_RESIDENCY_MAX_CHILDREN,
16876
16895
  global_concurrency: record2["global_concurrency"] ?? Math.max(8, resolveParallelism() * 2)
16877
16896
  });
16878
16897
  }
@@ -16963,6 +16982,7 @@ var OmoTypedHarnessConfigSchema = object({
16963
16982
  model_profile: string2().optional(),
16964
16983
  memory: OmoMemorySettingsLayerSchema.optional(),
16965
16984
  telemetry: OmoTelemetrySettingsLayerSchema.optional(),
16985
+ computer: OmoComputerSettingsLayerSchema.optional(),
16966
16986
  disabled_skills: OmoDisabledSkillsSchema.optional()
16967
16987
  }).strict();
16968
16988
  var OmoConfigProfileSchema = object({
@@ -16977,6 +16997,7 @@ var OmoConfigProfileSchema = object({
16977
16997
  model_profile: string2().optional(),
16978
16998
  memory: OmoMemorySettingsLayerSchema.optional(),
16979
16999
  telemetry: OmoTelemetrySettingsLayerSchema.optional(),
17000
+ computer: OmoComputerSettingsLayerSchema.optional(),
16980
17001
  disabled_skills: OmoDisabledSkillsSchema.optional(),
16981
17002
  "[opencode]": OmoOpenCodeHarnessConfigSchema.optional(),
16982
17003
  "[native]": OmoTypedHarnessConfigSchema.optional(),
@@ -16996,6 +17017,7 @@ var OmoConfigSchema = object({
16996
17017
  model_profile: string2().optional(),
16997
17018
  memory: OmoMemorySettingsSchema.optional(),
16998
17019
  telemetry: OmoTelemetrySettingsSchema.optional(),
17020
+ computer: OmoComputerSettingsSchema.optional(),
16999
17021
  disabled_skills: OmoDisabledSkillsSchema.optional(),
17000
17022
  "[opencode]": OmoOpenCodeHarnessConfigSchema.optional(),
17001
17023
  "[native]": OmoTypedHarnessConfigSchema.optional(),
@@ -17018,6 +17040,7 @@ var OmoConfigLayerSchema = object({
17018
17040
  model_profile: string2().optional(),
17019
17041
  memory: OmoMemorySettingsLayerSchema.optional(),
17020
17042
  telemetry: OmoTelemetrySettingsLayerSchema.optional(),
17043
+ computer: OmoComputerSettingsLayerSchema.optional(),
17021
17044
  disabled_skills: OmoDisabledSkillsSchema.optional(),
17022
17045
  "[opencode]": OmoOpenCodeHarnessConfigSchema.optional(),
17023
17046
  "[native]": OmoTypedHarnessConfigSchema.optional(),
@@ -5,5 +5,5 @@
5
5
  "index.js": "b6b064fab277031c6c51f5a58b84b31040f9960bf767bf646adc0540c1d25c1b",
6
6
  "page-bundle.js": "bfd51e8383cf47200d57387bbbed258bce5a3a3d00c38fc108034d2888e23f0c"
7
7
  },
8
- "stagedAtUtc": "2026-09-27T06:06:13.416Z"
8
+ "stagedAtUtc": "2026-09-28T19:17:28.100Z"
9
9
  }
@@ -66,7 +66,7 @@ The finder prints JSON for stdout and `jq`. Every result includes:
66
66
  | `detail_hint` | Ready-to-run `read` command for detailed inspection |
67
67
  | `match_reasons` | Search-only array explaining which field/content matched each query |
68
68
 
69
- Each `match_reasons` entry includes `query`, `platform`, `field`, and `snippet`, so you can tell which platform matched and what content caused the hit without opening every transcript.
69
+ Each `match_reasons` entry includes `query`, `platform`, `field`, and `snippet`, so you can tell which platform matched and what content caused the hit without opening every transcript. `field` is `user_message` when the query matched a user prompt that the `first_user_message`/`last_user_message` previews do not cover, such as a prompt in the middle of the session.
70
70
 
71
71
  ## Filters
72
72
 
@@ -93,6 +93,7 @@ def _aside_session(path: Path, index: dict[str, _StateRow]) -> Session:
93
93
  sid = _dir_session_id(path.parent.name)
94
94
  row = index.get(sid)
95
95
  first_user = last_user = ""
96
+ prompts: list[str] = []
96
97
  provider = model = None
97
98
  created = updated = None
98
99
  usage: JsonMap = {}
@@ -107,6 +108,7 @@ def _aside_session(path: Path, index: dict[str, _StateRow]) -> Session:
107
108
  if prompt:
108
109
  first_user = first_user or prompt
109
110
  last_user = prompt
111
+ prompts.append(prompt)
110
112
  elif role == "assistant":
111
113
  provider = provider or text(data.get("provider"))
112
114
  model = model or text(data.get("model"))
@@ -125,6 +127,7 @@ def _aside_session(path: Path, index: dict[str, _StateRow]) -> Session:
125
127
  row.parent_id if row is not None else None,
126
128
  row.title if row is not None and row.parent_id is not None else None,
127
129
  last_user,
130
+ tuple(prompts),
128
131
  )
129
132
 
130
133
 
@@ -226,6 +226,10 @@ def _match_reasons(item: Session, query: str) -> list[JsonMap]:
226
226
  for field, value in _search_fields(item):
227
227
  if needle in value.lower():
228
228
  reasons.append({"query": query, "platform": item.platform, "field": field, "snippet": _snippet(value, needle)})
229
+ if not reasons:
230
+ prompt = next((message for message in item.user_messages or () if needle in message.lower()), "")
231
+ if prompt:
232
+ reasons.append({"query": query, "platform": item.platform, "field": "user_message", "snippet": _snippet(prompt, needle)})
229
233
  return reasons
230
234
 
231
235
 
@@ -80,6 +80,7 @@ def scan_aider(extra_roots: tuple[Path, ...], workers: int) -> list[Session]:
80
80
  def _droid_session(path: Path) -> Session:
81
81
  sid = path.stem
82
82
  cwd = model = first_user = last_user = None
83
+ prompts: list[str] = []
83
84
  created = updated = None
84
85
  settings = as_map(read_json(path.with_suffix(".settings.json"))) or {}
85
86
  for data in iter_jsonl(path):
@@ -94,7 +95,8 @@ def _droid_session(path: Path) -> Session:
94
95
  if prompt:
95
96
  first_user = first_user or prompt
96
97
  last_user = prompt
97
- return Session("droid", sid, str(path), cwd, created or file_time(path), updated or created or file_time(path), None, text(settings.get("model")) or model, first_user or "", _usage(settings), last_user_message=last_user or "")
98
+ prompts.append(prompt)
99
+ return Session("droid", sid, str(path), cwd, created or file_time(path), updated or created or file_time(path), None, text(settings.get("model")) or model, first_user or "", _usage(settings), last_user_message=last_user or "", user_messages=tuple(prompts))
98
100
 
99
101
 
100
102
  def _amp_sessions(path: Path) -> list[Session]:
@@ -136,6 +138,7 @@ def _gemini_sessions(path: Path) -> list[Session]:
136
138
 
137
139
  def _kimi_session(path: Path) -> Session:
138
140
  first_user = last_user = ""
141
+ prompts: list[str] = []
139
142
  created = updated = None
140
143
  for data in iter_jsonl(path):
141
144
  stamp = data.get("timestamp")
@@ -146,7 +149,8 @@ def _kimi_session(path: Path) -> Session:
146
149
  if prompt:
147
150
  first_user = first_user or prompt
148
151
  last_user = prompt
149
- return Session("kimi", path.parent.name, str(path), None, created or file_time(path), updated or file_time(path), "moonshot", None, first_user, {}, last_user_message=last_user)
152
+ prompts.append(prompt)
153
+ return Session("kimi", path.parent.name, str(path), None, created or file_time(path), updated or file_time(path), "moonshot", None, first_user, {}, last_user_message=last_user, user_messages=tuple(prompts))
150
154
 
151
155
 
152
156
  def _codebuff_sessions(path: Path) -> list[Session]:
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from dataclasses import replace
4
5
  from pathlib import Path
5
6
  from typing import Callable, TypeAlias
6
7
 
@@ -95,8 +96,13 @@ def _dedupe(sessions: list[Session]) -> list[Session]:
95
96
  for session in sessions:
96
97
  key = (session.platform, session.id)
97
98
  current = found.get(key)
98
- if current is None or _linkage_score(session) > _linkage_score(current):
99
+ if current is None:
99
100
  found[key] = session
101
+ continue
102
+ winner, twin = (session, current) if _linkage_score(session) > _linkage_score(current) else (current, session)
103
+ if winner.user_messages is None and twin.user_messages is not None:
104
+ winner = replace(winner, user_messages=twin.user_messages)
105
+ found[key] = winner
100
106
  return list(found.values())
101
107
 
102
108
 
@@ -39,6 +39,7 @@ def jsonl_session(platform: str, path: Path, fallback_id: str) -> Session:
39
39
  sid = fallback_id
40
40
  cwd = provider = model = first_user = parent = agent = None
41
41
  last_user = ""
42
+ prompts: list[str] = []
42
43
  created = updated = None
43
44
  usage: JsonMap = {}
44
45
  for data in iter_jsonl(path):
@@ -65,8 +66,9 @@ def jsonl_session(platform: str, path: Path, fallback_id: str) -> Session:
65
66
  if prompt:
66
67
  first_user = first_user or prompt
67
68
  last_user = prompt
69
+ prompts.append(prompt)
68
70
  merge_usage(usage, as_map(message.get("usage")) or as_map(data.get("usage")))
69
- return Session(platform, sid, str(path), cwd, created or file_time(path), updated or created or file_time(path), provider, model, first_user or "", usage, parent, agent, last_user)
71
+ return Session(platform, sid, str(path), cwd, created or file_time(path), updated or created or file_time(path), provider, model, first_user or "", usage, parent, agent, last_user, tuple(prompts))
70
72
 
71
73
 
72
74
  def spawn_info(source: Json | None) -> tuple[str | None, str | None]:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass
3
+ from dataclasses import dataclass, field
4
4
  from pathlib import Path
5
5
  from typing import TypeAlias
6
6
 
@@ -38,6 +38,8 @@ class Session:
38
38
  parent_id: str | None = None
39
39
  agent: str | None = None
40
40
  last_user_message: str = ""
41
+ # Every user prompt the scanner already parsed, kept for search only; None when the scanner did not read the whole transcript.
42
+ user_messages: tuple[str, ...] | None = field(default=None, compare=False, repr=False)
41
43
 
42
44
  def to_json(self) -> JsonMap:
43
45
  return {
@@ -16,4 +16,8 @@ sys.path.insert(0, str(Path(__file__).resolve().parent))
16
16
 
17
17
 
18
18
  if __name__ == "__main__":
19
+ if sys.version_info < (3, 11):
20
+ found = ".".join(str(part) for part in sys.version_info[:3])
21
+ _ = sys.stderr.write(f"find-agent-sessions requires Python 3.11 or newer (found {found}); run it with python3.11+ or `uv run`.\n")
22
+ raise SystemExit(2)
19
23
  _ = runpy.run_module("agent_sessions.cli", run_name="__main__")