@cotal-ai/core 0.1.0
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/LICENSE +202 -0
- package/dist/agent-file.d.ts +24 -0
- package/dist/agent-file.d.ts.map +1 -0
- package/dist/agent-file.js +106 -0
- package/dist/agent-file.js.map +1 -0
- package/dist/command.d.ts +15 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +2 -0
- package/dist/command.js.map +1 -0
- package/dist/connector.d.ts +43 -0
- package/dist/connector.d.ts.map +1 -0
- package/dist/connector.js +2 -0
- package/dist/connector.js.map +1 -0
- package/dist/endpoint.d.ts +193 -0
- package/dist/endpoint.d.ts.map +1 -0
- package/dist/endpoint.js +628 -0
- package/dist/endpoint.js.map +1 -0
- package/dist/identity.d.ts +26 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +29 -0
- package/dist/identity.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/link.d.ts +37 -0
- package/dist/link.d.ts.map +1 -0
- package/dist/link.js +39 -0
- package/dist/link.js.map +1 -0
- package/dist/provision.d.ts +68 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +204 -0
- package/dist/provision.js.map +1 -0
- package/dist/registry.d.ts +24 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +26 -0
- package/dist/registry.js.map +1 -0
- package/dist/streams.d.ts +47 -0
- package/dist/streams.d.ts.map +1 -0
- package/dist/streams.js +97 -0
- package/dist/streams.js.map +1 -0
- package/dist/subjects.d.ts +83 -0
- package/dist/subjects.d.ts.map +1 -0
- package/dist/subjects.js +186 -0
- package/dist/subjects.js.map +1 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +31 -0
package/dist/streams.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { jetstreamManager, AckPolicy, DeliverPolicy, RetentionPolicy, DiscardPolicy, StorageType, } from "@nats-io/jetstream";
|
|
2
|
+
import { connect, credsAuthenticator, nanos } from "@nats-io/transport-node";
|
|
3
|
+
import { Kvm } from "@nats-io/kv";
|
|
4
|
+
import { spacePrefix, chatStream, dmStream, dmDurable, unicastSubject, taskStream, taskDurable, anycastSubject, presenceBucket, } from "./subjects.js";
|
|
5
|
+
/** Default presence-bucket entry TTL (ms) — matches the endpoint's default liveness window. */
|
|
6
|
+
const PRESENCE_TTL_MS = 6_000;
|
|
7
|
+
/**
|
|
8
|
+
* Create (idempotently) the three backing streams for a space — CHAT (multicast backlog +
|
|
9
|
+
* history), DM (per-instance inboxes), TASK (anycast work queue).
|
|
10
|
+
*
|
|
11
|
+
* This is **privileged**: under auth mode `STREAM.CREATE` is denied to regular agents
|
|
12
|
+
* (streams are space infrastructure, not per-agent), so it runs once at setup
|
|
13
|
+
* (`cotal up`) or from a permissive endpoint. The single source of the stream
|
|
14
|
+
* definitions, shared by the endpoint and the setup path so they can't diverge.
|
|
15
|
+
*/
|
|
16
|
+
export async function createSpaceStreams(jsm, space) {
|
|
17
|
+
const p = spacePrefix(space);
|
|
18
|
+
await jsm.streams.add({
|
|
19
|
+
name: chatStream(space),
|
|
20
|
+
subjects: [`${p}.chat.>`],
|
|
21
|
+
retention: RetentionPolicy.Limits,
|
|
22
|
+
storage: StorageType.File,
|
|
23
|
+
max_msgs_per_subject: 1000, // capped per-channel backlog (buffer + history)
|
|
24
|
+
discard: DiscardPolicy.Old,
|
|
25
|
+
});
|
|
26
|
+
await jsm.streams.add({
|
|
27
|
+
name: dmStream(space),
|
|
28
|
+
subjects: [`${p}.inst.>`],
|
|
29
|
+
retention: RetentionPolicy.Limits,
|
|
30
|
+
storage: StorageType.File,
|
|
31
|
+
});
|
|
32
|
+
await jsm.streams.add({
|
|
33
|
+
name: taskStream(space),
|
|
34
|
+
subjects: [`${p}.svc.>`],
|
|
35
|
+
retention: RetentionPolicy.Workqueue,
|
|
36
|
+
storage: StorageType.File,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The DM inbox durable for an instance — ONE definition, used both by the privileged
|
|
41
|
+
* pre-create (manager/provisioner, auth mode) and the endpoint's open-mode self-create, so
|
|
42
|
+
* an idempotent re-add can never error on a config delta. The `filter_subject` binds the
|
|
43
|
+
* durable to inst.<id>.* — only the privileged creator sets it, which is the whole point:
|
|
44
|
+
* an agent can't create a durable filtered to someone else's inbox.
|
|
45
|
+
*
|
|
46
|
+
* `inactive_threshold` is set ONLY when the caller passes one — i.e. the open-mode
|
|
47
|
+
* self-create, where the agent owns the durable and a threshold cleanly retires its inbox
|
|
48
|
+
* after it departs. The privileged auth pre-create OMITS it: the agent BINDS-only and is
|
|
49
|
+
* denied CONSUMER.CREATE, so a threshold would retire the durable before a late/relaunched
|
|
50
|
+
* agent binds it, and the bind would then fail permanently ("consumer not found"). Persisting
|
|
51
|
+
* it is the price of bind-only; explicit cleanup on agent-stop is a follow-up.
|
|
52
|
+
*/
|
|
53
|
+
export function dmDurableConfig(space, id, opts = {}) {
|
|
54
|
+
const cfg = {
|
|
55
|
+
durable_name: dmDurable(id),
|
|
56
|
+
filter_subject: unicastSubject(space, id, "*"),
|
|
57
|
+
ack_policy: AckPolicy.Explicit,
|
|
58
|
+
ack_wait: nanos(opts.ackWaitMs ?? 60_000),
|
|
59
|
+
deliver_policy: DeliverPolicy.All,
|
|
60
|
+
};
|
|
61
|
+
if (opts.inactiveThresholdMs)
|
|
62
|
+
cfg.inactive_threshold = nanos(opts.inactiveThresholdMs);
|
|
63
|
+
return cfg;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* The TASK work-queue durable for a role — ONE definition, shared by the privileged
|
|
67
|
+
* pre-create (auth mode) and the endpoint's open-mode self-create. The durable is shared
|
|
68
|
+
* across all instances of a role (queue group); the privileged creator sets the
|
|
69
|
+
* filter_subject to svc.<role>.* so an agent can't bind a consumer filtered to another
|
|
70
|
+
* role's queue (the same create-time-filter attack surface as DM). Idempotent per role.
|
|
71
|
+
*/
|
|
72
|
+
export function taskDurableConfig(space, role, opts = {}) {
|
|
73
|
+
return {
|
|
74
|
+
durable_name: taskDurable(role),
|
|
75
|
+
filter_subject: anycastSubject(space, role, "*"),
|
|
76
|
+
ack_policy: AckPolicy.Explicit,
|
|
77
|
+
ack_wait: nanos(opts.ackWaitMs ?? 60_000),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Connect with the given (privileged) creds, create the space's streams, and disconnect.
|
|
81
|
+
* Used by `cotal up` to pre-create streams once at setup. */
|
|
82
|
+
export async function setupSpaceStreams(opts) {
|
|
83
|
+
const nc = await connect({
|
|
84
|
+
servers: opts.servers,
|
|
85
|
+
authenticator: credsAuthenticator(new TextEncoder().encode(opts.creds)),
|
|
86
|
+
});
|
|
87
|
+
try {
|
|
88
|
+
await createSpaceStreams(await jetstreamManager(nc), opts.space);
|
|
89
|
+
// The presence KV bucket is a stream too — pre-create it so agents (denied KV
|
|
90
|
+
// stream-create) can open it. Idempotent.
|
|
91
|
+
await new Kvm(nc).create(presenceBucket(opts.space), { ttl: PRESENCE_TTL_MS });
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
await nc.drain();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=streams.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streams.js","sourceRoot":"","sources":["../src/streams.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subject naming — the routing half of the wire contract (v0).
|
|
3
|
+
*
|
|
4
|
+
* cotal.<space>.chat.<channel> multicast to a channel (dotted + hierarchical: team.backend, subscribe team.>)
|
|
5
|
+
* cotal.<space>.svc.<service> anycast to any one instance of a service (queue group)
|
|
6
|
+
* cotal.<space>.inst.<instance> unicast to one specific instance
|
|
7
|
+
* cotal.<space>.ctl.<service> control request/reply to a service (e.g. manager)
|
|
8
|
+
* cotal.<space>.trace.<instance> ambient lifecycle trace (later)
|
|
9
|
+
* cotal.<space>.control.<instance> control-plane commands (later)
|
|
10
|
+
*
|
|
11
|
+
* Presence lives in a JetStream KV bucket, not a subject (see presenceBucket()).
|
|
12
|
+
*/
|
|
13
|
+
/** Make a string safe to use as a single NATS subject token. */
|
|
14
|
+
export declare function token(s: string): string;
|
|
15
|
+
export declare const ROOT = "cotal";
|
|
16
|
+
export declare function spacePrefix(space: string): string;
|
|
17
|
+
/** Canonicalize a `mentions` list for the wire: trim, lowercase, drop empties, dedupe.
|
|
18
|
+
* Returns `undefined` for an empty result so the field is omitted rather than sent as `[]`.
|
|
19
|
+
* Presence-agnostic (no roster lookup) — validation lives in the connector. */
|
|
20
|
+
export declare function normalizeMentions(mentions?: string[]): string[] | undefined;
|
|
21
|
+
export declare function chatSubject(space: string, sender: string, channel: string): string;
|
|
22
|
+
/** True if a channel names a concrete sub-channel (no `*`/`>`) — i.e. it can be
|
|
23
|
+
* *published* to. Subscriptions may be wildcard; publishes must be concrete. */
|
|
24
|
+
export declare function isConcreteChannel(channel: string): boolean;
|
|
25
|
+
/** Drop exact duplicates and any subject subsumed by a more-general one — JetStream
|
|
26
|
+
* rejects a consumer whose `filter_subjects` overlap, so `[team.>, team.backend]`
|
|
27
|
+
* must collapse to `[team.>]` before binding the chat consumer. */
|
|
28
|
+
export declare function collapseFilterSubjects(subjects: string[]): string[];
|
|
29
|
+
/** Unicast: a specific instance's inbox, tagged with the sender. (Either position may be
|
|
30
|
+
* `*` for subscribe/allow rules: `inst.<myId>.*` to receive, `inst.*.<myId>` to send as me.) */
|
|
31
|
+
export declare function unicastSubject(space: string, target: string, sender: string): string;
|
|
32
|
+
/** Anycast: a service (role), tagged with the sender. Subscribers join a queue group so one instance receives. */
|
|
33
|
+
export declare function anycastSubject(space: string, service: string, sender: string): string;
|
|
34
|
+
/** Control request/reply to a service (e.g. the manager), tagged with the sender; anycast via queue group. */
|
|
35
|
+
export declare function controlServiceSubject(space: string, service: string, sender: string): string;
|
|
36
|
+
export declare function traceSubject(space: string, agentId: string): string;
|
|
37
|
+
export declare function controlSubject(space: string, agentId: string): string;
|
|
38
|
+
/** Wildcard matching every subject within a space. */
|
|
39
|
+
export declare function spaceWildcard(space: string): string;
|
|
40
|
+
/** Wildcard matching every chat (multicast) subject in a space — the read surface an
|
|
41
|
+
* observer is allowed (DM/anycast stay confidential). */
|
|
42
|
+
export declare function chatWildcard(space: string): string;
|
|
43
|
+
/** The three peer-message delivery modes (control/trace/presence are not deliveries). */
|
|
44
|
+
export type DeliveryMode = "chat" | "anycast" | "unicast";
|
|
45
|
+
/** A subject parsed into its routing parts. `sender` is the publishing agent's id;
|
|
46
|
+
* `rest` is the channel (chat) or the routed target/role/service (inst/svc/ctl). */
|
|
47
|
+
export interface ParsedSubject {
|
|
48
|
+
kind: "chat" | "inst" | "svc" | "ctl";
|
|
49
|
+
sender: string;
|
|
50
|
+
/** chat → channel (possibly hierarchical); inst → target; svc → role; ctl → service. */
|
|
51
|
+
rest: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The single authority on the subject layout — every reader of a wire subject goes
|
|
55
|
+
* through this, so the sender-position asymmetry lives in exactly one place:
|
|
56
|
+
* chat.<sender>.<channel…> sender at [3], channel is everything after
|
|
57
|
+
* inst.<target>.<sender> sender at [4]
|
|
58
|
+
* svc.<role>.<sender> sender at [4]
|
|
59
|
+
* ctl.<service>.<sender> sender at [4]
|
|
60
|
+
* Validates the prefix and per-kind shape first and returns `null` on anything else,
|
|
61
|
+
* so a malformed subject can never be read as if it carried a sender.
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseSubject(subject: string): ParsedSubject | null;
|
|
64
|
+
/**
|
|
65
|
+
* Classify a subject's delivery mode, or `null` for control/trace/etc. A thin map over
|
|
66
|
+
* {@link parseSubject}. Observers (e.g. a feed) use this instead of re-parsing the layout.
|
|
67
|
+
*/
|
|
68
|
+
export declare function deliveryOf(subject: string): DeliveryMode | null;
|
|
69
|
+
/** Name of the KV bucket holding presence for a space. */
|
|
70
|
+
export declare function presenceBucket(space: string): string;
|
|
71
|
+
/** Stream capturing `chat.>` — multicast backlog + history. */
|
|
72
|
+
export declare function chatStream(space: string): string;
|
|
73
|
+
/** Stream capturing `inst.>` — per-instance direct-message inboxes. */
|
|
74
|
+
export declare function dmStream(space: string): string;
|
|
75
|
+
/** Stream capturing `svc.>` — anycast work queue. */
|
|
76
|
+
export declare function taskStream(space: string): string;
|
|
77
|
+
/** Durable consumer name for an instance's view of the chat stream. */
|
|
78
|
+
export declare function chatDurable(instance: string): string;
|
|
79
|
+
/** Durable consumer name for an instance's private DM inbox. */
|
|
80
|
+
export declare function dmDurable(instance: string): string;
|
|
81
|
+
/** Durable consumer name (shared across instances of a role) for the task queue. */
|
|
82
|
+
export declare function taskDurable(service: string): string;
|
|
83
|
+
//# sourceMappingURL=subjects.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subjects.d.ts","sourceRoot":"","sources":["../src/subjects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,gEAAgE;AAChE,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAGvC;AAED,eAAO,MAAM,IAAI,UAAU,CAAC;AAE5B,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED;;gFAEgF;AAChF,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,GAAG,SAAS,CAI3E;AAgCD,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAElF;AAED;iFACiF;AACjF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE1D;AAeD;;oEAEoE;AACpE,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAGnE;AAED;iGACiG;AACjG,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED,kHAAkH;AAClH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAErF;AAED,8GAA8G;AAC9G,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAE5F;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAErE;AAED,sDAAsD;AACtD,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;0DAC0D;AAC1D,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,yFAAyF;AACzF,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;AAE1D;qFACqF;AACrF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,wFAAwF;IACxF,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAalE;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAI/D;AAED,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAID,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,uEAAuE;AACvE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,gEAAgE;AAChE,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD"}
|
package/dist/subjects.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subject naming — the routing half of the wire contract (v0).
|
|
3
|
+
*
|
|
4
|
+
* cotal.<space>.chat.<channel> multicast to a channel (dotted + hierarchical: team.backend, subscribe team.>)
|
|
5
|
+
* cotal.<space>.svc.<service> anycast to any one instance of a service (queue group)
|
|
6
|
+
* cotal.<space>.inst.<instance> unicast to one specific instance
|
|
7
|
+
* cotal.<space>.ctl.<service> control request/reply to a service (e.g. manager)
|
|
8
|
+
* cotal.<space>.trace.<instance> ambient lifecycle trace (later)
|
|
9
|
+
* cotal.<space>.control.<instance> control-plane commands (later)
|
|
10
|
+
*
|
|
11
|
+
* Presence lives in a JetStream KV bucket, not a subject (see presenceBucket()).
|
|
12
|
+
*/
|
|
13
|
+
const ILLEGAL = /[^A-Za-z0-9_-]/g;
|
|
14
|
+
/** Make a string safe to use as a single NATS subject token. */
|
|
15
|
+
export function token(s) {
|
|
16
|
+
const t = s.trim().replace(ILLEGAL, "_");
|
|
17
|
+
return t.length > 0 ? t : "_";
|
|
18
|
+
}
|
|
19
|
+
export const ROOT = "cotal";
|
|
20
|
+
export function spacePrefix(space) {
|
|
21
|
+
return `${ROOT}.${token(space)}`;
|
|
22
|
+
}
|
|
23
|
+
/** Canonicalize a `mentions` list for the wire: trim, lowercase, drop empties, dedupe.
|
|
24
|
+
* Returns `undefined` for an empty result so the field is omitted rather than sent as `[]`.
|
|
25
|
+
* Presence-agnostic (no roster lookup) — validation lives in the connector. */
|
|
26
|
+
export function normalizeMentions(mentions) {
|
|
27
|
+
if (!mentions?.length)
|
|
28
|
+
return undefined;
|
|
29
|
+
const out = [...new Set(mentions.map((m) => m.trim().toLowerCase()).filter((m) => m.length > 0))];
|
|
30
|
+
return out.length ? out : undefined;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Build the channel portion of a chat subject, preserving NATS hierarchy: split on
|
|
34
|
+
* `.`, sanitize each segment to a safe token, but keep whole-segment wildcards
|
|
35
|
+
* (`*` = one level, `>` = the rest). So `team.backend` → `team.backend` (a
|
|
36
|
+
* sub-channel) and `team.>` → `team.>` (its whole subtree). `>` is only legal as
|
|
37
|
+
* the final segment; empty segments are dropped.
|
|
38
|
+
*/
|
|
39
|
+
function channelPath(channel) {
|
|
40
|
+
const segs = channel.split(".").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
41
|
+
if (segs.length === 0)
|
|
42
|
+
return "_";
|
|
43
|
+
return segs
|
|
44
|
+
.map((s, i) => {
|
|
45
|
+
if (s === ">") {
|
|
46
|
+
if (i !== segs.length - 1)
|
|
47
|
+
throw new Error(`channel "${channel}": '>' is only valid as the last segment`);
|
|
48
|
+
return ">";
|
|
49
|
+
}
|
|
50
|
+
return s === "*" ? "*" : token(s);
|
|
51
|
+
})
|
|
52
|
+
.join(".");
|
|
53
|
+
}
|
|
54
|
+
/** A routing token (sender, target, role, service), preserving the literal `*` wildcard
|
|
55
|
+
* used on the subscribe/allow side but sanitizing everything else. A no-op on real ids
|
|
56
|
+
* (nkey public keys are base32 [A-Z0-9]) and equal to `token()` on every concrete value —
|
|
57
|
+
* it only additionally lets `*` through, e.g. for `inst.*.<id>` / `svc.*.<id>` allow rules. */
|
|
58
|
+
function routeToken(s) {
|
|
59
|
+
return s === "*" ? "*" : token(s);
|
|
60
|
+
}
|
|
61
|
+
export function chatSubject(space, sender, channel) {
|
|
62
|
+
return `${spacePrefix(space)}.chat.${routeToken(sender)}.${channelPath(channel)}`;
|
|
63
|
+
}
|
|
64
|
+
/** True if a channel names a concrete sub-channel (no `*`/`>`) — i.e. it can be
|
|
65
|
+
* *published* to. Subscriptions may be wildcard; publishes must be concrete. */
|
|
66
|
+
export function isConcreteChannel(channel) {
|
|
67
|
+
return !channel.split(".").some((s) => s.trim() === "*" || s.trim() === ">");
|
|
68
|
+
}
|
|
69
|
+
/** Does NATS subject `pattern` (with `*`/`>`) match `subject`? */
|
|
70
|
+
function subjectMatches(pattern, subject) {
|
|
71
|
+
const p = pattern.split(".");
|
|
72
|
+
const s = subject.split(".");
|
|
73
|
+
for (let i = 0; i < p.length; i++) {
|
|
74
|
+
if (p[i] === ">")
|
|
75
|
+
return true; // matches all remaining tokens
|
|
76
|
+
if (i >= s.length)
|
|
77
|
+
return false;
|
|
78
|
+
if (p[i] === "*")
|
|
79
|
+
continue;
|
|
80
|
+
if (p[i] !== s[i])
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
return p.length === s.length;
|
|
84
|
+
}
|
|
85
|
+
/** Drop exact duplicates and any subject subsumed by a more-general one — JetStream
|
|
86
|
+
* rejects a consumer whose `filter_subjects` overlap, so `[team.>, team.backend]`
|
|
87
|
+
* must collapse to `[team.>]` before binding the chat consumer. */
|
|
88
|
+
export function collapseFilterSubjects(subjects) {
|
|
89
|
+
const uniq = [...new Set(subjects)];
|
|
90
|
+
return uniq.filter((x) => !uniq.some((y) => y !== x && subjectMatches(y, x)));
|
|
91
|
+
}
|
|
92
|
+
/** Unicast: a specific instance's inbox, tagged with the sender. (Either position may be
|
|
93
|
+
* `*` for subscribe/allow rules: `inst.<myId>.*` to receive, `inst.*.<myId>` to send as me.) */
|
|
94
|
+
export function unicastSubject(space, target, sender) {
|
|
95
|
+
return `${spacePrefix(space)}.inst.${routeToken(target)}.${routeToken(sender)}`;
|
|
96
|
+
}
|
|
97
|
+
/** Anycast: a service (role), tagged with the sender. Subscribers join a queue group so one instance receives. */
|
|
98
|
+
export function anycastSubject(space, service, sender) {
|
|
99
|
+
return `${spacePrefix(space)}.svc.${routeToken(service)}.${routeToken(sender)}`;
|
|
100
|
+
}
|
|
101
|
+
/** Control request/reply to a service (e.g. the manager), tagged with the sender; anycast via queue group. */
|
|
102
|
+
export function controlServiceSubject(space, service, sender) {
|
|
103
|
+
return `${spacePrefix(space)}.ctl.${routeToken(service)}.${routeToken(sender)}`;
|
|
104
|
+
}
|
|
105
|
+
export function traceSubject(space, agentId) {
|
|
106
|
+
return `${spacePrefix(space)}.trace.${token(agentId)}`;
|
|
107
|
+
}
|
|
108
|
+
export function controlSubject(space, agentId) {
|
|
109
|
+
return `${spacePrefix(space)}.control.${token(agentId)}`;
|
|
110
|
+
}
|
|
111
|
+
/** Wildcard matching every subject within a space. */
|
|
112
|
+
export function spaceWildcard(space) {
|
|
113
|
+
return `${spacePrefix(space)}.>`;
|
|
114
|
+
}
|
|
115
|
+
/** Wildcard matching every chat (multicast) subject in a space — the read surface an
|
|
116
|
+
* observer is allowed (DM/anycast stay confidential). */
|
|
117
|
+
export function chatWildcard(space) {
|
|
118
|
+
return `${spacePrefix(space)}.chat.>`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The single authority on the subject layout — every reader of a wire subject goes
|
|
122
|
+
* through this, so the sender-position asymmetry lives in exactly one place:
|
|
123
|
+
* chat.<sender>.<channel…> sender at [3], channel is everything after
|
|
124
|
+
* inst.<target>.<sender> sender at [4]
|
|
125
|
+
* svc.<role>.<sender> sender at [4]
|
|
126
|
+
* ctl.<service>.<sender> sender at [4]
|
|
127
|
+
* Validates the prefix and per-kind shape first and returns `null` on anything else,
|
|
128
|
+
* so a malformed subject can never be read as if it carried a sender.
|
|
129
|
+
*/
|
|
130
|
+
export function parseSubject(subject) {
|
|
131
|
+
const parts = subject.split(".");
|
|
132
|
+
if (parts[0] !== ROOT)
|
|
133
|
+
return null; // cotal.<space>.<kind>.…
|
|
134
|
+
const kind = parts[2];
|
|
135
|
+
if (kind === "chat") {
|
|
136
|
+
if (parts.length < 5)
|
|
137
|
+
return null; // cotal.<space>.chat.<sender>.<channel…>
|
|
138
|
+
return { kind, sender: parts[3], rest: parts.slice(4).join(".") };
|
|
139
|
+
}
|
|
140
|
+
if (kind === "inst" || kind === "svc" || kind === "ctl") {
|
|
141
|
+
if (parts.length !== 5)
|
|
142
|
+
return null; // cotal.<space>.<kind>.<route>.<sender>
|
|
143
|
+
return { kind, sender: parts[4], rest: parts[3] };
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Classify a subject's delivery mode, or `null` for control/trace/etc. A thin map over
|
|
149
|
+
* {@link parseSubject}. Observers (e.g. a feed) use this instead of re-parsing the layout.
|
|
150
|
+
*/
|
|
151
|
+
export function deliveryOf(subject) {
|
|
152
|
+
const p = parseSubject(subject);
|
|
153
|
+
if (!p)
|
|
154
|
+
return null;
|
|
155
|
+
return p.kind === "chat" ? "chat" : p.kind === "svc" ? "anycast" : p.kind === "inst" ? "unicast" : null;
|
|
156
|
+
}
|
|
157
|
+
/** Name of the KV bucket holding presence for a space. */
|
|
158
|
+
export function presenceBucket(space) {
|
|
159
|
+
return `cotal_presence_${token(space)}`;
|
|
160
|
+
}
|
|
161
|
+
// ---- JetStream streams (the durable backing for the three delivery modes) ----
|
|
162
|
+
/** Stream capturing `chat.>` — multicast backlog + history. */
|
|
163
|
+
export function chatStream(space) {
|
|
164
|
+
return `CHAT_${token(space)}`;
|
|
165
|
+
}
|
|
166
|
+
/** Stream capturing `inst.>` — per-instance direct-message inboxes. */
|
|
167
|
+
export function dmStream(space) {
|
|
168
|
+
return `DM_${token(space)}`;
|
|
169
|
+
}
|
|
170
|
+
/** Stream capturing `svc.>` — anycast work queue. */
|
|
171
|
+
export function taskStream(space) {
|
|
172
|
+
return `TASK_${token(space)}`;
|
|
173
|
+
}
|
|
174
|
+
/** Durable consumer name for an instance's view of the chat stream. */
|
|
175
|
+
export function chatDurable(instance) {
|
|
176
|
+
return `chat_${token(instance)}`;
|
|
177
|
+
}
|
|
178
|
+
/** Durable consumer name for an instance's private DM inbox. */
|
|
179
|
+
export function dmDurable(instance) {
|
|
180
|
+
return `dm_${token(instance)}`;
|
|
181
|
+
}
|
|
182
|
+
/** Durable consumer name (shared across instances of a role) for the task queue. */
|
|
183
|
+
export function taskDurable(service) {
|
|
184
|
+
return `svc_${token(service)}`;
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=subjects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subjects.js","sourceRoot":"","sources":["../src/subjects.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cotal wire types (v0).
|
|
3
|
+
*
|
|
4
|
+
* These are the shapes that travel on the mesh. They are intentionally A2A-inspired
|
|
5
|
+
* (AgentCard / Message / Part) but transport-agnostic. This file IS part of the
|
|
6
|
+
* "wire contract" — treat changes here as protocol changes.
|
|
7
|
+
*/
|
|
8
|
+
export type EndpointKind = "agent" | "endpoint";
|
|
9
|
+
export interface AgentSkill {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
}
|
|
14
|
+
/** A2A-inspired identity record for an endpoint or agent. */
|
|
15
|
+
export interface AgentCard {
|
|
16
|
+
/** Unique, stable for the lifetime of this connection. */
|
|
17
|
+
id: string;
|
|
18
|
+
/** Human-readable display name. */
|
|
19
|
+
name: string;
|
|
20
|
+
/** 'agent' (participates in coordination) or a plain 'endpoint' (logger, dashboard…). */
|
|
21
|
+
kind: EndpointKind;
|
|
22
|
+
/** Cotal addition: the role this participant plays (planner, reviewer, …). */
|
|
23
|
+
role?: string;
|
|
24
|
+
/** A2A-style one-line summary of what this agent does (discovery / observability). */
|
|
25
|
+
description?: string;
|
|
26
|
+
/** Cotal: free-form "what it can do" tags (A2A skill-tags, flattened) — discovery only. */
|
|
27
|
+
tags?: string[];
|
|
28
|
+
skills?: AgentSkill[];
|
|
29
|
+
meta?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Lifecycle status of a participant.
|
|
33
|
+
* - `idle`: connected, no active task
|
|
34
|
+
* - `waiting`: blocked — awaiting input, approval, or a peer
|
|
35
|
+
* - `working`: actively executing a task / in a turn
|
|
36
|
+
* - `offline`: disconnected or heartbeat lapsed (derived by observers, not self-set while live)
|
|
37
|
+
*/
|
|
38
|
+
export type PresenceStatus = "idle" | "waiting" | "working" | "offline";
|
|
39
|
+
/** Live presence record. Stored in the space's KV bucket under key = card.id. */
|
|
40
|
+
export interface Presence {
|
|
41
|
+
card: AgentCard;
|
|
42
|
+
status: PresenceStatus;
|
|
43
|
+
/** Freeform "what I'm doing right now". */
|
|
44
|
+
activity?: string;
|
|
45
|
+
/** Epoch ms of the last heartbeat. */
|
|
46
|
+
ts: number;
|
|
47
|
+
}
|
|
48
|
+
export type Part = {
|
|
49
|
+
kind: "text";
|
|
50
|
+
text: string;
|
|
51
|
+
} | {
|
|
52
|
+
kind: "data";
|
|
53
|
+
data: unknown;
|
|
54
|
+
};
|
|
55
|
+
export interface EndpointRef {
|
|
56
|
+
id: string;
|
|
57
|
+
name: string;
|
|
58
|
+
role?: string;
|
|
59
|
+
}
|
|
60
|
+
/** A message on the mesh (chat / direct message for now; extensible to other families). */
|
|
61
|
+
export interface CotalMessage {
|
|
62
|
+
/** Unique message id. */
|
|
63
|
+
id: string;
|
|
64
|
+
/** Epoch ms. */
|
|
65
|
+
ts: number;
|
|
66
|
+
space: string;
|
|
67
|
+
from: EndpointRef;
|
|
68
|
+
/** Channel name — multicast (broadcast to everyone on the channel). */
|
|
69
|
+
channel?: string;
|
|
70
|
+
/** Instance id — unicast (direct to one specific endpoint). */
|
|
71
|
+
to?: string;
|
|
72
|
+
/** Service / role — anycast (any one instance of the service receives it). */
|
|
73
|
+
toService?: string;
|
|
74
|
+
/** Lowercased peer names called out within a `channel` message — a priority/wake hint,
|
|
75
|
+
* not a routing target: the message still multicasts to the whole channel. Omitted when
|
|
76
|
+
* empty. */
|
|
77
|
+
mentions?: string[];
|
|
78
|
+
parts: Part[];
|
|
79
|
+
/** Id of the message being replied to. */
|
|
80
|
+
replyTo?: string;
|
|
81
|
+
/** Conversation / thread correlation id. */
|
|
82
|
+
contextId?: string;
|
|
83
|
+
}
|
|
84
|
+
export type PresenceEvent = {
|
|
85
|
+
type: "join";
|
|
86
|
+
presence: Presence;
|
|
87
|
+
} | {
|
|
88
|
+
type: "update";
|
|
89
|
+
presence: Presence;
|
|
90
|
+
} | {
|
|
91
|
+
type: "offline";
|
|
92
|
+
presence: Presence;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Delivery control handed to "message" listeners alongside each {@link CotalMessage}.
|
|
96
|
+
* The message stays on its JetStream stream until {@link Delivery.ack} is called — so
|
|
97
|
+
* ack ONLY once the message has actually been surfaced (printed, injected, handled).
|
|
98
|
+
* A crash before ack redelivers it.
|
|
99
|
+
*/
|
|
100
|
+
export interface Delivery {
|
|
101
|
+
/** Mark the message handled; advances this reader's bookmark so it won't redeliver. */
|
|
102
|
+
ack(): void;
|
|
103
|
+
/** Decline for now; the message redelivers (e.g. couldn't surface it yet). */
|
|
104
|
+
nak(): void;
|
|
105
|
+
}
|
|
106
|
+
/** Control-plane request/reply (e.g. CLI → manager). */
|
|
107
|
+
export interface ControlRequest {
|
|
108
|
+
op: string;
|
|
109
|
+
args?: Record<string, unknown>;
|
|
110
|
+
from?: EndpointRef;
|
|
111
|
+
}
|
|
112
|
+
export interface ControlReply {
|
|
113
|
+
ok: boolean;
|
|
114
|
+
data?: unknown;
|
|
115
|
+
error?: string;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cotal wire types (v0).
|
|
3
|
+
*
|
|
4
|
+
* These are the shapes that travel on the mesh. They are intentionally A2A-inspired
|
|
5
|
+
* (AgentCard / Message / Part) but transport-agnostic. This file IS part of the
|
|
6
|
+
* "wire contract" — treat changes here as protocol changes.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cotal-ai/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@nats-io/jetstream": "^3.4.0",
|
|
16
|
+
"@nats-io/jwt": "0.0.10-5",
|
|
17
|
+
"@nats-io/kv": "^3.4.0",
|
|
18
|
+
"@nats-io/nkeys": "^2.0.3",
|
|
19
|
+
"@nats-io/transport-node": "^3.4.0"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"publishConfig": {
|
|
25
|
+
"access": "public"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
29
|
+
"build": "tsc -p tsconfig.json"
|
|
30
|
+
}
|
|
31
|
+
}
|