@cordfuse/crosstalk 7.0.0-alpha.9 → 7.0.0-beta.1

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 (72) hide show
  1. package/GUIDE-CLI.md +322 -0
  2. package/GUIDE-PROMPTS.md +118 -0
  3. package/LICENSE +21 -0
  4. package/README.md +402 -61
  5. package/bin/crosstalk.js +88 -54
  6. package/commands/agent.js +69 -0
  7. package/commands/auth.js +273 -0
  8. package/commands/channel.js +54 -44
  9. package/commands/chat.js +107 -71
  10. package/commands/daemon.js +120 -0
  11. package/commands/logs.js +108 -19
  12. package/commands/message.js +125 -0
  13. package/commands/server.js +153 -0
  14. package/commands/settings.js +49 -0
  15. package/commands/status.js +37 -13
  16. package/commands/token.js +136 -0
  17. package/commands/transport.js +270 -0
  18. package/commands/version.js +3 -3
  19. package/commands/workflow.js +234 -0
  20. package/deploy/crosstalk@.service +62 -0
  21. package/deploy/install.sh +82 -0
  22. package/lib/api-client.js +77 -22
  23. package/lib/credentials.js +207 -0
  24. package/lib/nativeServer.js +173 -0
  25. package/lib/resolve.js +101 -34
  26. package/package.json +27 -4
  27. package/src/activation.ts +104 -0
  28. package/src/api.ts +1716 -0
  29. package/src/auth/enforce.ts +68 -0
  30. package/src/auth/handlers.ts +266 -0
  31. package/src/auth/middleware.ts +132 -0
  32. package/src/auth/setup.ts +263 -0
  33. package/src/auth/tokens.ts +285 -0
  34. package/src/auth/users.ts +267 -0
  35. package/src/dispatch.ts +492 -0
  36. package/src/dispatchers.ts +91 -0
  37. package/src/filenames.ts +28 -0
  38. package/src/frontmatter.ts +26 -0
  39. package/src/init.ts +116 -0
  40. package/src/invoke.ts +201 -0
  41. package/src/log-buffer.ts +67 -0
  42. package/src/models.ts +283 -0
  43. package/src/resolve.ts +100 -0
  44. package/src/state.ts +190 -0
  45. package/src/stop.ts +37 -0
  46. package/src/transport.ts +243 -0
  47. package/src/web/auth-pages.ts +160 -0
  48. package/src/web/channels.ts +395 -0
  49. package/src/web/chat-page.ts +636 -0
  50. package/src/web/chat-pty.ts +254 -0
  51. package/src/web/dashboard.ts +129 -0
  52. package/src/web/layout.ts +237 -0
  53. package/src/web/stubs.ts +510 -0
  54. package/src/web/workflows.ts +490 -0
  55. package/src/workflow.ts +470 -0
  56. package/template/CLAUDE.md +10 -0
  57. package/template/CROSSTALK-VERSION +1 -0
  58. package/template/CROSSTALK.md +262 -0
  59. package/template/PROTOCOL.md +70 -0
  60. package/template/README.md +64 -0
  61. package/template/auth/.gitkeep +0 -0
  62. package/template/auth/README.md +224 -0
  63. package/template/data/crosstalk.yaml +196 -0
  64. package/template/gitignore +4 -0
  65. package/commands/down.js +0 -40
  66. package/commands/init.js +0 -243
  67. package/commands/pull.js +0 -22
  68. package/commands/replies.js +0 -40
  69. package/commands/restart.js +0 -29
  70. package/commands/rm.js +0 -109
  71. package/commands/run.js +0 -115
  72. package/commands/up.js +0 -135
@@ -0,0 +1,104 @@
1
+ // The activation rule — pure functions, no I/O, exhaustively unit-tested.
2
+ //
3
+ // One rule (CROSSTALK.md "Activation"):
4
+ //
5
+ // A message wakes its addressee if it has no `re:` (a new task), or its
6
+ // `re:` points at a message the addressee sent.
7
+ //
8
+ // `re:` is written by the runtime at send time, never inferred at read
9
+ // time. It is a string or a list: a reply that answers a batch of N
10
+ // messages records ALL N relPaths, so no answered message is ever lost to
11
+ // batching.
12
+
13
+ export interface ActivationMessage {
14
+ from: string;
15
+ to: string[];
16
+ re?: string | string[];
17
+ }
18
+
19
+ export function recipients(toField: unknown): string[] {
20
+ if (Array.isArray(toField)) return toField.map(String);
21
+ if (typeof toField === 'string') return [toField];
22
+ return [];
23
+ }
24
+
25
+ export function reList(reField: unknown): string[] {
26
+ if (Array.isArray(reField)) return reField.map(String);
27
+ if (typeof reField === 'string') return [reField];
28
+ return [];
29
+ }
30
+
31
+ // A recipient is `actor` or `actor@host`.
32
+ export function extractActor(recipient: string): string {
33
+ const at = recipient.indexOf('@');
34
+ return at === -1 ? recipient : recipient.slice(0, at);
35
+ }
36
+
37
+ export function targetHost(recipient: string): string | null {
38
+ const at = recipient.indexOf('@');
39
+ return at === -1 ? null : recipient.slice(at + 1);
40
+ }
41
+
42
+ export interface RoutingResult {
43
+ addressed: boolean;
44
+ // Actor was named, but every instance targeted a different host — logged
45
+ // by the dispatcher so wrong-host routes are visible, never silent.
46
+ wrongHost: boolean;
47
+ }
48
+
49
+ export function matchRouting(
50
+ recipientList: string[],
51
+ actorName: string,
52
+ thisHost: string,
53
+ ): RoutingResult {
54
+ let actorNamedAtAll = false;
55
+ for (const r of recipientList) {
56
+ if (r === 'all') return { addressed: true, wrongHost: false };
57
+ if (extractActor(r) !== actorName) continue;
58
+ actorNamedAtAll = true;
59
+ const host = targetHost(r);
60
+ if (host === null || host === thisHost) return { addressed: true, wrongHost: false };
61
+ }
62
+ return { addressed: false, wrongHost: actorNamedAtAll };
63
+ }
64
+
65
+ export type WakeDecision = 'wake' | 'skip' | 'wrong-host';
66
+
67
+ // `senderOf` resolves a channel relPath to the `from:` of the message there,
68
+ // or undefined if no such message exists. A dangling `re:` entry (target
69
+ // missing) wakes the addressee — fail open so a message is never silently
70
+ // dropped; no loop is possible because the reply to it carries a
71
+ // resolvable `re:`.
72
+ export function decideWake(
73
+ msg: ActivationMessage,
74
+ actorName: string,
75
+ thisHost: string,
76
+ senderOf: (relPath: string) => string | undefined,
77
+ ): WakeDecision {
78
+ const routing = matchRouting(msg.to, actorName, thisHost);
79
+ if (!routing.addressed) return routing.wrongHost ? 'wrong-host' : 'skip';
80
+ if (msg.from === actorName) return 'skip';
81
+ const targets = reList(msg.re);
82
+ if (targets.length === 0) return 'wake';
83
+ for (const target of targets) {
84
+ const asker = senderOf(target);
85
+ if (asker === undefined || asker === actorName) return 'wake';
86
+ }
87
+ return 'skip';
88
+ }
89
+
90
+ // Split a channel's pending messages (already sorted by relPath) into
91
+ // contiguous batches sized for the actor's concurrency. Contiguous so each
92
+ // batch's highest relPath is monotone across batches — the cursor advances
93
+ // safely per batch. When pending fits within concurrency, every batch is a
94
+ // single message (parallel fan-out); when it exceeds, batches collapse into
95
+ // ~concurrency invocations (fan-in stays O(1) per actor).
96
+ export function splitForConcurrency<T>(msgs: T[], concurrency: number): T[][] {
97
+ if (concurrency <= 1 || msgs.length <= 1) return [msgs];
98
+ const chunkSize = Math.max(1, Math.ceil(msgs.length / concurrency));
99
+ const out: T[][] = [];
100
+ for (let i = 0; i < msgs.length; i += chunkSize) {
101
+ out.push(msgs.slice(i, i + chunkSize));
102
+ }
103
+ return out;
104
+ }