ace-wake 0.1.0 → 0.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c70984a4740dce94a78731fd0852370b10a23a4b05e43286450ad83b7121bad3
4
- data.tar.gz: 757c46a35a41081cfc85666681326a0275d127d8d2490c6470e6e8dbb1db4fc6
3
+ metadata.gz: c2dff41db21b22ff36dbe9d9dda962979e2598072dfba16a406b81cdb1fb0cbf
4
+ data.tar.gz: f303030a7487b009196d4a4f1ffe92ea90f4872e66b20a9549871c968c85eaa9
5
5
  SHA512:
6
- metadata.gz: bd19ae88ac86295da911337b06c20aa07f6c282fc14cc5f900527b93fb3b2c6989083789863565f49af0d17e77faee14b543a90995c514a4236ab33710f9ba40
7
- data.tar.gz: 41b39de4a48e8dfc036774424161bcbc416aa25f36090daa7d181bb021223e2b2a5e1d1b863c810f94309c5adb03760821400a6bea502bdc008729746ab11e02
6
+ metadata.gz: e3284df4db91ce0b48fa1714b00b818622d0df1c20ee7d5f4b7812683f545696bbeed3bb11089a066825726cd3d5a81d09836747c9ee276b503bae19028770e8
7
+ data.tar.gz: 9496ea0bc29040ce63e62c2be847a782b4b7c0237efa5982a9793dcc9b81de2d5e591bb3b33cc4d9b8e862b7fda6f0477b16a691e67ba2e2e3c5e33d9f77e358
data/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.0] - 2026-10-31
11
+
12
+ ### Added
13
+
14
+ - Agent-callable eyes start with configPath, returning initial pending revisions
15
+ without a redundant wake; quota breakers and retirement remain protected.
16
+
8
17
  ## [0.1.0] - 2026-10-30
9
18
 
10
19
  First publishable release of the ace-wake asset package (task family
@@ -25,7 +25,7 @@ export async function observeHitl(cwd, config) {
25
25
 
26
26
  export function registerEyes(pi, { observe = observeHitl, now = Date.now,
27
27
  schedule = (fn, ms) => setInterval(fn, ms), cancel = clearInterval } = {}) {
28
- let eyes, timer, attempt, compacting = false, running = false;
28
+ let eyes, timer, attempt, observedSources, bindingGeneration = 0, compacting = false, running = false, arming = false;
29
29
  const stopTimer = () => { if (timer !== undefined) cancel(timer); timer = undefined; };
30
30
  function arm() {
31
31
  stopTimer();
@@ -35,7 +35,8 @@ export function registerEyes(pi, { observe = observeHitl, now = Date.now,
35
35
  }
36
36
  }
37
37
  const bind = (ctx) => {
38
- stopTimer(); eyes?.dispose(); attempt = undefined; compacting = false; running = false;
38
+ const generation = ++bindingGeneration;
39
+ stopTimer(); eyes?.dispose(); attempt = undefined; observedSources = undefined; compacting = false; running = false;
39
40
  const entries = ctx.sessionManager.getBranch();
40
41
  const snapshot = [...entries].reverse().find((e) => e.type === "custom" && e.customType === EYES_ENTRY)?.data;
41
42
  const ownerCwd = path.resolve(ctx.cwd);
@@ -50,8 +51,14 @@ export function registerEyes(pi, { observe = observeHitl, now = Date.now,
50
51
  ctx.ui?.setStatus?.("ace-eyes", `eyes ${state.retired ? "retired" : state.breaker?.reason || (state.enabled ? "observing" : "off")} pending:${state.pending}`);
51
52
  if (state.breaker || state.retired) stopTimer();
52
53
  },
53
- observe: (config) => observe(ctx.cwd, config),
54
- canDeliver: () => !compacting && (ctx.isIdle() || running),
54
+ observe: async (config) => {
55
+ const result = await observe(ctx.cwd, config);
56
+ // A disposed binding can still finish its asynchronous owner read.
57
+ // Its source health is never evidence for the current session binding.
58
+ if (generation === bindingGeneration) observedSources = result.sources;
59
+ return result;
60
+ },
61
+ canDeliver: () => !arming && !compacting && (ctx.isIdle() || running),
55
62
  deliveryStatus: () => {
56
63
  if (attempt?.accepted) return "accepted";
57
64
  // sendUserMessage is a void wrapper. Busy/queued is uncertain, not
@@ -61,8 +68,8 @@ export function registerEyes(pi, { observe = observeHitl, now = Date.now,
61
68
  },
62
69
  deliver: async (batch) => {
63
70
  if (!ctx.model) return false;
64
- const text = `[ace-eyes] Pending canonical HITL/deadline revisions: ${JSON.stringify(batch)}. ` +
65
- "Read original events; adopt reports through ace-overseer process-reports and existing task/assignment gates. " +
71
+ const text = `[ace-eyes] Pending task/assignment/HITL/deadline revisions: ${JSON.stringify(batch)}. ` +
72
+ "Read ace-overseer observe and the original state owners; adopt reports through ace-overseer process-reports and existing task/assignment gates. " +
66
73
  "Reconcile uncertain effects; never repeat external actions on wake alone. After handling each exact revision, " +
67
74
  "acknowledge with the ace_eyes tool (action ack, key, revision). Settlement alone is not acknowledgement.";
68
75
  attempt = { text, accepted: false };
@@ -94,9 +101,10 @@ export function registerEyes(pi, { observe = observeHitl, now = Date.now,
94
101
  if (message?.role === "assistant" && message.stopReason === "error") eyes?.quota(message.errorMessage);
95
102
  });
96
103
  pi.registerTool?.({
97
- name: "ace_eyes", label: "Eyes acknowledgement", description: "Read pending semantic events or acknowledge an exact handled revision. Does not accept reports, authorize effects, start actors or reset quota. Use existing report/assignment owners first. Deadlines require an operation identity and revision; cancel when no longer active.",
104
+ name: "ace_eyes", label: "Eyes observation", description: "Start standing observation with an existing config file, read pending semantic events or acknowledge an exact handled revision. Does not accept reports, authorize effects, start actors or reset quota. Use existing report/assignment owners first. Deadlines require an operation identity and revision; cancel when no longer active.",
98
105
  parameters: { type: "object", properties: {
99
- action: { type: "string", enum: ["status", "ack", "deadline", "cancel"] },
106
+ action: { type: "string", enum: ["start", "status", "ack", "deadline", "cancel"] },
107
+ configPath: { type: "string" },
100
108
  key: { type: "string" }, revision: { type: "string" }, at: { type: "integer" }
101
109
  }, required: ["action"], additionalProperties: false },
102
110
  execute: async (_id, args, _signal, _onUpdate, ctx) => {
@@ -104,13 +112,24 @@ export function registerEyes(pi, { observe = observeHitl, now = Date.now,
104
112
  if (!ctx) throw new Error("Eyes session not initialized");
105
113
  bind(ctx);
106
114
  }
107
- if (args.action === "ack") eyes.ack(args.key, args.revision);
115
+ if (args.action === "start") {
116
+ if (!ctx || typeof args.configPath !== "string" || !args.configPath.trim()) {
117
+ throw new Error("Eyes start requires configPath and the current session context");
118
+ }
119
+ const config = JSON.parse(fs.readFileSync(path.resolve(ctx.cwd, args.configPath), "utf8"));
120
+ eyes.start(config); arm();
121
+ // Observe now, but do not enqueue a redundant model turn inside the
122
+ // caller's turn. Pending revisions are returned to the caller below.
123
+ arming = true;
124
+ try { await eyes.tick(); } finally { arming = false; }
125
+ }
126
+ else if (args.action === "ack") eyes.ack(args.key, args.revision);
108
127
  else if (args.action === "deadline") eyes.deadline(args.key, args.at, args.revision);
109
128
  else if (args.action === "cancel") eyes.cancelDeadline(args.key);
110
129
  else if (args.action !== "status") throw new Error("Unknown eyes action");
111
130
  // Do not send a second wake from inside the running model turn. The next
112
131
  // observation delivers remaining coalesced revisions through the same owner.
113
- const details = { ...eyes.summary(), pending: eyes.s.pending, deadlines: eyes.s.deadlines };
132
+ const details = { ...eyes.summary(), sources: observedSources, pending: eyes.s.pending, deadlines: eyes.s.deadlines };
114
133
  return { content: [{ type: "text", text: JSON.stringify(details) }], details };
115
134
  }
116
135
  });
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Ace
4
4
  module Wake
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ace-wake
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Czyz
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2026-10-30 00:00:00.000000000 Z
10
+ date: 2026-10-31 00:00:00.000000000 Z
11
11
  dependencies: []
12
12
  description: 'Publishable asset package for the ace-wake Pi harness extension: /loop
13
13
  timers and /watch file subscriptions that wake idle agents. Ships the extension