silas 0.1.1 → 0.1.3

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: '08d5115f914d3ff251c01f57b506951e3367717d777bfdd3d175a165169552f7'
4
- data.tar.gz: 04e26fde565b7cf2d2a402316e095d6b0cf9f6a753b2d2444682890ec0ebe66f
3
+ metadata.gz: 35e622a6b7c17416ebe71717f1d072bd07c8a075f901c5b477177aa9bfccfa29
4
+ data.tar.gz: 59a87acd719063f275926852e255507d3c6159c16892907f70c8a77180dd61f8
5
5
  SHA512:
6
- metadata.gz: 48d66589890f1c88e9ac8e1d476b131821907aea8c6e60d87300a42970e3e6a7bf52673020a94961f9bc32d2b3409cd6f735b6dfbb3d973ccaa86087298cb939
7
- data.tar.gz: 9ddc0ddbba4ad607ecba6443b85492e94d72a058aab78f13ad256d62c2d0631f0a8d61cc0f37cd902d2701e821b4e5248aa7a1076321a2fe25f34fbf13c5a6c9
6
+ metadata.gz: 608b8bc79c926eadf04b74360e62832413580986c8ef5c6899b5acdc9371695db555799d9dd26d76b3aea77d2deafee13f32236cf8564c3cc6ad47692c58be80
7
+ data.tar.gz: f992423e27b802ae2efb6e7cf1c366f584ff703bd2210b9388b68abb9730616f2d16ded6b0da6e4b0a62c4c9fefd5becc8e8a85c6a17763d47dfd72bfef6d9ab
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.3
4
+
5
+ - **Ledger: parallel graded gates.** When the model emits several tool calls in
6
+ one step, `settle!` now settles every invocation instead of stopping at the
7
+ first one that needs approval. An ungated call (e.g. a low-risk write) runs
8
+ immediately even when a gated sibling (e.g. a money move) parks for a human —
9
+ it is no longer stranded behind the approval. This changes only *timing*, not
10
+ safety: an unsettled sibling already executed on resume regardless of the
11
+ human's approve/decline, so stopping early only delayed independent work.
12
+ Regression test in `spec/silas/parallel_tool_calls_spec.rb`.
13
+
14
+ ## 0.1.2
15
+
16
+ - **Security (email channel scaffold): approvals no longer go to the session
17
+ initiator.** The generated `Agent::Channels::Email#deliver_approval` mailed the
18
+ approve link to `email["from"]` — the address that started the session, which
19
+ for a support agent is the customer, letting them approve their own gated
20
+ request. It now routes to a configured operator (`SILAS_APPROVER_EMAIL`) and
21
+ fails closed (sends nothing) if unset. The Slack scaffold was unaffected (its
22
+ card posts to the team channel/thread, not the initiator).
23
+
3
24
  ## 0.1.1
4
25
 
5
26
  - **Fix: parallel tool calls.** When the model emitted several tool_use blocks
@@ -10,9 +10,20 @@ class Agent::Channels::Email < Silas::Channel
10
10
  end
11
11
 
12
12
  def deliver_approval(session:, invocation:)
13
- email = session.metadata["email"] || {}
13
+ # SECURITY: an approval must go to an OPERATOR, never to whoever started the
14
+ # session. For an email-driven agent, session.metadata["email"]["from"] is
15
+ # the person who emailed in — often the customer — so mailing THEM the
16
+ # approve link lets them approve their own request. Route approvals to your
17
+ # ops/approver inbox instead, and fail closed if it isn't configured.
18
+ approver = ENV["SILAS_APPROVER_EMAIL"] # e.g. "approvals@yourcompany.com"
19
+ if approver.blank?
20
+ Rails.logger.warn("[Silas] SILAS_APPROVER_EMAIL unset — approval for " \
21
+ "invocation #{invocation.id} not emailed (won't send to the sender).")
22
+ return
23
+ end
24
+
14
25
  Silas::ChannelMailer.approval(
15
- to: email["from"], subject: "Approval needed: #{invocation.tool_name}", invocation: invocation
26
+ to: approver, subject: "Approval needed: #{invocation.tool_name}", invocation: invocation
16
27
  ).deliver_later
17
28
  end
18
29
  end
data/lib/silas/ledger.rb CHANGED
@@ -34,12 +34,19 @@ module Silas
34
34
  "the continuation believes are committed."
35
35
  end
36
36
 
37
+ # Settle EVERY invocation in the step, then report whether the turn must
38
+ # park. When the model emits parallel tool calls, an ungated one must not
39
+ # be blocked by a gated sibling's approval — and it wouldn't be protected
40
+ # anyway: an unsettled sibling executes on resume regardless of whether the
41
+ # human approves or declines the gated call, so stopping at the first park
42
+ # only delays independent work. Execute what can proceed; park if any
43
+ # invocation is awaiting a human.
37
44
  def settle!(step, resolver:)
45
+ parked = false
38
46
  step.tool_invocations.order(:id).each do |invocation|
39
- outcome = settle_invocation!(invocation, resolver)
40
- return :parked if outcome == :parked
47
+ parked = true if settle_invocation!(invocation, resolver) == :parked
41
48
  end
42
- :completed
49
+ parked ? :parked : :completed
43
50
  end
44
51
 
45
52
  # Drive a SINGLE freshly-created invocation to a terminal state — the
data/lib/silas/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Silas
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.3"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: silas
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel St Paul