@atolis-hq/wake 0.1.16 → 0.1.18

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/README.md CHANGED
@@ -1,12 +1,16 @@
1
1
  # Wake
2
2
 
3
- Wake is a local autonomous agent control plane for software development. It
4
- watches the work channels your team already uses, such as GitHub Issues,
5
- decides each item's next lifecycle step with deterministic rules, and launches
6
- local coding-agent CLIs only when agentic execution is actually needed.
3
+ Wake is a control plane for autonomous software engineering. It watches the
4
+ channels your team already uses, coordinates agent activity and involves humans
5
+ when needed, and keeps the durable record attached to the work instead of the
6
+ terminal session that happened to run it.
7
7
 
8
- Wake is the control plane and decision-maker. It launches and manages local
9
- agent sessions for each unit of agentic work.
8
+ Create an issue, assign Wake, and keep doing your own work. Wake investigates,
9
+ asks for input when human judgment matters, proposes a plan, launches local
10
+ coding-agent CLIs to implement changes, opens pull requests, and carries the
11
+ conversation forward wherever the work is already happening.
12
+
13
+ Wake owns coordination. Coding agents execute the work.
10
14
 
11
15
  ## The Problem
12
16
 
@@ -27,10 +31,11 @@ out of the token-burning path wherever possible.
27
31
 
28
32
  ## Vision
29
33
 
30
- Wake should make reliable, resumable, token-aware local agent execution
34
+ Wake should make reliable, resumable, token-aware autonomous engineering
31
35
  practical. The default operating model is asynchronous and channel-driven: work
32
- enters and progresses through durable external systems, while execution happens
33
- locally in an inspectable workspace or sandbox.
36
+ enters through durable external systems, Wake keeps humans in the loop at useful
37
+ decision points, and execution happens locally in an inspectable workspace or
38
+ sandbox.
34
39
 
35
40
  Wake is not trying to replace coding agents, issue trackers, or source control.
36
41
  It coordinates across them. Over time it should become the layer that picks
@@ -1,6 +1,8 @@
1
1
  import { Octokit } from '@octokit/rest';
2
+ import { createEtagCache, fetchPaginatedWithEtag, fetchSingleWithEtag, } from './github-etag-cache.js';
2
3
  export function createGitHubClient(token) {
3
4
  const octokit = new Octokit({ auth: token });
5
+ const etagCache = createEtagCache();
4
6
  return {
5
7
  async getAuthenticatedLogin() {
6
8
  const { data } = await octokit.rest.users.getAuthenticated();
@@ -12,27 +14,31 @@ export function createGitHubClient(token) {
12
14
  // issues. Stop paginating as soon as the cap is reached.
13
15
  async listIssues(owner, repo, maxResults, since) {
14
16
  const perPage = Math.min(maxResults, 100);
15
- const results = [];
16
- for await (const { data } of octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
17
- owner,
18
- repo,
19
- state: 'all',
20
- per_page: perPage,
21
- ...(since === undefined ? {} : { since }),
22
- })) {
23
- results.push(...data);
24
- if (results.length >= maxResults) {
25
- break;
26
- }
27
- }
28
- return results.slice(0, maxResults);
17
+ return fetchPaginatedWithEtag({
18
+ cache: etagCache,
19
+ cacheKey: `issues:${owner}/${repo}`,
20
+ maxResults,
21
+ pages: (headers) => octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
22
+ owner,
23
+ repo,
24
+ state: 'all',
25
+ per_page: perPage,
26
+ ...(since === undefined ? {} : { since }),
27
+ ...(headers === undefined ? {} : { headers }),
28
+ }),
29
+ });
29
30
  },
30
31
  async listComments(owner, repo, issueNumber, perPage) {
31
- return octokit.paginate(octokit.rest.issues.listComments, {
32
- owner,
33
- repo,
34
- issue_number: issueNumber,
35
- per_page: perPage,
32
+ return fetchPaginatedWithEtag({
33
+ cache: etagCache,
34
+ cacheKey: `issue-comments:${owner}/${repo}#${issueNumber}`,
35
+ pages: (headers) => octokit.paginate.iterator(octokit.rest.issues.listComments, {
36
+ owner,
37
+ repo,
38
+ issue_number: issueNumber,
39
+ per_page: perPage,
40
+ ...(headers === undefined ? {} : { headers }),
41
+ }),
36
42
  });
37
43
  },
38
44
  async createComment(owner, repo, issueNumber, body) {
@@ -60,10 +66,18 @@ export function createGitHubClient(token) {
60
66
  return data;
61
67
  },
62
68
  async getRequiredStatusChecks(owner, repo, branch) {
63
- const { data } = await octokit.rest.repos.getBranch({
64
- owner,
65
- repo,
66
- branch,
69
+ const data = await fetchSingleWithEtag({
70
+ cache: etagCache,
71
+ cacheKey: `required-status-checks:${owner}/${repo}@${branch}`,
72
+ request: async (headers) => {
73
+ const response = await octokit.rest.repos.getBranch({
74
+ owner,
75
+ repo,
76
+ branch,
77
+ ...(headers === undefined ? {} : { headers }),
78
+ });
79
+ return { data: response.data, headers: response.headers ?? {} };
80
+ },
67
81
  });
68
82
  const requiredStatusChecks = data.protection?.required_status_checks;
69
83
  return {
@@ -74,52 +88,77 @@ export function createGitHubClient(token) {
74
88
  };
75
89
  },
76
90
  async listCheckRunsForRef(owner, repo, ref) {
77
- const { data } = await octokit.rest.checks.listForRef({
78
- owner,
79
- repo,
80
- ref,
81
- per_page: 100,
91
+ const data = await fetchSingleWithEtag({
92
+ cache: etagCache,
93
+ cacheKey: `check-runs:${owner}/${repo}@${ref}`,
94
+ request: async (headers) => {
95
+ const response = await octokit.rest.checks.listForRef({
96
+ owner,
97
+ repo,
98
+ ref,
99
+ per_page: 100,
100
+ ...(headers === undefined ? {} : { headers }),
101
+ });
102
+ return { data: response.data, headers: response.headers ?? {} };
103
+ },
82
104
  });
83
105
  return data.check_runs;
84
106
  },
85
107
  async getCombinedStatusForRef(owner, repo, ref) {
86
- const { data } = await octokit.rest.repos.getCombinedStatusForRef({
87
- owner,
88
- repo,
89
- ref,
108
+ const data = await fetchSingleWithEtag({
109
+ cache: etagCache,
110
+ cacheKey: `combined-status:${owner}/${repo}@${ref}`,
111
+ request: async (headers) => {
112
+ const response = await octokit.rest.repos.getCombinedStatusForRef({
113
+ owner,
114
+ repo,
115
+ ref,
116
+ ...(headers === undefined ? {} : { headers }),
117
+ });
118
+ return { data: response.data, headers: response.headers ?? {} };
119
+ },
90
120
  });
91
121
  return data.statuses;
92
122
  },
93
123
  async listPullRequests(owner, repo, maxResults) {
94
124
  const perPage = Math.min(maxResults, 100);
95
- const results = [];
96
- for await (const { data } of octokit.paginate.iterator(octokit.rest.pulls.list, {
97
- owner,
98
- repo,
99
- state: 'open',
100
- per_page: perPage,
101
- })) {
102
- results.push(...data);
103
- if (results.length >= maxResults) {
104
- break;
105
- }
106
- }
107
- return results.slice(0, maxResults);
125
+ return fetchPaginatedWithEtag({
126
+ cache: etagCache,
127
+ cacheKey: `pulls:${owner}/${repo}`,
128
+ maxResults,
129
+ pages: (headers) => octokit.paginate.iterator(octokit.rest.pulls.list, {
130
+ owner,
131
+ repo,
132
+ state: 'open',
133
+ per_page: perPage,
134
+ ...(headers === undefined ? {} : { headers }),
135
+ }),
136
+ });
108
137
  },
109
138
  async listReviews(owner, repo, pullNumber, perPage) {
110
- return octokit.paginate(octokit.rest.pulls.listReviews, {
111
- owner,
112
- repo,
113
- pull_number: pullNumber,
114
- per_page: perPage,
139
+ return fetchPaginatedWithEtag({
140
+ cache: etagCache,
141
+ cacheKey: `pr-reviews:${owner}/${repo}#${pullNumber}`,
142
+ pages: (headers) => octokit.paginate.iterator(octokit.rest.pulls.listReviews, {
143
+ owner,
144
+ repo,
145
+ pull_number: pullNumber,
146
+ per_page: perPage,
147
+ ...(headers === undefined ? {} : { headers }),
148
+ }),
115
149
  });
116
150
  },
117
151
  async listReviewComments(owner, repo, pullNumber, perPage) {
118
- return octokit.paginate(octokit.rest.pulls.listReviewComments, {
119
- owner,
120
- repo,
121
- pull_number: pullNumber,
122
- per_page: perPage,
152
+ return fetchPaginatedWithEtag({
153
+ cache: etagCache,
154
+ cacheKey: `pr-review-comments:${owner}/${repo}#${pullNumber}`,
155
+ pages: (headers) => octokit.paginate.iterator(octokit.rest.pulls.listReviewComments, {
156
+ owner,
157
+ repo,
158
+ pull_number: pullNumber,
159
+ per_page: perPage,
160
+ ...(headers === undefined ? {} : { headers }),
161
+ }),
123
162
  });
124
163
  },
125
164
  async replyToReviewComment(owner, repo, pullNumber, commentId, body) {
@@ -0,0 +1,57 @@
1
+ export function createEtagCache() {
2
+ return new Map();
3
+ }
4
+ function isNotModifiedError(error) {
5
+ return (typeof error === 'object' &&
6
+ error !== null &&
7
+ 'status' in error &&
8
+ error.status === 304);
9
+ }
10
+ export async function fetchSingleWithEtag(input) {
11
+ const cached = input.cache.get(input.cacheKey);
12
+ try {
13
+ const response = await input.request(cached === undefined ? undefined : { 'if-none-match': cached.etag });
14
+ if (typeof response.headers.etag === 'string') {
15
+ input.cache.set(input.cacheKey, { etag: response.headers.etag, data: response.data });
16
+ }
17
+ return response.data;
18
+ }
19
+ catch (error) {
20
+ if (isNotModifiedError(error) && cached !== undefined) {
21
+ return cached.data;
22
+ }
23
+ throw error;
24
+ }
25
+ }
26
+ export async function fetchPaginatedWithEtag(input) {
27
+ const cached = input.cache.get(input.cacheKey);
28
+ const results = [];
29
+ let pageCount = 0;
30
+ let lastHeaders = {};
31
+ try {
32
+ for await (const page of input.pages(cached === undefined ? undefined : { 'if-none-match': cached.etag })) {
33
+ pageCount += 1;
34
+ lastHeaders = page.headers;
35
+ results.push(...page.data);
36
+ if (input.maxResults !== undefined && results.length >= input.maxResults) {
37
+ break;
38
+ }
39
+ }
40
+ }
41
+ catch (error) {
42
+ if (isNotModifiedError(error) && cached !== undefined) {
43
+ return input.maxResults === undefined ? cached.data : cached.data.slice(0, input.maxResults);
44
+ }
45
+ throw error;
46
+ }
47
+ const truncated = input.maxResults !== undefined && results.length > input.maxResults
48
+ ? results.slice(0, input.maxResults)
49
+ : results;
50
+ if (pageCount <= 1 && typeof lastHeaders.etag === 'string') {
51
+ input.cache.set(input.cacheKey, { etag: lastHeaders.etag, data: truncated });
52
+ }
53
+ else {
54
+ input.cache.delete(input.cacheKey);
55
+ }
56
+ return truncated;
57
+ }
@@ -1 +1 @@
1
- export const wakeVersion = "0.1.16+g97c5859";
1
+ export const wakeVersion = "0.1.18+g6b6248c";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atolis-hq/wake",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Local autonomous agent control plane for software development",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {