@pulsarinteractive/geniustasker 0.1.0-beta.2 → 0.1.0-beta.3
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 +7 -0
- package/CONTRIBUTING.md +67 -12
- package/LICENSE +21 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/README.md +11 -6
- package/THIRD_PARTY_NOTICES.md +15 -0
- package/docs/ARCHITECTURE.md +104 -0
- package/docs/CLI.md +4 -9
- package/docs/INTEGRATIONS.md +3 -3
- package/docs/OPENAI.md +10 -11
- package/docs/RELEASING.md +4 -3
- package/docs/TESTING.md +52 -0
- package/examples/codex.toml +1 -1
- package/examples/mcp.json +1 -7
- package/mcp.json +1 -7
- package/package.json +20 -9
- package/plugin.json +6 -15
- package/skills/geniustasker/SKILL.md +0 -2
- package/skills/geniustasker/agents/openai.yaml +3 -3
- package/skills/geniustasker/references/collaboration.md +0 -2
- package/skills/geniustasker-memory/agents/openai.yaml +3 -3
- package/src/changes.mjs +104 -28
- package/src/cli.mjs +298 -77
- package/src/client.mjs +149 -32
- package/src/commands.mjs +202 -52
- package/src/errors.mjs +55 -12
- package/src/login.mjs +190 -47
- package/src/mcp.mjs +183 -43
- package/src/profiles.mjs +123 -32
- package/src/store.mjs +101 -26
- package/src/transport.mjs +141 -31
package/docs/TESTING.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
## Run the quality checks
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm ci
|
|
7
|
+
npm run verify
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`verify` runs formatting, ESLint, tests, package inventory, public-file checks and
|
|
11
|
+
plugin/schema validation. CI runs the same command on Linux and macOS with Node
|
|
12
|
+
22, followed by a dependency vulnerability audit. It has read-only repository
|
|
13
|
+
permissions and no live service credentials.
|
|
14
|
+
|
|
15
|
+
## Focused checks
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm run format:check
|
|
19
|
+
npm run lint
|
|
20
|
+
node --test test/login.test.mjs
|
|
21
|
+
node --test test/process.test.mjs
|
|
22
|
+
npm run check
|
|
23
|
+
npm audit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
`npm run format` applies formatting. The generated command catalog and vendored
|
|
27
|
+
schemas are excluded from formatting so their provenance remains stable.
|
|
28
|
+
|
|
29
|
+
## What the suites cover
|
|
30
|
+
|
|
31
|
+
| Suite | Observable guarantees |
|
|
32
|
+
| --------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| Login | Consent URL validation, PKCE state/callback handling, denial, cancellation, device-flow deadlines and slow-down |
|
|
34
|
+
| Client/store | Private file checks, origin binding, serialized renewal and uncertain-token recovery |
|
|
35
|
+
| Commands | Immutable requests, concurrent writes, identity binding, uncertain receipts and conflicts |
|
|
36
|
+
| Profiles | Grant validation, narrowed selection, revision conflicts and rotation uncertainty |
|
|
37
|
+
| Changes | Cursor validation, bounded waits, cancellation and no blind retries after quota or permission failures |
|
|
38
|
+
| Process | Real stdio MCP discovery/calls and coordination between separate OS processes |
|
|
39
|
+
| Public boundary | Rejection of excluded files, credentials, symlinks and escaping imports |
|
|
40
|
+
|
|
41
|
+
Tests create and clean up their own temporary directories and loopback servers.
|
|
42
|
+
They do not require browser sign-in or real project data. Use meaningful scenario
|
|
43
|
+
names, assert behavior at public boundaries and include the failure that motivates
|
|
44
|
+
a regression test. Avoid sleeps as synchronization when an explicit signal works.
|
|
45
|
+
|
|
46
|
+
## What local tests do not establish
|
|
47
|
+
|
|
48
|
+
They do not prove admission to the hosted beta, production availability, browser
|
|
49
|
+
provider compatibility on every device or acceptance by each agent host. Release
|
|
50
|
+
verification separately installs the published archive with a fresh cache,
|
|
51
|
+
compares registry integrity and exercises a deliberately authorized test account.
|
|
52
|
+
Record those checks without publishing account data.
|
package/examples/codex.toml
CHANGED
package/examples/mcp.json
CHANGED
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
"mcpServers": {
|
|
3
3
|
"geniustasker": {
|
|
4
4
|
"command": "npx",
|
|
5
|
-
"args": [
|
|
6
|
-
"-y",
|
|
7
|
-
"@pulsarinteractive/geniustasker@0.1.0-beta.2",
|
|
8
|
-
"mcp",
|
|
9
|
-
"--context",
|
|
10
|
-
"work"
|
|
11
|
-
]
|
|
5
|
+
"args": ["-y", "@pulsarinteractive/geniustasker@0.1.0-beta.3", "mcp", "--context", "work"]
|
|
12
6
|
}
|
|
13
7
|
}
|
|
14
8
|
}
|
package/mcp.json
CHANGED
|
@@ -4,13 +4,7 @@
|
|
|
4
4
|
"geniustasker": {
|
|
5
5
|
"type": "stdio",
|
|
6
6
|
"command": "npx",
|
|
7
|
-
"args": [
|
|
8
|
-
"-y",
|
|
9
|
-
"@pulsarinteractive/geniustasker@0.1.0-beta.2",
|
|
10
|
-
"mcp",
|
|
11
|
-
"--context",
|
|
12
|
-
"work"
|
|
13
|
-
]
|
|
7
|
+
"args": ["-y", "@pulsarinteractive/geniustasker@0.1.0-beta.3", "mcp", "--context", "work"]
|
|
14
8
|
}
|
|
15
9
|
}
|
|
16
10
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pulsarinteractive/geniustasker",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "GeniusTasker delegated CLI and local agent tools",
|
|
6
6
|
"type": "module",
|
|
@@ -21,13 +21,20 @@
|
|
|
21
21
|
"CONTRIBUTING.md",
|
|
22
22
|
"SECURITY.md",
|
|
23
23
|
"plugin.json",
|
|
24
|
-
"mcp.json"
|
|
24
|
+
"mcp.json",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"THIRD_PARTY_NOTICES.md",
|
|
27
|
+
"LICENSES/"
|
|
25
28
|
],
|
|
26
29
|
"scripts": {
|
|
27
30
|
"test": "node --test test/*.test.mjs",
|
|
28
31
|
"check": "node tools/check-package.mjs && node tools/check-public.mjs && node tools/check-plugin.mjs",
|
|
29
|
-
"prepublishOnly": "npm
|
|
30
|
-
"check:public": "node tools/check-public.mjs"
|
|
32
|
+
"prepublishOnly": "npm run verify",
|
|
33
|
+
"check:public": "node tools/check-public.mjs",
|
|
34
|
+
"format": "prettier --write .",
|
|
35
|
+
"format:check": "prettier --check .",
|
|
36
|
+
"lint": "eslint .",
|
|
37
|
+
"verify": "npm run format:check && npm run lint && npm test && npm run check"
|
|
31
38
|
},
|
|
32
39
|
"dependencies": {
|
|
33
40
|
"@modelcontextprotocol/server": "2.1.0",
|
|
@@ -35,10 +42,14 @@
|
|
|
35
42
|
"zod": "4.6.5"
|
|
36
43
|
},
|
|
37
44
|
"devDependencies": {
|
|
45
|
+
"@eslint/js": "10.0.1",
|
|
38
46
|
"@modelcontextprotocol/client": "2.1.0",
|
|
39
|
-
"ajv": "8.20.0"
|
|
47
|
+
"ajv": "8.20.0",
|
|
48
|
+
"eslint": "10.11.0",
|
|
49
|
+
"globals": "17.12.0",
|
|
50
|
+
"prettier": "3.9.9"
|
|
40
51
|
},
|
|
41
|
-
"license": "
|
|
52
|
+
"license": "MIT",
|
|
42
53
|
"publishConfig": {
|
|
43
54
|
"access": "public",
|
|
44
55
|
"tag": "beta",
|
|
@@ -46,11 +57,11 @@
|
|
|
46
57
|
},
|
|
47
58
|
"repository": {
|
|
48
59
|
"type": "git",
|
|
49
|
-
"url": "https://github.com/
|
|
60
|
+
"url": "https://github.com/PulsarInteractive/geniustasker-agent-tools.git"
|
|
50
61
|
},
|
|
51
|
-
"homepage": "https://github.com/
|
|
62
|
+
"homepage": "https://github.com/PulsarInteractive/geniustasker-agent-tools#readme",
|
|
52
63
|
"bugs": {
|
|
53
|
-
"url": "https://github.com/
|
|
64
|
+
"url": "https://github.com/PulsarInteractive/geniustasker-agent-tools/issues"
|
|
54
65
|
},
|
|
55
66
|
"keywords": [
|
|
56
67
|
"geniustasker",
|
package/plugin.json
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "geniustasker",
|
|
4
|
-
"version": "0.1.0-beta.
|
|
4
|
+
"version": "0.1.0-beta.3",
|
|
5
5
|
"description": "Track project work and independent product memories with your own agents.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Pulsar Interactive"
|
|
8
8
|
},
|
|
9
|
-
"repository": "https://github.com/
|
|
10
|
-
"license": "
|
|
11
|
-
"keywords": [
|
|
12
|
-
"productivity",
|
|
13
|
-
"projects",
|
|
14
|
-
"memory"
|
|
15
|
-
],
|
|
9
|
+
"repository": "https://github.com/PulsarInteractive/geniustasker-agent-tools",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": ["productivity", "projects", "memory"],
|
|
16
12
|
"extensions": {
|
|
17
13
|
"com.openai": {
|
|
18
14
|
"interface": {
|
|
@@ -20,13 +16,8 @@
|
|
|
20
16
|
"shortDescription": "Projects, progress and memory for your agents",
|
|
21
17
|
"developerName": "Pulsar Interactive",
|
|
22
18
|
"category": "Productivity",
|
|
23
|
-
"capabilities": [
|
|
24
|
-
|
|
25
|
-
"Write"
|
|
26
|
-
],
|
|
27
|
-
"defaultPrompt": [
|
|
28
|
-
"Use GeniusTasker to review my project progress and next tasks."
|
|
29
|
-
]
|
|
19
|
+
"capabilities": ["Read", "Write"],
|
|
20
|
+
"defaultPrompt": ["Use GeniusTasker to review my project progress and next tasks."]
|
|
30
21
|
}
|
|
31
22
|
}
|
|
32
23
|
}
|
|
@@ -99,8 +99,6 @@ to evade quotas. If the requested capability is unavailable, say what is pending
|
|
|
99
99
|
and preserve the useful findings. Final progress should identify the actual
|
|
100
100
|
project/ticket and distinguish completed work, validation and remaining work.
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
102
|
## Specialized workflows
|
|
105
103
|
|
|
106
104
|
- To create, switch or restrict an agent identity, read [Profiles](references/profiles.md).
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
|
-
display_name:
|
|
3
|
-
short_description:
|
|
4
|
-
default_prompt:
|
|
2
|
+
display_name: 'GeniusTasker'
|
|
3
|
+
short_description: 'Work on shared projects with your own agents'
|
|
4
|
+
default_prompt: 'Use $geniustasker to review my project and record the next useful step.'
|
|
@@ -22,7 +22,6 @@ agent policy. Use `question.close` only for this profile's own question, with it
|
|
|
22
22
|
current revision. If no answer arrives, record the blocker and proceed only with
|
|
23
23
|
independent authorized work; do not invent approval or repeatedly poll rapidly.
|
|
24
24
|
|
|
25
|
-
|
|
26
25
|
## Leave a useful handoff
|
|
27
26
|
|
|
28
27
|
Use the discovered `task.report` command with `tasks:write`, an open unarchived
|
|
@@ -42,7 +41,6 @@ is not an invitation to recreate its text. Completing/reopening the task clears
|
|
|
42
41
|
its current work phase and retains the report as history. A report does not grant
|
|
43
42
|
permission or count as approval. Use a question when a human answer is needed.
|
|
44
43
|
|
|
45
|
-
|
|
46
44
|
## Request and consume a precise human decision
|
|
47
45
|
|
|
48
46
|
Use `approval.request` with `approvals:write` when a concrete action needs a
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
|
-
display_name:
|
|
3
|
-
short_description:
|
|
4
|
-
default_prompt:
|
|
2
|
+
display_name: 'GeniusTasker Memory'
|
|
3
|
+
short_description: 'Navigate and maintain independent product knowledge'
|
|
4
|
+
default_prompt: 'Use $geniustasker-memory to find the relevant product knowledge and update it with verified results.'
|
package/src/changes.mjs
CHANGED
|
@@ -5,26 +5,73 @@ import { Transport } from './transport.mjs';
|
|
|
5
5
|
import catalog from './command-catalog.mjs';
|
|
6
6
|
import { fail } from './errors.mjs';
|
|
7
7
|
|
|
8
|
-
export const changesSchema = z
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
8
|
+
export const changesSchema = z
|
|
9
|
+
.object({
|
|
10
|
+
scope: z
|
|
11
|
+
.string()
|
|
12
|
+
.max(256)
|
|
13
|
+
// eslint-disable-next-line no-control-regex -- Reject control characters in untrusted input.
|
|
14
|
+
.regex(/^(project|memory):[^\u0000-\u001f]+$/u),
|
|
15
|
+
collection: z.string().min(1).max(32),
|
|
16
|
+
cursor: z.string().min(1).max(4096),
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
export const watchSchema = changesSchema
|
|
20
|
+
.extend({ waitSeconds: z.number().int().min(1).max(catalog.changes.maxWatchSeconds).optional() })
|
|
21
|
+
.strict();
|
|
22
|
+
const revision = z
|
|
23
|
+
.string()
|
|
24
|
+
.max(16)
|
|
25
|
+
.regex(/^(0|[1-9][0-9]*)$/)
|
|
26
|
+
.refine((value) => BigInt(value) <= 9007199254740991n);
|
|
27
|
+
const changePageSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
scope: z.string(),
|
|
30
|
+
collection: z.string(),
|
|
31
|
+
epoch: z.string().min(1),
|
|
32
|
+
checkpoint: revision,
|
|
33
|
+
items: z.array(
|
|
34
|
+
z.discriminatedUnion('kind', [
|
|
35
|
+
z
|
|
36
|
+
.object({
|
|
37
|
+
kind: z.literal('upsert'),
|
|
38
|
+
id: z.string().min(1),
|
|
39
|
+
revision,
|
|
40
|
+
data: z.record(z.string(), z.unknown()),
|
|
41
|
+
})
|
|
42
|
+
.strict(),
|
|
43
|
+
z
|
|
44
|
+
.object({ kind: z.literal('remove'), id: z.string().min(1), revision, data: z.null() })
|
|
45
|
+
.strict(),
|
|
46
|
+
]),
|
|
47
|
+
),
|
|
48
|
+
omittedIds: z.array(z.string().min(1)),
|
|
49
|
+
nextCursor: z.string().min(1).max(4096),
|
|
50
|
+
hasMore: z.boolean(),
|
|
51
|
+
pollAfterSeconds: z.number().int().min(1).max(86400),
|
|
52
|
+
})
|
|
53
|
+
.strict();
|
|
19
54
|
function query(input, schema) {
|
|
20
55
|
const parsed = schema.safeParse(input);
|
|
21
|
-
if (!parsed.success)
|
|
56
|
+
if (!parsed.success) {
|
|
57
|
+
fail(
|
|
58
|
+
'invalid_arguments',
|
|
59
|
+
'Use a project or memory scope, collection and unchanged change cursor; waitSeconds must be within the supported bound.',
|
|
60
|
+
);
|
|
61
|
+
}
|
|
22
62
|
return parsed.data;
|
|
23
63
|
}
|
|
64
|
+
/** Validate the entire bounded page before a caller can adopt its next cursor. */
|
|
24
65
|
export async function readChanges(client, input) {
|
|
25
|
-
const requested = query(input, changesSchema),
|
|
26
|
-
|
|
66
|
+
const requested = query(input, changesSchema),
|
|
67
|
+
page = await client.get('changes', requested);
|
|
68
|
+
if (
|
|
69
|
+
!changePageSchema.safeParse(page).success ||
|
|
70
|
+
page.scope !== requested.scope ||
|
|
71
|
+
page.collection !== requested.collection
|
|
72
|
+
) {
|
|
27
73
|
fail('invalid_response', 'The change page was invalid; keep the previous cursor.');
|
|
74
|
+
}
|
|
28
75
|
return page;
|
|
29
76
|
}
|
|
30
77
|
/** One bounded wait, not a daemon. Each poll spends the same account budget as
|
|
@@ -32,32 +79,61 @@ export async function readChanges(client, input) {
|
|
|
32
79
|
* quotas, expired history and revocation never trigger a blind retry loop. */
|
|
33
80
|
export async function watchChanges(client, input, requestSignal) {
|
|
34
81
|
const { waitSeconds = 30, ...requested } = query(input, watchSchema);
|
|
35
|
-
const deadline = new AbortController(),
|
|
82
|
+
const deadline = new AbortController(),
|
|
83
|
+
parents = [client.transport.signal, requestSignal].filter(Boolean);
|
|
36
84
|
const parent = parents.length ? AbortSignal.any(parents) : undefined;
|
|
37
85
|
const signal = parent ? AbortSignal.any([parent, deadline.signal]) : deadline.signal;
|
|
38
86
|
const timer = setTimeout(() => deadline.abort(), waitSeconds * 1000);
|
|
39
|
-
const bounded = new AgentClient(
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
87
|
+
const bounded = new AgentClient(
|
|
88
|
+
new Transport(client.transport.origin, {
|
|
89
|
+
fetchImpl: client.transport.fetch,
|
|
90
|
+
timeoutMs: client.transport.timeoutMs,
|
|
91
|
+
signal,
|
|
92
|
+
}),
|
|
93
|
+
client.store,
|
|
94
|
+
);
|
|
95
|
+
let page = null,
|
|
96
|
+
polls = 0,
|
|
97
|
+
cursor = requested.cursor;
|
|
43
98
|
const timedOut = () => ({ reason: 'timeout', polls, page, nextCursor: cursor });
|
|
44
99
|
try {
|
|
45
|
-
if (parent?.aborted)
|
|
100
|
+
if (parent?.aborted) {
|
|
101
|
+
fail('cancelled', 'The wait was cancelled.', { nextCursor: cursor });
|
|
102
|
+
}
|
|
46
103
|
while (!signal.aborted) {
|
|
47
|
-
page = await readChanges(bounded, { ...requested, cursor });
|
|
104
|
+
page = await readChanges(bounded, { ...requested, cursor });
|
|
105
|
+
polls++;
|
|
48
106
|
cursor = page.nextCursor;
|
|
49
|
-
if (page.items.length || page.omittedIds.length || page.hasMore)
|
|
107
|
+
if (page.items.length || page.omittedIds.length || page.hasMore) {
|
|
50
108
|
return { reason: 'changes', polls, page, nextCursor: cursor };
|
|
51
|
-
|
|
109
|
+
}
|
|
110
|
+
await sleep(
|
|
111
|
+
Math.max(catalog.changes.pollIntervalSeconds, page.pollAfterSeconds) * 1000,
|
|
112
|
+
undefined,
|
|
113
|
+
{ signal },
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (parent?.aborted) {
|
|
117
|
+
fail('cancelled', 'The wait was cancelled.', { nextCursor: cursor });
|
|
52
118
|
}
|
|
53
|
-
if (parent?.aborted) fail('cancelled', 'The wait was cancelled.', { nextCursor: cursor });
|
|
54
119
|
return timedOut();
|
|
55
120
|
} catch (error) {
|
|
56
|
-
if (parent?.aborted)
|
|
57
|
-
|
|
121
|
+
if (parent?.aborted) {
|
|
122
|
+
fail('cancelled', 'The wait was cancelled.', { nextCursor: cursor });
|
|
123
|
+
}
|
|
124
|
+
if (
|
|
125
|
+
deadline.signal.aborted &&
|
|
126
|
+
(error?.name === 'AbortError' || error?.code === 'network_unavailable')
|
|
127
|
+
) {
|
|
128
|
+
return timedOut();
|
|
129
|
+
}
|
|
58
130
|
// Completed empty polls may advance the cursor. Preserve it on an error,
|
|
59
131
|
// but do not claim that a failed final request was an empty successful read.
|
|
60
|
-
if (error?.details)
|
|
132
|
+
if (error?.details) {
|
|
133
|
+
error.details.nextCursor = cursor;
|
|
134
|
+
}
|
|
61
135
|
throw error;
|
|
62
|
-
} finally {
|
|
136
|
+
} finally {
|
|
137
|
+
clearTimeout(timer);
|
|
138
|
+
}
|
|
63
139
|
}
|