@pulsarinteractive/geniustasker 0.1.0-beta.3 → 0.1.0-beta.5
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/CHANGELOG.md +24 -0
- package/README.md +55 -63
- package/docs/ARCHITECTURE.md +40 -0
- package/docs/CLI.md +26 -2
- package/docs/DOCUMENTATION_SYNC.md +144 -0
- package/docs/INTEGRATIONS.md +2 -2
- package/docs/LIMITS.md +49 -0
- package/docs/MEMORY.md +38 -0
- package/docs/RELEASING.md +7 -0
- package/examples/codex.toml +1 -1
- package/examples/mcp.json +1 -1
- package/mcp.json +1 -1
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/geniustasker/SKILL.md +3 -0
- package/skills/geniustasker/references/project-reads.md +43 -0
- package/skills/geniustasker-documentation/SKILL.md +39 -0
- package/skills/geniustasker-documentation/agents/openai.yaml +4 -0
- package/skills/geniustasker-documentation/references/structure.md +29 -0
- package/skills/geniustasker-graph/SKILL.md +31 -0
- package/skills/geniustasker-graph/agents/openai.yaml +4 -0
- package/skills/geniustasker-media/SKILL.md +33 -0
- package/skills/geniustasker-media/agents/openai.yaml +4 -0
- package/skills/geniustasker-memory/SKILL.md +75 -7
- package/skills/geniustasker-memory/references/knowledge-workflows.md +57 -0
- package/src/cli.mjs +151 -0
- package/src/client.mjs +54 -2
- package/src/command-catalog.mjs +109 -5
- package/src/commands.mjs +15 -2
- package/src/documentation-graph.mjs +251 -0
- package/src/documentation-store.mjs +589 -0
- package/src/documentation-sync.mjs +517 -0
- package/src/documentation.mjs +136 -0
- package/src/knowledge.mjs +109 -0
- package/src/mcp.mjs +122 -3
- package/src/media.mjs +307 -0
- package/src/read-cache.mjs +111 -0
- package/src/transport.mjs +33 -10
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { fail } from './errors.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bounded process-local LRU for conditional resource reads. Every access calls
|
|
5
|
+
* the server; TTL controls retention only, never permission freshness. Document
|
|
6
|
+
* data is neither persisted to disk nor served as an offline fallback.
|
|
7
|
+
*/
|
|
8
|
+
export class ConditionalReadCache {
|
|
9
|
+
constructor({
|
|
10
|
+
maxBytes = 8 * 1024 * 1024,
|
|
11
|
+
maxEntries = 64,
|
|
12
|
+
maxAgeMs = 900000,
|
|
13
|
+
now = Date.now,
|
|
14
|
+
} = {}) {
|
|
15
|
+
for (const value of [maxBytes, maxEntries, maxAgeMs]) {
|
|
16
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
17
|
+
throw new TypeError('Read cache limits must be positive integers.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
this.maxBytes = maxBytes;
|
|
21
|
+
this.maxEntries = maxEntries;
|
|
22
|
+
this.maxAgeMs = maxAgeMs;
|
|
23
|
+
this.now = now;
|
|
24
|
+
this.entries = new Map();
|
|
25
|
+
this.pending = new Map();
|
|
26
|
+
this.bytes = 0;
|
|
27
|
+
this.generation = 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
clear() {
|
|
31
|
+
this.generation++;
|
|
32
|
+
this.entries.clear();
|
|
33
|
+
this.pending.clear();
|
|
34
|
+
this.bytes = 0;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
remove(key) {
|
|
38
|
+
const old = this.entries.get(key);
|
|
39
|
+
if (old) {
|
|
40
|
+
this.bytes -= old.bytes;
|
|
41
|
+
this.entries.delete(key);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async read(key, load) {
|
|
46
|
+
const existing = this.pending.get(key);
|
|
47
|
+
if (existing) {
|
|
48
|
+
return structuredClone(await existing);
|
|
49
|
+
}
|
|
50
|
+
const generation = this.generation;
|
|
51
|
+
for (const [entryKey, entry] of this.entries) {
|
|
52
|
+
if (this.now() - entry.at >= this.maxAgeMs) {
|
|
53
|
+
this.remove(entryKey);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const cached = this.entries.get(key);
|
|
57
|
+
const pending = (async () => {
|
|
58
|
+
const received = await load(cached?.value.etag);
|
|
59
|
+
if (generation !== this.generation) {
|
|
60
|
+
fail('read_context_changed', 'The read context changed while this request was running.', {
|
|
61
|
+
retryable: true,
|
|
62
|
+
remediation: 'Read again in the currently selected account and profile.',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (!received || typeof received !== 'object' || Array.isArray(received)) {
|
|
66
|
+
fail('invalid_response', 'The resource response was invalid.');
|
|
67
|
+
}
|
|
68
|
+
let value = received;
|
|
69
|
+
if (received?.notModified === true) {
|
|
70
|
+
if (
|
|
71
|
+
!cached ||
|
|
72
|
+
received.etag !== cached.value.etag ||
|
|
73
|
+
received.scope !== cached.value.scope ||
|
|
74
|
+
received.collection !== cached.value.collection ||
|
|
75
|
+
received.epoch !== cached.value.epoch ||
|
|
76
|
+
received.checkpoint !== cached.value.checkpoint ||
|
|
77
|
+
!Array.isArray(received.items) ||
|
|
78
|
+
received.items.length !== 0
|
|
79
|
+
) {
|
|
80
|
+
fail('invalid_response', 'A conditional response did not match the cached resource.');
|
|
81
|
+
}
|
|
82
|
+
value = cached.value;
|
|
83
|
+
}
|
|
84
|
+
this.remove(key);
|
|
85
|
+
const size = Buffer.byteLength(JSON.stringify(value));
|
|
86
|
+
if (typeof value?.etag === 'string' && value.etag.length <= 8192 && size <= this.maxBytes) {
|
|
87
|
+
this.entries.set(key, { value: structuredClone(value), bytes: size, at: this.now() });
|
|
88
|
+
this.bytes += size;
|
|
89
|
+
while (this.entries.size > this.maxEntries || this.bytes > this.maxBytes) {
|
|
90
|
+
this.remove(this.entries.keys().next().value);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
})();
|
|
95
|
+
this.pending.set(key, pending);
|
|
96
|
+
try {
|
|
97
|
+
return structuredClone(await pending);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// A permission, authentication, network or schema failure can never turn
|
|
100
|
+
// into stale private success, or let another in-flight read refill it.
|
|
101
|
+
if (generation === this.generation) {
|
|
102
|
+
this.clear();
|
|
103
|
+
}
|
|
104
|
+
throw error;
|
|
105
|
+
} finally {
|
|
106
|
+
if (this.pending.get(key) === pending) {
|
|
107
|
+
this.pending.delete(key);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/transport.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AgentError, fail } from './errors.mjs';
|
|
2
|
+
import catalog from './command-catalog.mjs';
|
|
2
3
|
|
|
3
4
|
export const ORIGINS = Object.freeze({
|
|
4
5
|
production: 'https://api-geniustasker.pulsarinteractive.cloud',
|
|
@@ -54,7 +55,7 @@ export class Transport {
|
|
|
54
55
|
this.timeoutMs = timeoutMs;
|
|
55
56
|
this.signal = signal;
|
|
56
57
|
}
|
|
57
|
-
async request(path, { method = 'GET', form, json, token, query } = {}) {
|
|
58
|
+
async request(path, { method = 'GET', form, json, token, query, bytes, binary = false } = {}) {
|
|
58
59
|
if (
|
|
59
60
|
!(path.startsWith('/api/agents/') || path === '/.well-known/oauth-authorization-server') ||
|
|
60
61
|
path.startsWith('//')
|
|
@@ -78,12 +79,23 @@ export class Transport {
|
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
}
|
|
82
|
+
if (
|
|
83
|
+
(bytes !== undefined || binary) &&
|
|
84
|
+
(url.pathname !== '/api/agents/v1/media/content' ||
|
|
85
|
+
(bytes !== undefined && (!Buffer.isBuffer(bytes) || bytes.length > 15000000)) ||
|
|
86
|
+
(bytes !== undefined && (json !== undefined || form)))
|
|
87
|
+
) {
|
|
88
|
+
fail('invalid_arguments', 'Binary transfer requires the bounded delegated media endpoint.');
|
|
89
|
+
}
|
|
81
90
|
if (form && json !== undefined) {
|
|
82
91
|
fail('invalid_arguments', 'Choose one request body format.');
|
|
83
92
|
}
|
|
84
93
|
const jsonBody = json === undefined ? undefined : JSON.stringify(json);
|
|
85
|
-
if (jsonBody && Buffer.byteLength(jsonBody) >
|
|
86
|
-
fail(
|
|
94
|
+
if (jsonBody && Buffer.byteLength(jsonBody) > catalog.maxCommandBytes) {
|
|
95
|
+
fail(
|
|
96
|
+
'invalid_arguments',
|
|
97
|
+
`Command exceeds the ${catalog.maxCommandBytes} byte request limit.`,
|
|
98
|
+
);
|
|
87
99
|
}
|
|
88
100
|
let response;
|
|
89
101
|
const timeout = AbortSignal.timeout(this.timeoutMs);
|
|
@@ -94,15 +106,23 @@ export class Transport {
|
|
|
94
106
|
redirect: 'error',
|
|
95
107
|
signal,
|
|
96
108
|
headers: {
|
|
97
|
-
Accept: 'application/json',
|
|
109
|
+
Accept: binary ? 'application/octet-stream' : 'application/json',
|
|
98
110
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
99
|
-
...(
|
|
100
|
-
? { 'Content-Type': 'application/
|
|
111
|
+
...(bytes !== undefined
|
|
112
|
+
? { 'Content-Type': 'application/octet-stream', 'Content-Length': String(bytes.length) }
|
|
113
|
+
: form
|
|
114
|
+
? { 'Content-Type': 'application/x-www-form-urlencoded' }
|
|
115
|
+
: jsonBody
|
|
116
|
+
? { 'Content-Type': 'application/json' }
|
|
117
|
+
: {}),
|
|
118
|
+
},
|
|
119
|
+
...(bytes !== undefined
|
|
120
|
+
? { body: bytes }
|
|
121
|
+
: form
|
|
122
|
+
? { body: new URLSearchParams(form) }
|
|
101
123
|
: jsonBody
|
|
102
|
-
? {
|
|
124
|
+
? { body: jsonBody }
|
|
103
125
|
: {}),
|
|
104
|
-
},
|
|
105
|
-
...(form ? { body: new URLSearchParams(form) } : jsonBody ? { body: jsonBody } : {}),
|
|
106
126
|
});
|
|
107
127
|
} catch {
|
|
108
128
|
throw new AgentError('network_unavailable', 'The API did not return a usable response.', {
|
|
@@ -118,7 +138,7 @@ export class Transport {
|
|
|
118
138
|
if (response.body) {
|
|
119
139
|
for await (const chunk of response.body) {
|
|
120
140
|
size += chunk.length;
|
|
121
|
-
if (size > 262144) {
|
|
141
|
+
if (size > (binary && response.ok ? 15000000 : 262144)) {
|
|
122
142
|
fail('invalid_response', 'The API response exceeded the size limit.');
|
|
123
143
|
}
|
|
124
144
|
chunks.push(chunk);
|
|
@@ -133,6 +153,9 @@ export class Transport {
|
|
|
133
153
|
execution: method === 'GET' ? 'not_applicable' : 'unknown',
|
|
134
154
|
});
|
|
135
155
|
}
|
|
156
|
+
if (binary && response.ok) {
|
|
157
|
+
return Buffer.concat(chunks);
|
|
158
|
+
}
|
|
136
159
|
let data;
|
|
137
160
|
try {
|
|
138
161
|
data = size ? JSON.parse(Buffer.concat(chunks).toString('utf8')) : null;
|