Class: Harnex::Adapters::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/harnex/adapters/base.rb

Direct Known Subclasses

Claude, Codex, Generic

Constant Summary collapse

PROMPT_PREFIXES =
[">", "\u203A", "\u276F"].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(key, extra_args = []) ⇒ Base

Returns a new instance of Base.



18
19
20
21
# File 'lib/harnex/adapters/base.rb', line 18

def initialize(key, extra_args = [])
  @key = key
  @extra_args = extra_args.dup
end

Instance Attribute Details

#keyObject (readonly)

Adapter contract — subclasses MUST implement:

base_command          -> Array[String]  CLI args to spawn

Subclasses MAY override:

input_state(text)     -> Hash           Parse screen for state
build_send_payload    -> Hash           Build injection payload
inject_exit(writer)   -> void           Send a stop/exit sequence
infer_repo_path(argv) -> String         Extract repo path from CLI args
wait_for_sendable     -> String         Wait for a send-ready snapshot


16
17
18
# File 'lib/harnex/adapters/base.rb', line 16

def key
  @key
end

Instance Method Details

#base_commandObject

Raises:

  • (NotImplementedError)


27
28
29
# File 'lib/harnex/adapters/base.rb', line 27

def base_command
  raise NotImplementedError, "#{self.class} must define #base_command"
end

#build_commandObject



23
24
25
# File 'lib/harnex/adapters/base.rb', line 23

def build_command
  base_command + @extra_args
end

#build_send_payload(text:, submit:, enter_only:, screen_text:, force: false) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/harnex/adapters/base.rb', line 70

def build_send_payload(text:, submit:, enter_only:, screen_text:, force: false)
  state = input_state(screen_text)
  if !force && blocked_state?(state, enter_only: enter_only)
    raise ArgumentError, blocked_message(state, enter_only: enter_only)
  end

  payload = enter_only ? "" : text.to_s
  payload << submit_bytes if submit || enter_only

  {
    text: payload,
    newline: false,
    input_state: state,
    force: force
  }
end

#infer_repo_path(_argv) ⇒ Object



31
32
33
# File 'lib/harnex/adapters/base.rb', line 31

def infer_repo_path(_argv)
  Dir.pwd
end

#inject_exit(writer, delay_ms: 0) ⇒ Object



87
88
89
90
91
92
93
# File 'lib/harnex/adapters/base.rb', line 87

def inject_exit(writer, delay_ms: 0)
  writer.write("/exit")
  writer.flush
  sleep(delay_ms / 1000.0) if delay_ms.positive?
  writer.write(submit_bytes)
  writer.flush
end

#input_state(screen_text) ⇒ Object



35
36
37
38
39
40
# File 'lib/harnex/adapters/base.rb', line 35

def input_state(screen_text)
  {
    state: "unknown",
    input_ready: nil
  }
end

#send_wait_seconds(submit:, enter_only:) ⇒ Object



42
43
44
# File 'lib/harnex/adapters/base.rb', line 42

def send_wait_seconds(submit:, enter_only:)
  0.0
end

#wait_for_sendable(screen_snapshot_fn, submit:, enter_only:, force:) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/harnex/adapters/base.rb', line 50

def wait_for_sendable(screen_snapshot_fn, submit:, enter_only:, force:)
  snapshot = screen_snapshot_fn.call
  return snapshot if force

  wait_secs = send_wait_seconds(submit: submit, enter_only: enter_only).to_f
  return snapshot unless wait_secs.positive?

  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + wait_secs
  state = input_state(snapshot)

  while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline &&
        wait_for_sendable_state?(state, submit: submit, enter_only: enter_only)
    sleep 0.05
    snapshot = screen_snapshot_fn.call
    state = input_state(snapshot)
  end

  snapshot
end

#wait_for_sendable_state?(_state, submit:, enter_only:) ⇒ Boolean

Returns:

  • (Boolean)


46
47
48
# File 'lib/harnex/adapters/base.rb', line 46

def wait_for_sendable_state?(_state, submit:, enter_only:)
  false
end