@jpcasabiancai/cayenne 0.5.11 → 0.5.13

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 (2) hide show
  1. package/dist/cli.js +164 -213
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -554,6 +554,7 @@ var init_repositories = __esm({
554
554
  "not_found",
555
555
  "not_installed",
556
556
  "github_not_installed",
557
+ "github_not_connected",
557
558
  "archived",
558
559
  "repository_mismatch",
559
560
  "not_configured",
@@ -660,7 +661,7 @@ var init_skills = __esm({
660
661
 
661
662
  // ../../packages/shared/src/protocol/integrations.ts
662
663
  import { z as z7 } from "zod";
663
- var INTEGRATION_PROVIDERS, AUTH_KINDS, both, PROVIDER_META, IntegrationProviderSchema, AuthKindSchema, ProviderConfigPublic, ConnectStartRequest, ConnectStartResponse, ConnectCompleteRequest, DisconnectRequest, GithubInstallationId, GithubAccountLogin, GithubAccountType, GithubAvatarUrl, GithubInstallation, GithubAvailableInstallation, GithubInstallationList, GithubBindRequest, GithubBindResponse, GithubUnbindRequest, GithubUnbindResponse, MessageResourceKind, ReplyRequest, ReplyResponse, ResourceList, SetSubscriptionsRequest, INBOX_ERROR_CODES, InboxErrorCode, CONVERSATION_KINDS, ConversationKind, MESSAGE_ATTACHMENTS_MAX, MessageAttachment, MessageAttachments, INBOX_CHANNELS, InboxChannel, INBOX_CATEGORIES, InboxCategory, ATTACHMENT_KINDS, AttachmentKind, INTEL_STATUSES, IntelStatus, Probability, IntelNeeds, IntelFlags, InboxIntel, MessageIntel;
664
+ var INTEGRATION_PROVIDERS, AUTH_KINDS, IntegrationProviderSchema, AuthKindSchema, ProviderConfigPublic, ConnectStartRequest, ConnectStartResponse, ConnectCompleteRequest, DisconnectRequest, MessageResourceKind, ReplyRequest, ReplyResponse, ResourceList, SetSubscriptionsRequest, INBOX_ERROR_CODES, InboxErrorCode, CONVERSATION_KINDS, ConversationKind, MESSAGE_ATTACHMENTS_MAX, MessageAttachment, MessageAttachments, INBOX_CHANNELS, InboxChannel, INBOX_CATEGORIES, InboxCategory, ATTACHMENT_KINDS, AttachmentKind, INTEL_STATUSES, IntelStatus, Probability, IntelNeeds, IntelFlags, InboxIntel, MessageIntel;
664
665
  var init_integrations = __esm({
665
666
  "../../packages/shared/src/protocol/integrations.ts"() {
666
667
  "use strict";
@@ -678,91 +679,6 @@ var init_integrations = __esm({
678
679
  "trello"
679
680
  ];
680
681
  AUTH_KINDS = ["native", "composio", "platform"];
681
- both = () => ["native", "composio"];
682
- PROVIDER_META = {
683
- slack: {
684
- label: "Slack",
685
- badge: "SL",
686
- paths: { native: null, composio: null, platform: { stage: 1 } },
687
- capabilities: { messages: ["platform"], tasks: [] },
688
- webhook: { fn: "slack-events", params: [] }
689
- },
690
- clickup: {
691
- label: "ClickUp",
692
- badge: "CU",
693
- // Comments on the personal connection; tasks on the workspace's native connection.
694
- paths: { native: { stage: 1 }, composio: null, platform: { stage: 1 } },
695
- capabilities: { messages: ["platform"], tasks: ["native"] },
696
- webhook: { fn: "clickup-webhook", params: ["ws", "sub"] }
697
- },
698
- gmail: {
699
- label: "Gmail",
700
- badge: "ML",
701
- paths: { native: null, composio: null, platform: { stage: 1 } },
702
- capabilities: { messages: ["platform"], tasks: [] },
703
- webhook: null
704
- },
705
- teams: {
706
- label: "Microsoft Teams",
707
- badge: "MS",
708
- // Withdrawn until a personal path exists (personal inbox §3.8); its adapter stays in the tree.
709
- paths: { native: null, composio: null, platform: null },
710
- capabilities: { messages: [], tasks: [] },
711
- webhook: { fn: "teams-notifications", params: ["ws"] }
712
- },
713
- github: {
714
- label: "GitHub",
715
- badge: "GH",
716
- // The Cayenne GitHub App (D57), a team workspace's platform path; Composio is Stage 3.
717
- paths: { native: null, composio: { stage: 3, toolkit: "github" }, platform: { stage: 1 } },
718
- capabilities: { messages: [], tasks: ["composio", "platform"] },
719
- webhook: { fn: "github-webhook", params: [] }
720
- },
721
- monday: {
722
- label: "Monday.com",
723
- badge: "MO",
724
- // Composio's Monday toolkit has no triggers; not offered.
725
- paths: { native: { stage: 1 }, composio: null, platform: null },
726
- capabilities: { messages: [], tasks: ["native"] },
727
- webhook: { fn: "monday-webhook", params: ["ws", "sub"] }
728
- },
729
- linear: {
730
- label: "Linear",
731
- badge: "LN",
732
- paths: { native: { stage: 3 }, composio: { stage: 1, toolkit: "linear" }, platform: null },
733
- capabilities: { messages: [], tasks: both() },
734
- webhook: { fn: "linear-webhook", params: ["ws", "sub"] }
735
- },
736
- jira: {
737
- label: "Jira",
738
- badge: "JR",
739
- paths: { native: { stage: 3 }, composio: { stage: 1, toolkit: "jira" }, platform: null },
740
- capabilities: { messages: [], tasks: both() },
741
- // Jira signs nothing: `t` is the subscription's token, compared constant-time.
742
- webhook: { fn: "jira-webhook", params: ["ws", "sub", "t"] }
743
- },
744
- asana: {
745
- label: "Asana",
746
- badge: "AS",
747
- paths: { native: { stage: 3 }, composio: { stage: 1, toolkit: "asana" }, platform: null },
748
- capabilities: { messages: [], tasks: both() },
749
- webhook: { fn: "asana-webhook", params: ["ws", "sub"] }
750
- },
751
- notion: {
752
- label: "Notion",
753
- badge: "NO",
754
- paths: { native: { stage: 3 }, composio: { stage: 1, toolkit: "notion" }, platform: null },
755
- capabilities: { messages: [], tasks: both() },
756
- webhook: null
757
- },
758
- trello: {
759
- label: "Trello",
760
- badge: "TR",
761
- paths: { native: { stage: 3 }, composio: { stage: 1, toolkit: "trello" }, platform: null },
762
- capabilities: { messages: [], tasks: both() },
763
- webhook: { fn: "trello-webhook", params: ["ws", "sub"] }
764
- }
765
- };
766
682
  IntegrationProviderSchema = z7.enum(INTEGRATION_PROVIDERS);
767
683
  AuthKindSchema = z7.enum(AUTH_KINDS);
768
684
  ProviderConfigPublic = z7.object({
@@ -778,49 +694,11 @@ var init_integrations = __esm({
778
694
  ConnectStartRequest = z7.strictObject({
779
695
  workspaceId: z7.uuid(),
780
696
  provider: IntegrationProviderSchema,
781
- acknowledged: z7.boolean().optional(),
782
- intent: z7.enum(["connect", "install"]).optional()
783
- }).superRefine((r, ctx) => {
784
- if (r.intent === "install" && r.provider !== "github") {
785
- ctx.addIssue({ code: "custom", path: ["intent"], message: "install is GitHub only" });
786
- }
697
+ acknowledged: z7.boolean().optional()
787
698
  });
788
699
  ConnectStartResponse = z7.object({ url: z7.url({ protocol: /^https$/ }) });
789
700
  ConnectCompleteRequest = z7.strictObject({ pendingId: z7.uuid() });
790
701
  DisconnectRequest = z7.strictObject({ integrationId: z7.uuid() });
791
- GithubInstallationId = z7.number().int().positive();
792
- GithubAccountLogin = z7.string().min(1).max(100);
793
- GithubAccountType = z7.enum(["Organization", "User"]);
794
- GithubAvatarUrl = z7.url({
795
- protocol: /^https$/,
796
- hostname: /^avatars[.]githubusercontent[.]com$/
797
- });
798
- GithubInstallation = z7.object({
799
- id: z7.uuid(),
800
- installationId: GithubInstallationId,
801
- accountLogin: GithubAccountLogin,
802
- accountType: GithubAccountType,
803
- avatarUrl: GithubAvatarUrl,
804
- addedBy: z7.object({ id: z7.uuid(), displayName: z7.string().nullable() }).nullable(),
805
- createdAt: z7.string()
806
- });
807
- GithubAvailableInstallation = z7.object({
808
- installationId: GithubInstallationId,
809
- accountLogin: GithubAccountLogin,
810
- accountType: GithubAccountType,
811
- avatarUrl: GithubAvatarUrl
812
- });
813
- GithubInstallationList = z7.object({
814
- bound: z7.array(GithubInstallation),
815
- available: z7.array(GithubAvailableInstallation)
816
- });
817
- GithubBindRequest = z7.strictObject({
818
- workspaceId: z7.uuid(),
819
- installationId: GithubInstallationId
820
- });
821
- GithubBindResponse = z7.object({ binding: GithubInstallation });
822
- GithubUnbindRequest = z7.strictObject({ workspaceId: z7.uuid(), bindingId: z7.uuid() });
823
- GithubUnbindResponse = z7.object({ orphaned: z7.number().int().nonnegative() });
824
702
  MessageResourceKind = z7.enum(["clickup_team", "teams_channel", "teams_chat"]);
825
703
  ReplyRequest = z7.strictObject({
826
704
  messageId: z7.uuid(),
@@ -962,9 +840,13 @@ var init_tasks = __esm({
962
840
  });
963
841
  TaskDraft = TaskPatch.required({ title: true });
964
842
  ContainerLinkRequest = z8.strictObject({
843
+ workspaceId: z8.uuid(),
965
844
  integrationId: z8.uuid(),
966
- externalId: z8.string().min(1).max(500),
845
+ shareUrl: z8.string().min(1).max(2048).optional(),
846
+ externalId: z8.string().min(1).max(500).optional(),
967
847
  projectId: z8.uuid().optional()
848
+ }).refine((r) => r.shareUrl === void 0 !== (r.externalId === void 0), {
849
+ message: "give exactly one of shareUrl and externalId"
968
850
  });
969
851
  StatusId = z8.string().min(1).max(200);
970
852
  StatusMap = z8.strictObject({
@@ -1470,6 +1352,28 @@ var init_context = __esm({
1470
1352
  kind: z13.literal("claim"),
1471
1353
  id: z13.uuid(),
1472
1354
  collision: z13.enum(["overlap", "sequence"])
1355
+ }),
1356
+ /**
1357
+ * A recall hit on an `items` row (layer `recall`): the item its chunk belongs to, with the item's
1358
+ * `source_ref` when the hit carried one. Ids only: never a title or content.
1359
+ */
1360
+ z13.object({ kind: z13.literal("item"), id: z13.uuid(), source_ref: z13.string().max(200).optional() }),
1361
+ /** A recall hit on a `facts` row (layer `recall`). */
1362
+ z13.object({ kind: z13.literal("fact"), id: z13.uuid() }),
1363
+ /**
1364
+ * How many recall hits the block showed that are not workspace-visible, unknown visibility
1365
+ * included (layer `recall`): never their ids or `source_ref`, since a session that turns
1366
+ * workspace-visible later exposes its ledger with it. Strict: it carries no id.
1367
+ */
1368
+ z13.strictObject({ kind: z13.literal("recall_private"), count: z13.number().int().positive() }),
1369
+ /**
1370
+ * The earlier segment a handoff carried (layer `handoff`): `memory_recall`'s
1371
+ * `chat_segments:<session>:<index>`. Segment `n`'s handoff is written in `n + 1`.
1372
+ */
1373
+ z13.object({
1374
+ kind: z13.literal("segment"),
1375
+ session: z13.uuid(),
1376
+ index: z13.number().int().nonnegative()
1473
1377
  })
1474
1378
  ]);
1475
1379
  CONTEXT_UNAVAILABLE = "CONTEXT_UNAVAILABLE";
@@ -6100,7 +6004,7 @@ var init_credentials = __esm({
6100
6004
  init_introspect();
6101
6005
  READ_ONLY_NOT_CONNECTED = "Cayenne: this repository chat is read-only because you have not connected GitHub. Connect it in Settings \u2192 Integrations, then send your next message here.";
6102
6006
  READ_ONLY_NO_EXPIRY = "Cayenne: this repository chat is read-only on this runner because your GitHub token never expires, and Cayenne lends only expiring tokens to another member's runner. Reconnect GitHub in Settings \u2192 Integrations to get a token that expires, then send your next message here.";
6103
- READ_ONLY_GITHUB_NOT_INSTALLED = "Cayenne: this repository chat is read-only because this repository's GitHub organisation is not connected to this workspace. Add it in Settings \u2192 Integrations, then send your next message here.";
6007
+ READ_ONLY_GITHUB_NOT_INSTALLED = "Cayenne: this repository chat is read-only because your GitHub account cannot reach this repository through the Cayenne GitHub App. Install the app on the repository's organisation from Settings \u2192 Integrations, then send your next message here.";
6104
6008
  READ_ONLY_SCOPE_REFUSED = "Cayenne: this repository chat is read-only because GitHub would not give your token access to this repository; your GitHub account may not have access to it. Send another message here once it does.";
6105
6009
  READ_ONLY_UNAVAILABLE = "Cayenne: this repository chat could not get your GitHub token, so pushing is off for now. Send another message to retry.";
6106
6010
  CREDENTIAL_HELPER = String.raw`!f() { test "$1" = get || exit 0; if [ -n "$GH_TOKEN" ]; then echo username=x-access-token; echo "password=$GH_TOKEN"; else printf '%s\n' "$CAYENNE_READ_ONLY" >&2; exit 1; fi; }; f`;
@@ -10123,9 +10027,13 @@ function cloneRefusal(code, slug) {
10123
10027
  switch (code) {
10124
10028
  case "not_installed":
10125
10029
  return `VALIDATION: ${slug} is not in the Cayenne GitHub App's installation`;
10126
- // D57: the repository's installation is not bound to this workspace, or there is none.
10030
+ // D68: `/clone` mints for the runner owner, whose own GitHub token cannot read the repository or
10031
+ // lists no installation covering it. The member reading this may not be that owner.
10127
10032
  case "github_not_installed":
10128
- return `VALIDATION: ${slug}'s GitHub organisation is not connected to this workspace; add it in Settings \u2192 Integrations`;
10033
+ return `VALIDATION: this runner's owner cannot reach ${slug} through the Cayenne GitHub App; they need access to it on GitHub, and the app installed on its organisation from Settings \u2192 Integrations`;
10034
+ // D68: the runner owner has no active GitHub connection of their own, or GitHub refused it.
10035
+ case "github_not_connected":
10036
+ return `VALIDATION: this runner cannot get ${slug} because its owner has not connected GitHub, or their connection expired; they can connect it in Settings \u2192 Integrations`;
10129
10037
  case "repository_mismatch":
10130
10038
  return `VALIDATION: ${slug} was renamed or replaced on GitHub; pick it again`;
10131
10039
  case "bad_default_branch":
@@ -10145,7 +10053,7 @@ var init_token_client = __esm({
10145
10053
  "use strict";
10146
10054
  init_src();
10147
10055
  TOKEN_SHAPE = /^[A-Za-z0-9_]{20,255}$/;
10148
- DEFAULT_TIMEOUT_MS = 45e3;
10056
+ DEFAULT_TIMEOUT_MS = 9e4;
10149
10057
  }
10150
10058
  });
10151
10059
 
@@ -11249,6 +11157,65 @@ var init_budget = __esm({
11249
11157
  }
11250
11158
  });
11251
11159
 
11160
+ // src/context/ledger.ts
11161
+ function capRefs(refs, max = MAX_REFS) {
11162
+ const counts = refs.filter((r) => COUNT_KINDS.has(r.kind)).slice(0, max);
11163
+ const ids = refs.filter((r) => !COUNT_KINDS.has(r.kind)).slice(0, max - counts.length);
11164
+ return [...ids, ...counts];
11165
+ }
11166
+ async function writeLedger(insert, at, rows, log) {
11167
+ const valid = [];
11168
+ for (const row of rows) {
11169
+ const parsed = ContextLedgerRow.safeParse({
11170
+ workspace_id: at.workspaceId,
11171
+ session_id: at.sessionId,
11172
+ segment: at.segment,
11173
+ turn_seq: row.turn_seq ?? null,
11174
+ layer: row.layer,
11175
+ refs: capRefs(row.refs),
11176
+ tokens: row.tokens
11177
+ });
11178
+ if (parsed.success) valid.push(parsed.data);
11179
+ else log?.warn({ sessionId: at.sessionId, layer: row.layer }, "context ledger row refused");
11180
+ }
11181
+ if (valid.length === 0) return true;
11182
+ try {
11183
+ const { error } = await insert(valid);
11184
+ if (!error) return true;
11185
+ log?.warn({ sessionId: at.sessionId, err: error.message }, "context ledger write failed");
11186
+ } catch (err) {
11187
+ log?.warn(
11188
+ { sessionId: at.sessionId, err: err instanceof Error ? err.message : String(err) },
11189
+ "context ledger write failed"
11190
+ );
11191
+ }
11192
+ return false;
11193
+ }
11194
+ var ledgerInsert, MAX_REFS, COUNT_KINDS;
11195
+ var init_ledger = __esm({
11196
+ "src/context/ledger.ts"() {
11197
+ "use strict";
11198
+ init_src();
11199
+ ledgerInsert = (db) => (rows) => db.from("context_ledger").insert(
11200
+ rows.map((r) => ({
11201
+ workspace_id: r.workspace_id,
11202
+ session_id: r.session_id,
11203
+ segment: r.segment,
11204
+ turn_seq: r.turn_seq ?? null,
11205
+ layer: r.layer,
11206
+ refs: r.refs,
11207
+ tokens: r.tokens
11208
+ }))
11209
+ );
11210
+ MAX_REFS = 50;
11211
+ COUNT_KINDS = /* @__PURE__ */ new Set([
11212
+ "activity_private",
11213
+ "person_private",
11214
+ "recall_private"
11215
+ ]);
11216
+ }
11217
+ });
11218
+
11252
11219
  // src/context/recall.ts
11253
11220
  function parseHit(v) {
11254
11221
  if (!isObj2(v)) return null;
@@ -11316,8 +11283,8 @@ function hitLine(hit2, contentChars) {
11316
11283
  const title = text2(hit2.provenance.title);
11317
11284
  return `- ${word(hit2.kind)}${title ? ` ${quoted(title, 160)}` : ""}${meta ? ` (${meta})` : ""}: ${quoted(hit2.content, contentChars)}`;
11318
11285
  }
11319
- function recallBlock(hits, max = RECALL_MAX_TOKENS) {
11320
- if (hits.length === 0) return "";
11286
+ function recallFit(hits, max) {
11287
+ if (hits.length === 0) return { text: "", shown: [] };
11321
11288
  const frame = `${RECALL_PREAMBLE}
11322
11289
  ${RECALL_OPEN}
11323
11290
 
@@ -11326,10 +11293,28 @@ ${RECALL_CLOSE}`;
11326
11293
  hits.map((h) => ({ text: hitLine(h, RECALL_HIT_CHARS) })),
11327
11294
  max - estimateTokens(frame)
11328
11295
  );
11329
- return fit.text ? `${RECALL_PREAMBLE}
11296
+ if (!fit.text) return { text: "", shown: [] };
11297
+ return {
11298
+ text: `${RECALL_PREAMBLE}
11330
11299
  ${RECALL_OPEN}
11331
11300
  ${fit.text}
11332
- ${RECALL_CLOSE}` : "";
11301
+ ${RECALL_CLOSE}`,
11302
+ shown: hits.slice(0, fit.shown.length)
11303
+ };
11304
+ }
11305
+ function hitLedgerRef(hit2) {
11306
+ if (hit2.kind === "fact") return UUID4.test(hit2.id) ? { kind: "fact", id: hit2.id } : null;
11307
+ if (hit2.item_id === null || !UUID4.test(hit2.item_id)) return null;
11308
+ const sourceRef = text2(hit2.provenance.source_ref);
11309
+ return sourceRef !== null && sourceRef.length <= 200 ? { kind: "item", id: hit2.item_id, source_ref: sourceRef } : { kind: "item", id: hit2.item_id };
11310
+ }
11311
+ function recallLedgerRefs(shown) {
11312
+ const named = shown.filter((h) => h.provenance.visibility === "workspace");
11313
+ const hidden = shown.length - named.length;
11314
+ return capRefs([
11315
+ ...named.flatMap((h) => hitLedgerRef(h) ?? []),
11316
+ ...hidden > 0 ? [{ kind: "recall_private", count: hidden }] : []
11317
+ ]);
11333
11318
  }
11334
11319
  function recallQuery(prompt) {
11335
11320
  let rest = prompt.trim();
@@ -11354,11 +11339,12 @@ async function firstPromptRecall(deps, at) {
11354
11339
  const found = await searchMemory(deps.db, { sessionId: at.sessionId, query, scope }, at.signal);
11355
11340
  if (at.signal.aborted || !found.ok) return null;
11356
11341
  const hits = shownHits(found.hits, scope);
11357
- const block = recallBlock(hits);
11342
+ const { text: block, shown } = recallFit(hits, RECALL_MAX_TOKENS);
11358
11343
  if (!block) return null;
11344
+ const refs = recallLedgerRefs(shown);
11359
11345
  return {
11360
11346
  text: block,
11361
- ledger: { layer: "recall", refs: [], tokens: estimateTokens(block), turn_seq: at.turnSeq },
11347
+ ledger: { layer: "recall", refs, tokens: estimateTokens(block), turn_seq: at.turnSeq },
11362
11348
  privateSeen: scope === "all" && holdsPrivate(hits)
11363
11349
  };
11364
11350
  } catch {
@@ -11440,6 +11426,7 @@ var init_recall = __esm({
11440
11426
  init_rotation();
11441
11427
  init_budget();
11442
11428
  init_format();
11429
+ init_ledger();
11443
11430
  RECALL_MAX_TOKENS = 3e3;
11444
11431
  MAX_QUERY_CHARS = 4e3;
11445
11432
  MEMORY_KINDS = [
@@ -12376,7 +12363,7 @@ function asInterrupted(row) {
12376
12363
  )
12377
12364
  };
12378
12365
  }
12379
- var SESSION_FLAG_SETTINGS, SESSION_ASK_TOOLS, GUARD_FAILED, WRITE_TOOLS_MATCHER, CONTEXT_HOOK_TIMEOUT_MS, ASK_TOOL_REASON, CAYENNE_CHECK_MS, CAYENNE_CHECK_POLL_MS, CAYENNE_UNCONFIRMED, TOOL_FAILED, toResult, INTERRUPTED, STOPPED_TURN, ClaudeSession;
12366
+ var SESSION_FLAG_SETTINGS, SESSION_ASK_TOOLS, GUARD_FAILED, WRITE_TOOLS_MATCHER, CONTEXT_HOOK_TIMEOUT_MS, ASK_TOOL_REASON, CAYENNE_CHECK_MS, CAYENNE_CHECK_POLL_MS, CAYENNE_UNLISTED_POLL_MS, CAYENNE_UNCONFIRMED, TOOL_FAILED, toResult, INTERRUPTED, STOPPED_TURN, ClaudeSession;
12380
12367
  var init_session2 = __esm({
12381
12368
  "src/claude/session.ts"() {
12382
12369
  "use strict";
@@ -12399,6 +12386,7 @@ var init_session2 = __esm({
12399
12386
  ASK_TOOL_REASON = "This session was started from a task, so every shell command needs the member's approval.";
12400
12387
  CAYENNE_CHECK_MS = 1e4;
12401
12388
  CAYENNE_CHECK_POLL_MS = 100;
12389
+ CAYENNE_UNLISTED_POLL_MS = 1e3;
12402
12390
  CAYENNE_UNCONFIRMED = "Refused: the cayenne MCP server is not confirmed as the runner\u2019s own.";
12403
12391
  TOOL_FAILED = "Cayenne could not answer this call.";
12404
12392
  toResult = (a) => ({
@@ -12678,15 +12666,21 @@ var init_session2 = __esm({
12678
12666
  *
12679
12667
  * Any other answer stops the session. A `cayenne` with another `serverInfo` or config type is a
12680
12668
  * foreign server (`CAYENNE_NAME_TAKEN`); one never confirmed (connected without `serverInfo`, a
12681
- * status other than `pending`, still pending or unlisted at the deadline, a status call that
12682
- * fails) ends with `CAYENNE_UNCONFIRMED_EXIT`, the reason logged at warn. Until confirmed,
12683
- * `canUseTool` refuses every `cayenne` tool. A session already stopping or ended is left alone.
12669
+ * status other than `pending`, still pending at the deadline, a status call that fails) ends with
12670
+ * `CAYENNE_UNCONFIRMED_EXIT`, the reason logged at warn. Until confirmed, `canUseTool` refuses
12671
+ * every `cayenne` tool. A session already stopping or ended is left alone.
12672
+ *
12673
+ * An unlisted `cayenne` is waited for, not stopped: the CLI answers `[]` before it registers its
12674
+ * MCP servers, and on a slow network that took longer than the window (three chats killed
12675
+ * 2026-10-21/22). Nothing answers under the name meanwhile, so no tool of it can be called.
12684
12676
  */
12685
12677
  async confirmCayenne(q) {
12686
12678
  let deadline;
12679
+ let unlistedSince;
12680
+ let unlistedWarned = false;
12681
+ const warn = this.opts.log?.warn ?? ((obj, m) => logger.warn(obj, m));
12687
12682
  const end = (fatal, fields, msg) => {
12688
12683
  if (this.ending) return false;
12689
- const warn = this.opts.log?.warn ?? ((obj, m) => logger.warn(obj, m));
12690
12684
  warn({ providerSessionId: this.providerSessionId, ...fields }, msg);
12691
12685
  this.fatal = fatal;
12692
12686
  void this.stop();
@@ -12701,21 +12695,33 @@ var init_session2 = __esm({
12701
12695
  for (; ; ) {
12702
12696
  if (this.ending) return false;
12703
12697
  const statuses = await q.mcpServerStatus();
12704
- deadline ??= Date.now() + CAYENNE_CHECK_MS;
12705
12698
  const entry = statuses.find((s) => s.name === CAYENNE_SERVER);
12706
- const config2 = entry?.config;
12707
- const info = entry?.serverInfo;
12699
+ if (!entry) {
12700
+ unlistedSince ??= Date.now();
12701
+ if (!unlistedWarned && Date.now() - unlistedSince >= CAYENNE_CHECK_MS) {
12702
+ unlistedWarned = true;
12703
+ warn(
12704
+ { providerSessionId: this.providerSessionId },
12705
+ "cayenne MCP server not listed yet; its tools stay refused until it is"
12706
+ );
12707
+ }
12708
+ await new Promise((r) => setTimeout(r, CAYENNE_UNLISTED_POLL_MS));
12709
+ continue;
12710
+ }
12711
+ deadline ??= Date.now() + CAYENNE_CHECK_MS;
12712
+ const config2 = entry.config;
12713
+ const info = entry.serverInfo;
12708
12714
  if (config2 !== void 0 && config2.type !== "sdk" || info !== void 0 && (info.name !== CAYENNE_SERVER || info.version !== this.cayenneVersion)) {
12709
- return end(CAYENNE_NAME_TAKEN, { scope: entry?.scope }, CAYENNE_NAME_TAKEN);
12715
+ return end(CAYENNE_NAME_TAKEN, { scope: entry.scope }, CAYENNE_NAME_TAKEN);
12710
12716
  }
12711
- if (entry?.status === "connected") {
12717
+ if (entry.status === "connected") {
12712
12718
  return info !== void 0 || unconfirmed("connected without serverInfo", entry.scope);
12713
12719
  }
12714
- if (entry && entry.status !== "pending") {
12720
+ if (entry.status !== "pending") {
12715
12721
  return unconfirmed(`status ${entry.status}`, entry.scope);
12716
12722
  }
12717
12723
  if (Date.now() >= deadline) {
12718
- return unconfirmed(entry ? "still pending" : "not listed by mcpServerStatus");
12724
+ return unconfirmed("still pending", entry.scope);
12719
12725
  }
12720
12726
  await new Promise((r) => setTimeout(r, CAYENNE_CHECK_POLL_MS));
12721
12727
  }
@@ -13095,64 +13101,6 @@ ${card.data}` : card.instructions;
13095
13101
  }
13096
13102
  });
13097
13103
 
13098
- // src/context/ledger.ts
13099
- function capRefs(refs, max = MAX_REFS) {
13100
- const counts = refs.filter((r) => COUNT_KINDS.has(r.kind)).slice(0, max);
13101
- const ids = refs.filter((r) => !COUNT_KINDS.has(r.kind)).slice(0, max - counts.length);
13102
- return [...ids, ...counts];
13103
- }
13104
- async function writeLedger(insert, at, rows, log) {
13105
- const valid = [];
13106
- for (const row of rows) {
13107
- const parsed = ContextLedgerRow.safeParse({
13108
- workspace_id: at.workspaceId,
13109
- session_id: at.sessionId,
13110
- segment: at.segment,
13111
- turn_seq: row.turn_seq ?? null,
13112
- layer: row.layer,
13113
- refs: capRefs(row.refs),
13114
- tokens: row.tokens
13115
- });
13116
- if (parsed.success) valid.push(parsed.data);
13117
- else log?.warn({ sessionId: at.sessionId, layer: row.layer }, "context ledger row refused");
13118
- }
13119
- if (valid.length === 0) return true;
13120
- try {
13121
- const { error } = await insert(valid);
13122
- if (!error) return true;
13123
- log?.warn({ sessionId: at.sessionId, err: error.message }, "context ledger write failed");
13124
- } catch (err) {
13125
- log?.warn(
13126
- { sessionId: at.sessionId, err: err instanceof Error ? err.message : String(err) },
13127
- "context ledger write failed"
13128
- );
13129
- }
13130
- return false;
13131
- }
13132
- var ledgerInsert, MAX_REFS, COUNT_KINDS;
13133
- var init_ledger = __esm({
13134
- "src/context/ledger.ts"() {
13135
- "use strict";
13136
- init_src();
13137
- ledgerInsert = (db) => (rows) => db.from("context_ledger").insert(
13138
- rows.map((r) => ({
13139
- workspace_id: r.workspace_id,
13140
- session_id: r.session_id,
13141
- segment: r.segment,
13142
- turn_seq: r.turn_seq ?? null,
13143
- layer: r.layer,
13144
- refs: r.refs,
13145
- tokens: r.tokens
13146
- }))
13147
- );
13148
- MAX_REFS = 50;
13149
- COUNT_KINDS = /* @__PURE__ */ new Set([
13150
- "activity_private",
13151
- "person_private"
13152
- ]);
13153
- }
13154
- });
13155
-
13156
13104
  // src/coordination/collision.ts
13157
13105
  function detectCollision(path, scope) {
13158
13106
  const hits = scope.index.collisionsFor(scope.repo, path, scope.writer);
@@ -19121,12 +19069,15 @@ var init_manager = __esm({
19121
19069
  ]);
19122
19070
  }
19123
19071
  /**
19124
- * A `<handoff segment="n">` block handed to segment `n + 1` as its `handoff` row (design §5.7 L2).
19125
- * No ref names the segment yet: `ContextLedgerRef` has no kind for it (protocol §2.10).
19072
+ * A `<handoff segment="n">` block handed to segment `n + 1` as its `handoff` row (design §5.7 L2),
19073
+ * naming segment `n` (`memory_recall`'s `chat_segments:<session>:<n>`, protocol §2.10).
19126
19074
  */
19127
19075
  ledgerHandoff(sessionId, live, block, seq) {
19076
+ const refs = [
19077
+ { kind: "segment", session: sessionId, index: block.segment }
19078
+ ];
19128
19079
  this.writeLedgerRows(sessionId, live.renewal.segment, [
19129
- { layer: "handoff", refs: [], tokens: estimateTokens(block), turn_seq: seq }
19080
+ { layer: "handoff", refs, tokens: estimateTokens(block.text), turn_seq: seq }
19130
19081
  ]);
19131
19082
  }
19132
19083
  /**
@@ -19870,14 +19821,14 @@ var init_manager = __esm({
19870
19821
  if (send === "now") {
19871
19822
  const queued = first !== null && rn.queued[0] === first ? rn.queued.shift() : void 0;
19872
19823
  if (queued && !await this.writeQueued(sessionId, live, queued)) {
19873
- rn.pendingBlock = handoffBlock(n, handoffText);
19824
+ rn.pendingBlock = { text: handoffBlock(n, handoffText), segment: n };
19874
19825
  } else {
19875
19826
  const uuid = queued?.uuid ?? randomUUID5();
19876
19827
  live.echoed.add(uuid);
19877
19828
  this.ledgerHandoff(
19878
19829
  sessionId,
19879
19830
  live,
19880
- handoffBlock(n, handoffText),
19831
+ { text: handoffBlock(n, handoffText), segment: n },
19881
19832
  live.promptSeqs.get(uuid) ?? null
19882
19833
  );
19883
19834
  live.handed.push({ text: text3, seq: live.promptSeqs.get(uuid) ?? null });
@@ -19887,7 +19838,7 @@ var init_manager = __esm({
19887
19838
  this.turnHandedOver(live);
19888
19839
  }
19889
19840
  } else {
19890
- rn.pendingBlock = handoffBlock(n, handoffText);
19841
+ rn.pendingBlock = { text: handoffBlock(n, handoffText), segment: n };
19891
19842
  }
19892
19843
  await this.flushQueued(sessionId, live);
19893
19844
  }
@@ -19965,7 +19916,7 @@ ${text3}`;
19965
19916
  const block = rn.pendingBlock;
19966
19917
  if (block !== null && !isSlashCommand(text3)) {
19967
19918
  rn.pendingBlock = null;
19968
- text3 = `${block}
19919
+ text3 = `${block.text}
19969
19920
 
19970
19921
  ${text3}`;
19971
19922
  this.ledgerHandoff(sessionId, live, block, live.promptSeqs.get(uuid) ?? null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jpcasabiancai/cayenne",
3
- "version": "0.5.11",
3
+ "version": "0.5.13",
4
4
  "files": [
5
5
  "dist/cli.js"
6
6
  ],