@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.
- package/GUIDE-CLI.md +322 -0
- package/GUIDE-PROMPTS.md +118 -0
- package/LICENSE +21 -0
- package/README.md +402 -61
- package/bin/crosstalk.js +88 -54
- package/commands/agent.js +69 -0
- package/commands/auth.js +273 -0
- package/commands/channel.js +54 -44
- package/commands/chat.js +107 -71
- package/commands/daemon.js +120 -0
- package/commands/logs.js +108 -19
- package/commands/message.js +125 -0
- package/commands/server.js +153 -0
- package/commands/settings.js +49 -0
- package/commands/status.js +37 -13
- package/commands/token.js +136 -0
- package/commands/transport.js +270 -0
- package/commands/version.js +3 -3
- package/commands/workflow.js +234 -0
- package/deploy/crosstalk@.service +62 -0
- package/deploy/install.sh +82 -0
- package/lib/api-client.js +77 -22
- package/lib/credentials.js +207 -0
- package/lib/nativeServer.js +173 -0
- package/lib/resolve.js +101 -34
- package/package.json +27 -4
- package/src/activation.ts +104 -0
- package/src/api.ts +1716 -0
- package/src/auth/enforce.ts +68 -0
- package/src/auth/handlers.ts +266 -0
- package/src/auth/middleware.ts +132 -0
- package/src/auth/setup.ts +263 -0
- package/src/auth/tokens.ts +285 -0
- package/src/auth/users.ts +267 -0
- package/src/dispatch.ts +492 -0
- package/src/dispatchers.ts +91 -0
- package/src/filenames.ts +28 -0
- package/src/frontmatter.ts +26 -0
- package/src/init.ts +116 -0
- package/src/invoke.ts +201 -0
- package/src/log-buffer.ts +67 -0
- package/src/models.ts +283 -0
- package/src/resolve.ts +100 -0
- package/src/state.ts +190 -0
- package/src/stop.ts +37 -0
- package/src/transport.ts +243 -0
- package/src/web/auth-pages.ts +160 -0
- package/src/web/channels.ts +395 -0
- package/src/web/chat-page.ts +636 -0
- package/src/web/chat-pty.ts +254 -0
- package/src/web/dashboard.ts +129 -0
- package/src/web/layout.ts +237 -0
- package/src/web/stubs.ts +510 -0
- package/src/web/workflows.ts +490 -0
- package/src/workflow.ts +470 -0
- package/template/CLAUDE.md +10 -0
- package/template/CROSSTALK-VERSION +1 -0
- package/template/CROSSTALK.md +262 -0
- package/template/PROTOCOL.md +70 -0
- package/template/README.md +64 -0
- package/template/auth/.gitkeep +0 -0
- package/template/auth/README.md +224 -0
- package/template/data/crosstalk.yaml +196 -0
- package/template/gitignore +4 -0
- package/commands/down.js +0 -40
- package/commands/init.js +0 -243
- package/commands/pull.js +0 -22
- package/commands/replies.js +0 -40
- package/commands/restart.js +0 -29
- package/commands/rm.js +0 -109
- package/commands/run.js +0 -115
- 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
|
+
}
|