@zalom/plastic 2.0.0-alpha.6 → 2.0.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hooks/message-display +2 -0
- package/package.json +1 -1
- package/scripts/hook-message-display +2 -0
- package/scripts/intent-screen +4 -0
- package/scripts/lib/hook_registry.rb +5 -3
- package/scripts/lib/intent_screen.rb +2 -0
- package/scripts/lib/intent_screen_ansi.rb +22 -11
- package/scripts/lib/message_display.rb +12 -1
package/hooks/message-display
CHANGED
|
@@ -24,6 +24,8 @@
|
|
|
24
24
|
# ALWAYS handed off, whatever it looks like, since it is the one that must
|
|
25
25
|
# not race. Ruby is the one place that actually waits (bounded, injectable
|
|
26
26
|
# for tests); this script only ever decides once, fast, and never sleeps.
|
|
27
|
+
#
|
|
28
|
+
# Claude adapter: Claude Code only; the core is harness-agnostic.
|
|
27
29
|
IFS= read -r -d '' INPUT # returns 1 at EOF: do NOT set -e
|
|
28
30
|
case $INPUT in *'"message_id"'*) ;; *) exit 0 ;; esac
|
|
29
31
|
rest=${INPUT#*\"message_id\"}; rest=${rest#*\"}; mid=${rest%%\"*}
|
package/package.json
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
# This script is the one place in the O4 stack allowed to read ENV, the
|
|
13
13
|
# clock, or Dir.tmpdir: MessageDisplay itself takes tmp_root/plastic_home/
|
|
14
14
|
# color/now as constructor arguments and touches none of them directly.
|
|
15
|
+
#
|
|
16
|
+
# Claude adapter: Claude Code only; the core is harness-agnostic.
|
|
15
17
|
|
|
16
18
|
require "json"
|
|
17
19
|
require "time"
|
package/scripts/intent-screen
CHANGED
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
# Exit codes:
|
|
27
27
|
# 0 - the screen is on stdout
|
|
28
28
|
# 2 - usage error, or the path is not an intent directory (one line on stderr)
|
|
29
|
+
#
|
|
30
|
+
# Harness-agnostic core: no harness assumption lives here. This script holds
|
|
31
|
+
# the plain/ANSI selection (intent 316a1, D3) and belongs to the same
|
|
32
|
+
# harness-agnostic core as lib/intent_screen.rb and lib/intent_screen_ansi.rb.
|
|
29
33
|
|
|
30
34
|
require_relative "lib/intent_screen"
|
|
31
35
|
require_relative "lib/intent_screen_ansi"
|
|
@@ -54,9 +54,11 @@ module HookRegistry
|
|
|
54
54
|
{ "name" => "capture", "status" => "Capturing prompt into the session ledger..." },
|
|
55
55
|
] },
|
|
56
56
|
],
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
57
|
+
# This entry belongs to the Claude adapter half of Plastic's
|
|
58
|
+
# harness-agnostic-core / Claude-adapter split (intent 316a1, D3
|
|
59
|
+
# supersedes 316a's D6): MessageDisplay is not one of
|
|
60
|
+
# CODEX_LIVE_STATE_EVENTS, so codex_hooks_json (below) never picks it
|
|
61
|
+
# up; codex_hook_names stays exactly what it was (pinned by
|
|
60
62
|
# test/hook_registry_test.rb:82 and :110-111). Fires on every streamed
|
|
61
63
|
# chunk of every assistant message (D11); the launcher (hooks/message-
|
|
62
64
|
# display) decides with shell builtins and forks nothing on the common
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
# text cut mid-sentence. `step_text`, `insight_fields` and `next_fields` are
|
|
13
13
|
# public so the ANSI renderer reuses the exact same trims (D3) rather than
|
|
14
14
|
# re-deriving them and drifting.
|
|
15
|
+
#
|
|
16
|
+
# Harness-agnostic core: no harness assumption lives here.
|
|
15
17
|
module IntentScreen
|
|
16
18
|
BAR_WIDTH = 20
|
|
17
19
|
ON = "█"
|
|
@@ -15,6 +15,13 @@ require_relative "intent_screen"
|
|
|
15
15
|
# `color:` is a constructor/call argument, never an environment read (D18):
|
|
16
16
|
# the plain path (`color: false`) is one call away and testable without
|
|
17
17
|
# touching NO_COLOR or a TTY. No ENV, no Dir.pwd, no Dir.home.
|
|
18
|
+
#
|
|
19
|
+
# Harness-agnostic core: no harness assumption lives here. `markdown_safe:`
|
|
20
|
+
# (intent 316a1, D3/D5) is the one choice a caller supplies rather than a
|
|
21
|
+
# choice this module makes for itself: a display surface that passes raw
|
|
22
|
+
# ANSI through untouched should not inherit a concession it never needed.
|
|
23
|
+
# See docs/reference/harness-adapters.md for which caller asks for it and
|
|
24
|
+
# why.
|
|
18
25
|
module IntentScreenAnsi
|
|
19
26
|
ESC = "\e"
|
|
20
27
|
RESET = "#{ESC}[0m".freeze
|
|
@@ -32,7 +39,7 @@ module IntentScreenAnsi
|
|
|
32
39
|
|
|
33
40
|
ELLIPSIS = "…"
|
|
34
41
|
|
|
35
|
-
def self.render(intent_dir:, store_root:, color: true, width: DEFAULT_WIDTH)
|
|
42
|
+
def self.render(intent_dir:, store_root:, color: true, width: DEFAULT_WIDTH, markdown_safe: false)
|
|
36
43
|
base = File.basename(intent_dir)
|
|
37
44
|
id = base.split("--", 2).first
|
|
38
45
|
intent_text = File.read(File.join(intent_dir, "#{base}.md"))
|
|
@@ -49,7 +56,7 @@ module IntentScreenAnsi
|
|
|
49
56
|
fields.merge!(IntentScreen.progress_fields(items))
|
|
50
57
|
fields.merge!(IntentScreen.next_fields(items, status, checklist_present: IntentScreen.items_present?(intent_dir), escape_pipes: false))
|
|
51
58
|
fields.merge!(IntentScreen.insight_fields(intent_text, escape_pipes: false))
|
|
52
|
-
fields.transform_values! { |v| clean(v) }
|
|
59
|
+
fields.transform_values! { |v| markdown_safe ? clean(v) : v }
|
|
53
60
|
|
|
54
61
|
done_n = fields["progress.done"].to_i
|
|
55
62
|
total_n = fields["progress.total"].to_i
|
|
@@ -103,7 +110,8 @@ module IntentScreenAnsi
|
|
|
103
110
|
badge = status_cell(item[:done], color)
|
|
104
111
|
prefix_plain = " #{num} [ #{item[:done] ? 'done' : 'open'} ] "
|
|
105
112
|
text_budget = [width - prefix_plain.length, 0].max
|
|
106
|
-
|
|
113
|
+
step = IntentScreen.step_text(item[:text])
|
|
114
|
+
text = fit_plain(markdown_safe ? clean(step) : step, text_budget)
|
|
107
115
|
out << " #{num} [#{badge}] #{text}\n"
|
|
108
116
|
end
|
|
109
117
|
end
|
|
@@ -111,12 +119,13 @@ module IntentScreenAnsi
|
|
|
111
119
|
out
|
|
112
120
|
end
|
|
113
121
|
|
|
114
|
-
# --- markdown-noise stripping (intent
|
|
122
|
+
# --- markdown-noise stripping, adapter-optional (intent 316a1, D3/D5) ------
|
|
115
123
|
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
124
|
+
# Not every display surface passes text through a Markdown renderer, so
|
|
125
|
+
# stripping is not this module's call to make (see `markdown_safe:` on
|
|
126
|
+
# `render` above; the justification for WHY a caller would ever ask for
|
|
127
|
+
# this lives with that caller, in scripts/lib/message_display.rb). When
|
|
128
|
+
# asked, strips backticks and neutralises `*`/`_` runs from a value.
|
|
120
129
|
# Single underscores are left alone: they are common inside ordinary words
|
|
121
130
|
# (`intent_screen.rb`) and GFM does not treat an intraword underscore as
|
|
122
131
|
# emphasis; only a run of 2+ (the bold marker `__`) is markdown-active.
|
|
@@ -126,9 +135,11 @@ module IntentScreenAnsi
|
|
|
126
135
|
|
|
127
136
|
# --- width cap (D15, matrix 18) --------------------------------------------
|
|
128
137
|
|
|
129
|
-
# Truncates `text`
|
|
130
|
-
#
|
|
131
|
-
#
|
|
138
|
+
# Truncates `text` to `max` visible columns with a trailing ellipsis when
|
|
139
|
+
# cut, then yields the truncated plain text to the block for coloring.
|
|
140
|
+
# Coloring never adds visible width. The cap itself is harness-neutral: a
|
|
141
|
+
# fixed width, not a re-flow, is what lets a column layout survive whatever
|
|
142
|
+
# display eventually shows it — no display's own wrapping is assumed here.
|
|
132
143
|
def self.fit(text, max)
|
|
133
144
|
plain = fit_plain(text, max)
|
|
134
145
|
block_given? ? yield(plain) : plain
|
|
@@ -15,6 +15,11 @@ require_relative "store_provisioning"
|
|
|
15
15
|
# the real Time.now/Kernel#sleep — the thin CLI (scripts/hook-message-display)
|
|
16
16
|
# is the one place allowed to read any of those.
|
|
17
17
|
#
|
|
18
|
+
# Claude adapter: Claude Code only; the core is harness-agnostic. (intent
|
|
19
|
+
# 316a1, D3 supersedes 316a's D6.) This is the sole caller that asks
|
|
20
|
+
# IntentScreenAnsi.render for `markdown_safe: true` (scripts/lib/
|
|
21
|
+
# intent_screen_ansi.rb) — see `finalize` below for why.
|
|
22
|
+
#
|
|
18
23
|
# A live run under a real pty (round 3) found that Claude Code fires the
|
|
19
24
|
# per-chunk hook processes CONCURRENTLY, not strictly in order. Chunk 0 is
|
|
20
25
|
# the one that recognizes the screen and creates the buffer (D13), and it can
|
|
@@ -230,7 +235,13 @@ class MessageDisplay
|
|
|
230
235
|
def finalize(buffered, decision)
|
|
231
236
|
intent_dir = decision[:intent_dir]
|
|
232
237
|
store_root = decision[:store_root]
|
|
233
|
-
|
|
238
|
+
# markdown_safe: true (intent 316a1, D5) - Claude Code still Markdown-
|
|
239
|
+
# processes displayContent even inside a raw ANSI block (316a's live
|
|
240
|
+
# capture showed backticks silently stripped from step text), so the
|
|
241
|
+
# Claude adapter asks the harness-agnostic core to strip markdown noise
|
|
242
|
+
# before it ever reaches the block. A harness whose display surface
|
|
243
|
+
# passes raw ANSI through untouched would ask for false instead.
|
|
244
|
+
ansi = IntentScreenAnsi.render(intent_dir: intent_dir, store_root: store_root, color: true, markdown_safe: true)
|
|
234
245
|
plain = IntentScreen.render(intent_dir: intent_dir, store_root: store_root, template: File.read(template_path))
|
|
235
246
|
splice(buffered, plain, ansi)
|
|
236
247
|
end
|