@agentshouse/kit 0.1.0-alpha.3 → 0.1.0-alpha.31

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.
Files changed (142) hide show
  1. package/README.md +722 -263
  2. package/bin/connect-linux.sh +200 -25
  3. package/bin/kit.js +179 -157
  4. package/bootstrap.json +17 -0
  5. package/package.json +7 -2
  6. package/src/adapter-mcp.js +76 -12
  7. package/src/agents.js +63 -8
  8. package/src/binding.js +52 -0
  9. package/src/blocks.js +2 -26
  10. package/src/bridge.js +28 -14
  11. package/src/bytes.js +19 -8
  12. package/src/configuration.js +13 -209
  13. package/src/connector-login.js +3 -84
  14. package/src/contract.js +36 -11
  15. package/src/delivery.js +4 -7
  16. package/src/directory.js +6 -26
  17. package/src/draft.js +4 -82
  18. package/src/git-main.js +138 -0
  19. package/src/git.js +62 -0
  20. package/src/google/calendar.js +39 -45
  21. package/src/google/drive.js +61 -186
  22. package/src/google/entries.js +346 -0
  23. package/src/google/gateway.js +3 -6
  24. package/src/google/gmail.js +33 -23
  25. package/src/google/oauth.js +106 -66
  26. package/src/google/source.js +21 -23
  27. package/src/host.js +6 -2
  28. package/src/house-main.js +65 -8
  29. package/src/house.js +446 -127
  30. package/src/how-we-work.md +25 -0
  31. package/src/inbound.js +2 -18
  32. package/src/kit-work-stream.js +118 -0
  33. package/src/launcher.js +46 -10
  34. package/src/local-files.js +3 -11
  35. package/src/login.js +23 -11
  36. package/src/mcp.js +18 -0
  37. package/src/mount.js +13 -21
  38. package/src/original.js +4 -4
  39. package/src/pacing.js +41 -0
  40. package/src/plan.js +37 -45
  41. package/src/repository.js +3 -61
  42. package/src/session-bin/git +2 -0
  43. package/src/setup.js +14 -15
  44. package/src/skill-set/ask-matt/PHASE-BOUNDARIES.md +55 -0
  45. package/src/skill-set/ask-matt/SKILL.md +91 -0
  46. package/src/skill-set/ask-matt/agents/openai.yaml +4 -0
  47. package/src/skill-set/code-review/LICENSE +21 -0
  48. package/src/skill-set/code-review/SKILL.md +89 -0
  49. package/src/skill-set/code-review/agents/openai.yaml +3 -0
  50. package/src/skill-set/codebase-design/DEEPENING.md +37 -0
  51. package/src/skill-set/codebase-design/DESIGN-IT-TWICE.md +44 -0
  52. package/src/skill-set/codebase-design/LICENSE +21 -0
  53. package/src/skill-set/codebase-design/SKILL.md +116 -0
  54. package/src/skill-set/codebase-design/agents/openai.yaml +3 -0
  55. package/src/skill-set/diagnosing-bugs/LICENSE +21 -0
  56. package/src/skill-set/diagnosing-bugs/SKILL.md +140 -0
  57. package/src/skill-set/diagnosing-bugs/agents/openai.yaml +3 -0
  58. package/src/skill-set/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
  59. package/src/skill-set/domain-modeling/ADR-FORMAT.md +47 -0
  60. package/src/skill-set/domain-modeling/CONTEXT-FORMAT.md +60 -0
  61. package/src/skill-set/domain-modeling/LICENSE +21 -0
  62. package/src/skill-set/domain-modeling/SKILL.md +76 -0
  63. package/src/skill-set/domain-modeling/agents/openai.yaml +3 -0
  64. package/src/skill-set/grill-me/LICENSE +21 -0
  65. package/src/skill-set/grill-me/SKILL.md +8 -0
  66. package/src/skill-set/grill-me/agents/openai.yaml +4 -0
  67. package/src/skill-set/grill-with-docs/LICENSE +21 -0
  68. package/src/skill-set/grill-with-docs/SKILL.md +8 -0
  69. package/src/skill-set/grill-with-docs/agents/openai.yaml +4 -0
  70. package/src/skill-set/grilling/LICENSE +21 -0
  71. package/src/{starter-set → skill-set}/grilling/SKILL.md +1 -1
  72. package/src/skill-set/handoff/LICENSE +21 -0
  73. package/src/skill-set/handoff/SKILL.md +17 -0
  74. package/src/skill-set/handoff/agents/openai.yaml +4 -0
  75. package/src/skill-set/implement/LICENSE +21 -0
  76. package/src/skill-set/implement/SKILL.md +16 -0
  77. package/src/skill-set/implement/agents/openai.yaml +4 -0
  78. package/src/skill-set/improve-codebase-architecture/HTML-REPORT.md +123 -0
  79. package/src/skill-set/improve-codebase-architecture/LICENSE +21 -0
  80. package/src/skill-set/improve-codebase-architecture/SKILL.md +72 -0
  81. package/src/skill-set/improve-codebase-architecture/agents/openai.yaml +4 -0
  82. package/src/skill-set/prototype/LICENSE +21 -0
  83. package/src/skill-set/prototype/LOGIC.md +67 -0
  84. package/src/skill-set/prototype/SKILL.md +28 -0
  85. package/src/skill-set/prototype/UI.md +112 -0
  86. package/src/skill-set/prototype/agents/openai.yaml +3 -0
  87. package/src/skill-set/research/LICENSE +21 -0
  88. package/src/skill-set/research/SKILL.md +14 -0
  89. package/src/skill-set/research/agents/openai.yaml +3 -0
  90. package/src/skill-set/resolving-merge-conflicts/LICENSE +21 -0
  91. package/src/skill-set/resolving-merge-conflicts/SKILL.md +16 -0
  92. package/src/skill-set/resolving-merge-conflicts/agents/openai.yaml +3 -0
  93. package/src/skill-set/tdd/LICENSE +21 -0
  94. package/src/skill-set/tdd/SKILL.md +40 -0
  95. package/src/skill-set/tdd/agents/openai.yaml +3 -0
  96. package/src/skill-set/tdd/mocking.md +59 -0
  97. package/src/skill-set/tdd/tests.md +77 -0
  98. package/src/skill-set/teach/GLOSSARY-FORMAT.md +35 -0
  99. package/src/skill-set/teach/LEARNING-RECORD-FORMAT.md +46 -0
  100. package/src/skill-set/teach/LICENSE +21 -0
  101. package/src/skill-set/teach/MISSION-FORMAT.md +31 -0
  102. package/src/skill-set/teach/RESOURCES-FORMAT.md +32 -0
  103. package/src/skill-set/teach/SKILL.md +141 -0
  104. package/src/skill-set/teach/agents/openai.yaml +4 -0
  105. package/src/skill-set/to-questionnaire/LICENSE +21 -0
  106. package/src/skill-set/to-questionnaire/SKILL.md +55 -0
  107. package/src/skill-set/to-questionnaire/agents/openai.yaml +4 -0
  108. package/src/skill-set/to-spec/LICENSE +21 -0
  109. package/src/skill-set/to-spec/SKILL.md +76 -0
  110. package/src/skill-set/to-spec/agents/openai.yaml +4 -0
  111. package/src/skill-set/to-tickets/LICENSE +21 -0
  112. package/src/skill-set/to-tickets/SKILL.md +106 -0
  113. package/src/skill-set/to-tickets/agents/openai.yaml +4 -0
  114. package/src/skill-set/wait-what/LICENSE +21 -0
  115. package/src/skill-set/wait-what/SKILL.md +8 -0
  116. package/src/skill-set/wait-what/agents/openai.yaml +4 -0
  117. package/src/skill-set/wayfinder/LICENSE +21 -0
  118. package/src/skill-set/wayfinder/SKILL.md +129 -0
  119. package/src/skill-set/wayfinder/agents/openai.yaml +4 -0
  120. package/src/skill-set/writing-for-agents/LICENSE +21 -0
  121. package/src/skill-set/writing-for-agents/SKILL-MECHANICS.md +22 -0
  122. package/src/skill-set/writing-for-agents/SKILL.md +83 -0
  123. package/src/skill-set/writing-for-agents/agents/openai.yaml +3 -0
  124. package/src/skill-set.js +85 -0
  125. package/src/source.js +93 -54
  126. package/src/state.js +19 -1
  127. package/src/sync-library.js +7 -11
  128. package/src/sync-state.js +16 -3
  129. package/src/sync.js +129 -41
  130. package/src/tasks.js +75 -0
  131. package/src/telegram-view.js +214 -0
  132. package/src/telegram.js +161 -66
  133. package/src/turn-frames.js +69 -0
  134. package/src/worker.js +685 -184
  135. package/src/working-copy.js +1221 -0
  136. package/src/workspace.js +40 -32
  137. package/src/google/declared.js +0 -146
  138. package/src/google/id.js +0 -1
  139. package/src/instance/google.js +0 -1
  140. package/src/starter-set.js +0 -17
  141. /package/src/{starter-set/grilling → skill-set/ask-matt}/LICENSE +0 -0
  142. /package/src/{starter-set → skill-set}/grilling/agents/openai.yaml +0 -0
@@ -0,0 +1,138 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { accessSync, constants, realpathSync, statSync } from "node:fs";
3
+ import { constants as system } from "node:os";
4
+ import { delimiter, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import {
7
+ BRIDGE_NONCE_ENVIRONMENT,
8
+ BRIDGE_SOCKET_ENVIRONMENT,
9
+ WORKING_COPY_INDEX_METHOD,
10
+ WORKING_COPY_RECEIVED_REF,
11
+ } from "./contract.js";
12
+ import { bridgeCall } from "./mcp.js";
13
+ import { formatCommandError, KitRefusal } from "./refusal.js";
14
+
15
+ const SHIM = realpathSync(fileURLToPath(new URL("session-bin/git", import.meta.url)));
16
+ const VALUED = new Set(["-C", "-c", "--git-dir", "--work-tree", "--namespace", "--config-env", "--attr-source", "--super-prefix"]);
17
+ const RELAYED = ["SIGINT", "SIGTERM", "SIGHUP", "SIGQUIT"];
18
+
19
+ function nativeGit() {
20
+ for (const directory of (process.env.PATH ?? "").split(delimiter)) {
21
+ if (directory === "") continue;
22
+ const candidate = join(directory, "git");
23
+ try {
24
+ if (!statSync(candidate).isFile() || realpathSync(candidate) === SHIM) continue;
25
+ accessSync(candidate, constants.X_OK);
26
+ return candidate;
27
+ } catch {}
28
+ }
29
+ return null;
30
+ }
31
+
32
+ function command(args) {
33
+ let index = 0;
34
+ while (index < args.length && args[index].startsWith("-")) index += VALUED.has(args[index]) ? 2 : 1;
35
+ return { globals: args.slice(0, index), name: args[index], rest: args.slice(index + 1) };
36
+ }
37
+
38
+ function delegated(git, args) {
39
+ return new Promise((resolve) => {
40
+ const child = spawn(git, args, { stdio: "inherit" });
41
+ const relay = (signal) => child.kill(signal);
42
+ for (const signal of RELAYED) process.on(signal, relay);
43
+ const settle = (outcome) => {
44
+ for (const signal of RELAYED) process.off(signal, relay);
45
+ resolve(outcome);
46
+ };
47
+ child.once("error", (error) => {
48
+ process.stderr.write(`kit_git_unavailable: ${error.message}\n`);
49
+ settle({ status: 127, signal: null });
50
+ });
51
+ child.once("close", (status, signal) => settle({ status, signal }));
52
+ });
53
+ }
54
+
55
+ function captured(git, args, env = process.env) {
56
+ const run = spawnSync(git, args, { stdio: ["ignore", "pipe", "ignore"], maxBuffer: Infinity, env });
57
+ return run.status === 0 ? run.stdout.toString("utf8") : null;
58
+ }
59
+
60
+ function indexed(listing) {
61
+ const entries = new Map();
62
+ for (const record of listing.split("\0")) {
63
+ const tab = record.indexOf("\t");
64
+ if (tab < 0) continue;
65
+ const [mode, object, stage] = record.slice(0, tab).split(" ");
66
+ entries.set(record.slice(tab + 1), stage === "0" ? `${mode} ${object}` : null);
67
+ }
68
+ return entries;
69
+ }
70
+
71
+ function renamings(output) {
72
+ return (output ?? "").split("\n").filter((line) => line.startsWith("Renaming ")).map((line) => line.slice(9));
73
+ }
74
+
75
+ function movedPaths(renaming, before, after) {
76
+ for (let at = renaming.indexOf(" to "); at >= 0; at = renaming.indexOf(" to ", at + 1)) {
77
+ const from = renaming.slice(0, at);
78
+ const to = renaming.slice(at + 4);
79
+ if (before.has(from) && !after.has(from) && after.get(to) === before.get(from)) return [{ from, to }];
80
+ }
81
+ return [];
82
+ }
83
+
84
+ async function recorded(cwd, change) {
85
+ const socket = process.env[BRIDGE_SOCKET_ENVIRONMENT];
86
+ const nonce = process.env[BRIDGE_NONCE_ENVIRONMENT];
87
+ if (socket === undefined || nonce === undefined) return;
88
+ try {
89
+ const frame = await bridgeCall({ socket, nonce }, { method: WORKING_COPY_INDEX_METHOD, params: { cwd, ...change } });
90
+ if (frame.error !== undefined) throw new KitRefusal(frame.error.message ?? "kit_house_refused");
91
+ } catch (refusal) {
92
+ process.stderr.write(`${formatCommandError(refusal)}: house git push will not see this index change\n`);
93
+ }
94
+ }
95
+
96
+ async function observed(git, { globals, name, rest }, args) {
97
+ const located = captured(git, [...globals, "rev-parse", "--show-toplevel", "--show-cdup", WORKING_COPY_RECEIVED_REF, "HEAD"]);
98
+ if (located === null) return delegated(git, args);
99
+ const [top, cdup, , head] = located.split("\n");
100
+ const listing = [...globals, "ls-files", "--stage", "-z", "--full-name", "--", cdup === "" ? "." : cdup];
101
+ const before = captured(git, listing);
102
+ const rehearsal = [...globals, "mv", "--dry-run", ...rest];
103
+ const planned = name === "mv" ? renamings(captured(git, rehearsal, { ...process.env, LC_ALL: "C" })) : [];
104
+ const outcome = await delegated(git, args);
105
+ const after = before === null ? null : captured(git, listing);
106
+ if (after === null) return outcome;
107
+ const [prior, next] = [indexed(before), indexed(after)];
108
+ const moves = outcome.status === 0 ? planned.flatMap((renaming) => movedPaths(renaming, prior, next)) : [];
109
+ const sources = new Set(moves.map((move) => move.from));
110
+ const destinations = new Set(moves.map((move) => move.to));
111
+ const gone = [...prior.keys()].filter((path) => !next.has(path) && !sources.has(path));
112
+ const arrived = [...next.keys()].filter((path) => !prior.has(path) && !destinations.has(path));
113
+ const heads = captured(git, [...globals, "rev-parse", "HEAD", ...(name === "rebase" ? ["ORIG_HEAD"] : [])]);
114
+ const [current, tip = head] = heads?.trim().split("\n") ?? [];
115
+ const commit = current === undefined || current === head ? [] : [tip, current];
116
+ if (moves.length > 0 || gone.length > 0 || arrived.length > 0 || commit.length > 0) {
117
+ await recorded(top, { moves, gone, arrived, commit, command: name });
118
+ }
119
+ return outcome;
120
+ }
121
+
122
+ async function run(args) {
123
+ const git = nativeGit();
124
+ if (git === null) {
125
+ process.stderr.write("kit_git_unavailable\n");
126
+ return { status: 127, signal: null };
127
+ }
128
+ return observed(git, command(args), args);
129
+ }
130
+
131
+ run(process.argv.slice(2)).then(({ status, signal }) => {
132
+ if (signal === null) {
133
+ process.exitCode = status;
134
+ return;
135
+ }
136
+ process.exitCode = 128 + system.signals[signal];
137
+ process.kill(process.pid, signal);
138
+ });
package/src/git.js ADDED
@@ -0,0 +1,62 @@
1
+ import { spawn } from "node:child_process";
2
+ import { KitRefusal } from "./refusal.js";
3
+
4
+ function cancelledOr(signal, error, code) {
5
+ return signal?.aborted === true || error?.name === "AbortError"
6
+ ? new KitRefusal("kit_command_cancelled")
7
+ : new KitRefusal(code);
8
+ }
9
+
10
+ function stdio(capture, input) {
11
+ if (input !== null) return ["pipe", capture ? "pipe" : "inherit", "inherit"];
12
+ return capture ? ["ignore", "pipe", "inherit"] : "inherit";
13
+ }
14
+
15
+ export function runGit(cwd, args, options = {}) {
16
+ const { dependencies = {}, capture = false, input = null, signal, failure } = options;
17
+ if (signal?.aborted === true) return Promise.reject(new KitRefusal("kit_command_cancelled"));
18
+ const launcher = dependencies.spawn ?? spawn;
19
+ return new Promise((resolve, reject) => {
20
+ let child;
21
+ try {
22
+ child = launcher("git", args, {
23
+ cwd,
24
+ env: process.env,
25
+ stdio: stdio(capture, input),
26
+ signal,
27
+ killSignal: "SIGKILL",
28
+ });
29
+ } catch (error) {
30
+ reject(cancelledOr(signal, error, "kit_git_unavailable"));
31
+ return;
32
+ }
33
+ const output = [];
34
+ let settled = false;
35
+ const finish = (operation) => {
36
+ if (settled) return;
37
+ settled = true;
38
+ operation();
39
+ };
40
+ child.stdout?.on("data", (chunk) => output.push(Buffer.from(chunk)));
41
+ child.once("error", (error) =>
42
+ finish(() => reject(cancelledOr(signal, error, "kit_git_unavailable"))),
43
+ );
44
+ child.once("close", (status) => {
45
+ finish(() => {
46
+ if (signal?.aborted === true) {
47
+ reject(new KitRefusal("kit_command_cancelled"));
48
+ return;
49
+ }
50
+ if (status !== 0) {
51
+ reject(new KitRefusal(failure));
52
+ return;
53
+ }
54
+ resolve(Buffer.concat(output));
55
+ });
56
+ });
57
+ if (input !== null) {
58
+ child.stdin.on("error", () => undefined);
59
+ child.stdin.end(input);
60
+ }
61
+ });
62
+ }
@@ -4,9 +4,10 @@ import { googleJson } from "./gateway.js";
4
4
  import { GOOGLE_SCOPES } from "./oauth.js";
5
5
 
6
6
  const CALENDAR_ORIGIN = "https://www.googleapis.com";
7
+ const CALENDAR = "primary";
7
8
 
8
- function cursorOf(value, calendars) {
9
- if (value === null) return { calendar: 0, pageToken: null, syncTokens: {} };
9
+ function cursorOf(value) {
10
+ if (value === null) return { pageToken: null, syncToken: null };
10
11
  let cursor;
11
12
  try {
12
13
  cursor = JSON.parse(value);
@@ -16,16 +17,8 @@ function cursorOf(value, calendars) {
16
17
  if (
17
18
  typeof cursor !== "object" ||
18
19
  cursor === null ||
19
- !Number.isInteger(cursor.calendar) ||
20
- cursor.calendar < 0 ||
21
- cursor.calendar >= calendars.length ||
22
20
  (cursor.pageToken !== null && typeof cursor.pageToken !== "string") ||
23
- typeof cursor.syncTokens !== "object" ||
24
- cursor.syncTokens === null ||
25
- Array.isArray(cursor.syncTokens) ||
26
- Object.entries(cursor.syncTokens).some(
27
- ([calendar, token]) => !calendars.includes(calendar) || typeof token !== "string",
28
- )
21
+ (cursor.syncToken !== null && typeof cursor.syncToken !== "string")
29
22
  ) {
30
23
  throw new KitRefusal("google_checkpoint_invalid", "calendar");
31
24
  }
@@ -45,7 +38,7 @@ function authorOf(event) {
45
38
  : event.organizer.email;
46
39
  }
47
40
 
48
- function normalizedEvent(event, calendar) {
41
+ function normalizedEvent(event) {
49
42
  if (
50
43
  typeof event?.id !== "string" ||
51
44
  typeof event.etag !== "string" ||
@@ -71,45 +64,53 @@ function normalizedEvent(event, calendar) {
71
64
  author: authorOf(event),
72
65
  url: event.htmlLink ?? null,
73
66
  attachments: [],
74
- container: `google/calendar/${calendar}`,
67
+ container: `google/calendar/${CALENDAR}`,
75
68
  };
76
69
  }
77
70
 
78
- function pageUrl(calendar, cursor, limit, backfillFloor) {
79
- const url = new URL(
80
- `/calendar/v3/calendars/${encodeURIComponent(calendar)}/events`,
81
- CALENDAR_ORIGIN,
82
- );
71
+ function common(options) {
72
+ return {
73
+ directory: options.directory,
74
+ account: options.account,
75
+ requiredScopes: [GOOGLE_SCOPES.calendar],
76
+ signal: options.signal,
77
+ };
78
+ }
79
+
80
+ function pageUrl(cursor, limit, backfillFloor) {
81
+ const url = new URL(`/calendar/v3/calendars/${CALENDAR}/events`, CALENDAR_ORIGIN);
83
82
  url.searchParams.set("maxResults", `${limit}`);
84
83
  url.searchParams.set("singleEvents", "true");
85
84
  url.searchParams.set("showDeleted", "true");
86
85
  if (cursor.pageToken !== null) url.searchParams.set("pageToken", cursor.pageToken);
87
- const syncToken = cursor.syncTokens[calendar];
88
- if (syncToken !== undefined) url.searchParams.set("syncToken", syncToken);
86
+ if (cursor.syncToken !== null) url.searchParams.set("syncToken", cursor.syncToken);
89
87
  else if (backfillFloor !== null && backfillFloor !== undefined) {
90
88
  url.searchParams.set("timeMin", backfillFloor);
91
89
  }
92
90
  return url.toString();
93
91
  }
94
92
 
93
+ export async function calendarEmail(options, dependencies = {}) {
94
+ const calendar = await googleJson(
95
+ { ...common(options), url: `${CALENDAR_ORIGIN}/calendar/v3/calendars/${CALENDAR}` },
96
+ dependencies,
97
+ );
98
+ if (typeof calendar.id !== "string") {
99
+ throw new KitRefusal("google_provider_response_invalid", "calendar");
100
+ }
101
+ return calendar.id;
102
+ }
103
+
95
104
  export async function calendarPage(options, dependencies = {}) {
96
- const calendars = options.selection.calendars;
97
- let cursor = cursorOf(options.cursor, calendars);
98
- const calendar = calendars[cursor.calendar];
99
- const common = {
100
- directory: options.directory,
101
- instance: options.instance,
102
- client: options.client,
103
- requiredScopes: [GOOGLE_SCOPES.calendar],
104
- signal: options.signal,
105
- };
105
+ let cursor = cursorOf(options.cursor);
106
+ const shared = common(options);
106
107
  let answer;
107
108
  try {
108
109
  answer = await googleJson(
109
110
  {
110
- ...common,
111
+ ...shared,
111
112
  goneCode: "google_calendar_cursor_expired",
112
- url: pageUrl(calendar, cursor, options.limit, options.backfillFloor),
113
+ url: pageUrl(cursor, options.limit, options.backfillFloor),
113
114
  },
114
115
  dependencies,
115
116
  );
@@ -117,11 +118,9 @@ export async function calendarPage(options, dependencies = {}) {
117
118
  if (!(error instanceof KitRefusal) || error.code !== "google_calendar_cursor_expired") {
118
119
  throw error;
119
120
  }
120
- const syncTokens = { ...cursor.syncTokens };
121
- delete syncTokens[calendar];
122
- cursor = { calendar: cursor.calendar, pageToken: null, syncTokens };
121
+ cursor = { pageToken: null, syncToken: null };
123
122
  answer = await googleJson(
124
- { ...common, url: pageUrl(calendar, cursor, options.limit, options.backfillFloor) },
123
+ { ...shared, url: pageUrl(cursor, options.limit, options.backfillFloor) },
125
124
  dependencies,
126
125
  );
127
126
  }
@@ -135,23 +134,18 @@ export async function calendarPage(options, dependencies = {}) {
135
134
  }
136
135
  let next;
137
136
  if (answer.nextPageToken !== undefined) {
138
- next = { calendar: cursor.calendar, pageToken: answer.nextPageToken, syncTokens: cursor.syncTokens };
137
+ next = { pageToken: answer.nextPageToken, syncToken: cursor.syncToken };
139
138
  } else {
140
139
  if (typeof answer.nextSyncToken !== "string") {
141
140
  throw new KitRefusal("google_provider_response_invalid", "calendar sync token");
142
141
  }
143
- const syncTokens = { ...cursor.syncTokens, [calendar]: answer.nextSyncToken };
144
- next = {
145
- calendar: cursor.calendar + 1 === calendars.length ? 0 : cursor.calendar + 1,
146
- pageToken: null,
147
- syncTokens,
148
- };
142
+ next = { pageToken: null, syncToken: answer.nextSyncToken };
149
143
  }
150
144
  const normalized = [];
151
145
  const dropped = [];
152
146
  for (const event of items) {
153
147
  try {
154
- normalized.push(normalizedEvent(event, calendar));
148
+ normalized.push(normalizedEvent(event));
155
149
  } catch (error) {
156
150
  if (
157
151
  error instanceof KitRefusal &&
@@ -172,6 +166,6 @@ export async function calendarPage(options, dependencies = {}) {
172
166
  items: normalized,
173
167
  dropped,
174
168
  cursor: JSON.stringify(next),
175
- more: answer.nextPageToken !== undefined || cursor.calendar + 1 < calendars.length,
169
+ more: answer.nextPageToken !== undefined,
176
170
  };
177
171
  }