codex-notify 1.0.0 → 1.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d3b7dc4b1f9edda880bd5f39324dbb56c70812bc9ebe1d37578d0915e4313a47
4
- data.tar.gz: fb7e1ee08765f2a617d56c5d4fa2151fe8816b305051928ba5f197c3f66df2d9
3
+ metadata.gz: a9f20095aeedc816a39f3c4cafe9fffeabb35b56783d0e4c35ccc6362c3320b7
4
+ data.tar.gz: e8ce3c1e7a9035451d705fef8c8d6a3a2f28bcb4a90df0cfe3c58dce086368d1
5
5
  SHA512:
6
- metadata.gz: fd8a7f1b1af5e6c08f954544718b0e340edbc6433eab906678c48079950ac9df2a1d893f7d0e1092f724637b93fa3a22ff250309974640b66f07cd6c602649b6
7
- data.tar.gz: a6c35b4741731a1f6694df415d89d1fccd51ed228ce0eaa8651ec896fcf51607144ab8d545035d10660e27a178035825c3c6c5ebff8e5d0669953eb226e290c8
6
+ metadata.gz: b74d3ea07ae06575e8e95f3cffba1b7add24f8565f7ff768ee0a75b5ba72b49a621a78262bca324a9452fa20a61ac3f1876d50d194b2d4ebfe44ec418a9cbd99
7
+ data.tar.gz: 8f70b6c4e10bba04b8077cacae2fd14779e563af69f8f1c5a369edae7a324b1453c33fb94011c99e2c961ba8913c89e9f3910a536f13efb8ad8c9ea59400c94b
data/CHANGELOG.md CHANGED
@@ -3,6 +3,27 @@
3
3
  All notable changes to codex-notify are documented in this file. Releases from
4
4
  1.0.0 onward follow [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [1.0.1] - 2026-10-28
7
+
8
+ ### Fixed
9
+
10
+ - Suppress known internal Codex task-title and Composer Predictions requests
11
+ and their responses in Hook normal mode and log-tail mode, reducing internal
12
+ activity sent to Slack while preserving ordinary prompts and quoted text.
13
+ - Preserve existing Hook conversation threads when suppressing these internal
14
+ requests, and resume notifications on the next ordinary user prompt.
15
+ - Carry initial internal-prompt suppression to the first identified log
16
+ session, including when the request is not repeated in the log.
17
+
18
+ ### Development and security
19
+
20
+ - Add dependency audits before CI tests and package verification, daily and
21
+ manually triggered audits, and GitHub Dependency Review on pull requests.
22
+ - Audit locked gems and the recorded Bundler version, require fresh advisory
23
+ data, and prevent project advisory ignores from hiding known vulnerabilities.
24
+ - Update the preferred development Ruby and CI/release jobs to Ruby 4.0.7.
25
+ Ruby 3.4 or newer remains supported; no configuration migration is required.
26
+
6
27
  ## [1.0.0] - 2026-08-13
7
28
 
8
29
  ### Added
@@ -47,4 +68,5 @@ All notable changes to codex-notify are documented in this file. Releases from
47
68
  `command -v codex-notify-hook`, then review and trust the changed Hook
48
69
  definition in Codex.
49
70
 
71
+ [1.0.1]: https://github.com/koichiro/codex-notify/releases/tag/v1.0.1
50
72
  [1.0.0]: https://github.com/koichiro/codex-notify/releases/tag/v1.0.0
data/README.md CHANGED
@@ -675,6 +675,41 @@ Notes:
675
675
 
676
676
  Hook mode does not require `--no-alt-screen`, because it does not depend on session-log tailing.
677
677
 
678
+ ### Internal title-generation and composer-prediction notifications
679
+
680
+ Hook normal mode and log-tail mode suppress the known Codex task-title request
681
+ and subsequent events from that internal session (Hook: the final response;
682
+ log-tail: responses and tool events). Detection requires the complete known
683
+ instruction envelope followed by `User prompt:` and a non-empty prompt. Ordinary
684
+ title requests, JSON responses containing `title`, and instructions quoted with
685
+ an introduction, block quote, or code fence remain eligible for notification.
686
+ An exact copy of the internal request is indistinguishable from that request;
687
+ changed or unknown internal templates are not suppressed automatically.
688
+
689
+ Hook normal mode and log-tail mode also suppress the known Codex Composer
690
+ Predictions request, which generates a suggested next user message, and
691
+ subsequent internal responses.
692
+ Detection requires the complete known instruction template, including its JSON
693
+ schema, with whitespace normalized. Ordinary requests for suggestions, responses
694
+ containing `suggestion`, and templates quoted with an introduction, block quote,
695
+ or code fence remain eligible for notification. Changed or unknown prediction
696
+ templates are not suppressed automatically, and an exact copy is
697
+ indistinguishable from the internal request.
698
+
699
+ Hook suppression persists across invocations and resume events. The next regular
700
+ prompt clears it; startup/clear events retain their existing reset behavior.
701
+ Suppressing title generation or composer prediction preserves an existing
702
+ conversation thread. Hook debug mode retains its existing diagnostic visibility, including these internal
703
+ requests and responses. Log-tail suppression lasts for the current monitor run
704
+ and applies even with tool notifications enabled; the monitoring-start message
705
+ is unchanged. An internal initial prompt supplied with `--prompt` also suppresses
706
+ responses after the first log session ID becomes available, until an ordinary
707
+ user event in that session clears suppression. Starting a monitor after the
708
+ internal prompt cannot classify its response from JSON shape alone.
709
+
710
+ This filter limits unnecessary outbound content. Existing secret redaction
711
+ still applies immediately before Slack API calls.
712
+
678
713
  ## Development
679
714
 
680
715
  Install development dependencies in a source checkout:
@@ -695,7 +730,9 @@ are checkout development commands.
695
730
 
696
731
  The test suite uses `minitest`, runs through `rake`, and enforces 90% line coverage for files under `lib/`.
697
732
 
698
- Ruby 3.4 or newer is supported. CI covers the minimum supported 3.4 series and the 4.0 series used by the project's `.ruby-version`.
733
+ Ruby 3.4 or newer is supported. The preferred development version in
734
+ `.ruby-version` is Ruby 4.0.7. CI covers the minimum supported 3.4 series and
735
+ Ruby 4.0.7; release jobs also use Ruby 4.0.7.
699
736
 
700
737
  Run either executable from a source checkout through Bundler:
701
738
 
@@ -709,6 +746,42 @@ These commands run the checkout code. In contrast, unqualified
709
746
  Ruby environment. Checkout Bundler and `.ruby-version` behavior is not a
710
747
  runtime requirement of the installed commands.
711
748
 
749
+ ### Dependency security checks
750
+
751
+ Dependabot checks Bundler and GitHub Actions updates weekly. CI also runs a
752
+ dependency audit on every push and pull request before tests and package
753
+ verification. The audit checks all gems in `Gemfile.lock`, including development
754
+ and transitive dependencies, plus the Bundler version recorded under
755
+ `BUNDLED WITH`, against a freshly updated Ruby Advisory Database:
756
+
757
+ ```bash
758
+ bundle exec ruby script/dependency_audit.rb
759
+ ```
760
+
761
+ The same audit runs daily at 09:17 Japan time (00:17 UTC) on the default branch
762
+ and can be started manually from Actions → Dependency audit → Run workflow.
763
+ Scheduled runs begin after the workflow is merged into the default branch.
764
+ Known vulnerabilities, insecure gem sources, and advisory database update
765
+ failures fail the audit rather than being ignored. Local audits require network
766
+ access; the test suite itself remains offline. The audit script requires an
767
+ existing advisory database to be a Git repository and refresh successfully.
768
+ Unlike stock `bundle-audit check --update`, which may warn and continue with
769
+ stale data when the database is not a Git repository, this script fails in that
770
+ case. A missing database is downloaded before scanning. A missing `BUNDLED WITH`
771
+ version also fails the audit instead of silently skipping Bundler. Advisory
772
+ `ignore` entries in a project's `.bundler-audit.yml` do not suppress findings
773
+ for locked gems or Bundler in this audit.
774
+
775
+ Pull requests additionally run GitHub's Dependency Review Action against the
776
+ GitHub Advisory Database, which also powers Dependabot alerts. Newly introduced
777
+ known vulnerabilities of low severity or higher fail the review for runtime,
778
+ development, and unknown dependency scopes. Results appear in Actions logs and
779
+ job summaries. These checks supplement Dependabot; they do not change repository
780
+ alert or automatic security-update settings. Dependency Review requires the
781
+ repository's Dependency graph to be enabled under Settings → Advanced Security;
782
+ enabling Dependabot alerts also enables the graph. To require successful checks
783
+ before merging, configure branch protection or repository rulesets separately.
784
+
712
785
  ### Gem packaging
713
786
 
714
787
  The gem version is defined once as `CodexNotify::VERSION`. The initial package
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'hook_config'
4
+ require_relative 'internal_task'
4
5
  require_relative 'hook_store'
5
6
  require_relative 'hook_formatter'
6
7
  require_relative 'hook_thread_publisher'
@@ -89,6 +90,12 @@ module CodexNotify
89
90
  def handle_user_prompt_submit(event)
90
91
  prompt = event.prompt
91
92
  session_id = event.session_id
93
+ reason = InternalTask.suppression_reason(prompt) if normal?
94
+ if reason
95
+ @store.suppress_session(session_id, reason, preserve_thread: true)
96
+ return
97
+ end
98
+
92
99
  if normal? && internal_ambient_suggestions_prompt?(prompt)
93
100
  @store.suppress_session(session_id, 'internal_ambient_suggestions')
94
101
  return
@@ -28,11 +28,13 @@ module CodexNotify
28
28
  end
29
29
  end
30
30
 
31
- def suppress_session(session_id, reason)
31
+ def suppress_session(session_id, reason, preserve_thread: false)
32
32
  update_state do |data|
33
33
  key = session_id.to_s
34
- data['threads'].delete(key)
35
- data['generations'][key] = data['generations'].fetch(key, 0).to_i + 1
34
+ unless preserve_thread
35
+ data['threads'].delete(key)
36
+ data['generations'][key] = data['generations'].fetch(key, 0).to_i + 1
37
+ end
36
38
  data['suppressed_sessions'][key] = {
37
39
  'reason' => reason.to_s,
38
40
  'suppressed_at' => Time.now.utc.iso8601
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CodexNotify
4
+ module InternalTask
5
+ module_function
6
+
7
+ # Match the complete known envelope, not title-related words or JSON output.
8
+ TITLE_INSTRUCTIONS = <<~TEXT.split.join(' ').freeze
9
+ You are a helpful assistant. You will be presented with a user prompt, and your job is to provide a short title for a task that will be created from that prompt.
10
+ The task usually has to do with coding work, such as fixing a bug, changing a feature, or answering a question about a codebase.
11
+ Generate a concise UI title of at most 36 characters.
12
+ Use a single line of plain text only.
13
+ Do not include quotes, markdown, formatting characters, or trailing punctuation.
14
+ If the prompt includes a ticket reference, include it verbatim.
15
+ Prefer an imperative verb when the user is asking for a change.
16
+ Do not answer the user or attempt the task.
17
+ TEXT
18
+
19
+ COMPOSER_PREDICTION_INSTRUCTIONS = <<~TEXT.split.join(' ').freeze
20
+ This is an app-generated request for a suggested next user message. The user did not write this message.
21
+
22
+ Your suggestion will appear in the user's empty message composer. They can accept it, edit it, or ignore it; it will not be sent automatically.
23
+
24
+ Predict the single message this user is most likely to type next from the visible conversation. Use the whole conversation to understand their current intent and the latest exchange. Choose the action and specific object that best continue that intent, rather than a generally useful follow-up. Express its core naturally in the user's voice, keeping the requirements needed to retain its meaning. Choose the strongest grounded move even if several continuations are possible; uncertainty about exact wording or incidental details is not a reason to return null. Use null only when the conversation offers no grounded next move at all.
25
+
26
+ Set suggestion to exactly one natural user message, one line, at most 240 characters, or null. No alternatives or explanation.
27
+
28
+ Return only JSON matching this schema: {"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"suggestion":{"type":["string","null"]}},"required":["suggestion"],"additionalProperties":false}.
29
+ TEXT
30
+
31
+ def suppression_reason(prompt)
32
+ return 'internal_title_generation' if title_prompt?(prompt)
33
+ return 'internal_composer_prediction' if composer_prediction_prompt?(prompt)
34
+
35
+ nil
36
+ end
37
+
38
+ def composer_prediction_prompt?(prompt)
39
+ prompt.to_s.split.join(' ') == COMPOSER_PREDICTION_INSTRUCTIONS
40
+ end
41
+
42
+ def title_prompt?(prompt)
43
+ instructions, separator, user_prompt = prompt.to_s.strip.partition(/\r?\n\s*User prompt:\s*\r?\n/)
44
+ !separator.empty? && !user_prompt.strip.empty? && instructions.split.join(' ') == TITLE_INSTRUCTIONS
45
+ end
46
+ end
47
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'json'
4
+ require_relative 'internal_task'
4
5
 
5
6
  module CodexNotify
6
7
  module StreamProcessor
@@ -8,6 +9,13 @@ module CodexNotify
8
9
 
9
10
  def process_codex_log_stream(stream, token:, channel:, root_message:, initial_prompt: nil, user_name: 'user', include_tools: false,
10
11
  throttle_sec: 0.0, post_func:, sleep_func: Kernel.method(:sleep), publisher: nil)
12
+ suppressed_sessions = {}
13
+ if InternalTask.suppression_reason(initial_prompt)
14
+ suppressed_sessions['__default__'] = true
15
+ initial_prompt = nil
16
+ end
17
+ stream = visible_log_events(stream, suppressed_sessions)
18
+
11
19
  if publisher
12
20
  return process_durable_stream(
13
21
  stream, root_message:, initial_prompt:, user_name:, include_tools:, publisher:
@@ -43,15 +51,7 @@ module CodexNotify
43
51
  thread_ts_by_session['__default__'] = post_root.call(message)
44
52
  end
45
53
 
46
- stream.each do |raw|
47
- line = raw.strip
48
- next if line.empty?
49
-
50
- event = JSON.parse(line)
51
- session_id = CodexNotify::LogEventParser.extract_session_id(event) || '__default__'
52
- extracted_events = CodexNotify::LogEventParser.extract_events(event)
53
- next if extracted_events.empty?
54
-
54
+ stream.each do |session_id, extracted_events|
55
55
  extracted_events.each do |kind, text, part_type|
56
56
  next if text.empty?
57
57
  next if part_type == 'tool' && !include_tools
@@ -78,8 +78,6 @@ module CodexNotify
78
78
 
79
79
  post_message.call(message, thread_ts)
80
80
  end
81
- rescue JSON::ParserError
82
- next
83
81
  end
84
82
 
85
83
  0
@@ -97,15 +95,7 @@ module CodexNotify
97
95
  return 1 unless drain_succeeded?(publisher)
98
96
  end
99
97
 
100
- stream.each do |raw|
101
- line = raw.strip
102
- next if line.empty?
103
-
104
- event = JSON.parse(line)
105
- session_id = CodexNotify::LogEventParser.extract_session_id(event) || '__default__'
106
- extracted_events = CodexNotify::LogEventParser.extract_events(event)
107
- next if extracted_events.empty?
108
-
98
+ stream.each do |session_id, extracted_events|
109
99
  extracted_events.each do |kind, body, part_type|
110
100
  next if body.empty?
111
101
  next if part_type == 'tool' && !include_tools
@@ -126,13 +116,35 @@ module CodexNotify
126
116
  end
127
117
  return 1 unless drain_succeeded?(publisher)
128
118
  end
129
- rescue JSON::ParserError
130
- next
131
119
  end
132
120
 
133
121
  0
134
122
  end
135
123
 
124
+ def visible_log_events(stream, suppressed_sessions)
125
+ Enumerator.new do |output|
126
+ current_session = '__default__'
127
+ stream.each do |raw|
128
+ begin
129
+ event = JSON.parse(raw)
130
+ rescue JSON::ParserError
131
+ next
132
+ end
133
+ session_id = LogEventParser.extract_session_id(event)
134
+ if session_id && current_session == '__default__' && suppressed_sessions.key?('__default__')
135
+ # Bind anonymous suppression to the first session, not every later ID.
136
+ suppressed_sessions[session_id] = suppressed_sessions.delete('__default__')
137
+ end
138
+ current_session = session_id || current_session
139
+ events = LogEventParser.extract_events(event).reject do |kind, text, _type|
140
+ suppressed_sessions[current_session] = InternalTask.suppression_reason(text) if kind == 'user'
141
+ suppressed_sessions[current_session]
142
+ end
143
+ output << [session_id || '__default__', events] unless events.empty?
144
+ end
145
+ end
146
+ end
147
+
136
148
  def drain_succeeded?(publisher)
137
149
  result = publisher.drain
138
150
  result.failed.empty? && result.needs_review.empty?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodexNotify
4
- VERSION = '1.0.0'
4
+ VERSION = '1.0.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codex-notify
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Koichiro Ohba
@@ -56,6 +56,7 @@ files:
56
56
  - lib/codex_notify/hook_runner.rb
57
57
  - lib/codex_notify/hook_store.rb
58
58
  - lib/codex_notify/hook_thread_publisher.rb
59
+ - lib/codex_notify/internal_task.rb
59
60
  - lib/codex_notify/log_event_parser.rb
60
61
  - lib/codex_notify/message_formatter.rb
61
62
  - lib/codex_notify/outbox_commands.rb
@@ -71,9 +72,9 @@ homepage: https://github.com/koichiro/codex-notify
71
72
  licenses:
72
73
  - MIT
73
74
  metadata:
74
- source_code_uri: https://github.com/koichiro/codex-notify/tree/v1.0.0
75
+ source_code_uri: https://github.com/koichiro/codex-notify/tree/v1.0.1
75
76
  bug_tracker_uri: https://github.com/koichiro/codex-notify/issues
76
- changelog_uri: https://github.com/koichiro/codex-notify/blob/v1.0.0/CHANGELOG.md
77
+ changelog_uri: https://github.com/koichiro/codex-notify/blob/v1.0.1/CHANGELOG.md
77
78
  documentation_uri: https://github.com/koichiro/codex-notify#readme
78
79
  rubygems_mfa_required: 'true'
79
80
  rdoc_options: []
@@ -90,7 +91,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
91
  - !ruby/object:Gem::Version
91
92
  version: '0'
92
93
  requirements: []
93
- rubygems_version: 4.0.16
94
+ rubygems_version: 4.0.20
94
95
  specification_version: 4
95
96
  summary: Send compact Codex activity notifications to Slack
96
97
  test_files: []