qiksy 1.4.0 → 1.5.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "qiksy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Qiksy \u2014 one local engine for every Qiksy tool (Work Finder, Travel Assistant, Client Finder). Runs on your own Claude; nothing leaves your machine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -72,7 +72,7 @@ const AUDIENCE = [
|
|
|
72
72
|
|
|
73
73
|
/** A place — "в Киеве", "во Львове", "in Berlin", a country. */
|
|
74
74
|
const GEO = [
|
|
75
|
-
/\b
|
|
75
|
+
/\b(в|во|по|у|під|біля|около)\s+[А-ЯЁІЇЄ][а-яёіїє]{2,}/,
|
|
76
76
|
/\b(in|near|across)\s+[A-Z][a-z]{2,}/,
|
|
77
77
|
/(украин|польш|герман|казахстан|европ|киев|львов|варшав|берлин|краков|вроцлав|одесс|харьков|днепр)/i,
|
|
78
78
|
/\b(ukraine|poland|germany|europe|kyiv|warsaw|berlin|krakow|lviv)/i,
|
|
@@ -86,7 +86,7 @@ const QUALIFIER = [
|
|
|
86
86
|
|
|
87
87
|
/** Verbs of the job itself — "найди", "собери", "нужны". Supportive, not required. */
|
|
88
88
|
const SEARCH_INTENT = [
|
|
89
|
-
/(
|
|
89
|
+
/(найд|найт|знайд|шука|ищ[иу]|искать|подбер|собер|нужн|потрібн|список|лід|лид[ыов]|клієнт|клиент|покупател|покупц)/i,
|
|
90
90
|
/\b(find|search|prospect|lead|client|customer|list)\b/i,
|
|
91
91
|
];
|
|
92
92
|
|
|
@@ -134,10 +134,20 @@ export function checkBrief(brief) {
|
|
|
134
134
|
hint: `Добавьте кто, где и по какому признаку — иначе агент пойдёт искать «всех подряд» за ваши токены. ${EXAMPLE}`,
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
|
+
/* ONE signal is enough — audience, intent, a place, or a qualifying condition.
|
|
138
|
+
It used to demand geography AND a qualifier together before it would accept a
|
|
139
|
+
brief that named no business type from the list, and the list is the losing
|
|
140
|
+
game this file warns about at the top: «парикмахерские» was simply not in it,
|
|
141
|
+
so "надо мне найти по Львову всех парикмахерские" was refused as having no
|
|
142
|
+
audience. The guard's job is to catch what is obviously NOT a brief — chat, a
|
|
143
|
+
question, someone else's product, all of which are caught above and by the
|
|
144
|
+
three-word floor. Proving that something IS a brief is not its job, and every
|
|
145
|
+
false refusal here costs a real search. */
|
|
137
146
|
const looksLikeBrief =
|
|
138
147
|
AUDIENCE.some((re) => re.test(text)) ||
|
|
139
148
|
SEARCH_INTENT.some((re) => re.test(text)) ||
|
|
140
|
-
|
|
149
|
+
GEO.some((re) => re.test(text)) ||
|
|
150
|
+
QUALIFIER.some((re) => re.test(text));
|
|
141
151
|
if (!looksLikeBrief) {
|
|
142
152
|
return {
|
|
143
153
|
ok: false,
|
|
@@ -16,7 +16,22 @@ function emitJob(job) {
|
|
|
16
16
|
|
|
17
17
|
const jobs = new Map();
|
|
18
18
|
const controllers = new Map(); // job.id → AbortController (только у выполняющихся)
|
|
19
|
-
|
|
19
|
+
/* How many agents may work at once. Two was fine for one person hunting one role, and
|
|
20
|
+
it is the wrong number for a team: with six candidates queued, seven jobs sat idle
|
|
21
|
+
for nine minutes and the last would have started an hour later (measured 2026-07-26).
|
|
22
|
+
Each agent is a real `claude -p` — roughly a gigabyte of RSS and a share of the
|
|
23
|
+
person's own Claude quota — so this scales with the plan rather than going wide:
|
|
24
|
+
free 1 (do not burn a free account), pro 3, dev/team 4. QIKSY_CONCURRENCY overrides. */
|
|
25
|
+
const CONCURRENCY_BY_PLAN = { free: 1, pro: 3, dev: 4 };
|
|
26
|
+
let MAX_CONCURRENT = Number(process.env.QIKSY_CONCURRENCY) || CONCURRENCY_BY_PLAN.free;
|
|
27
|
+
|
|
28
|
+
/** The server calls this whenever the effective plan changes. */
|
|
29
|
+
export function setConcurrencyForPlan(plan) {
|
|
30
|
+
if (process.env.QIKSY_CONCURRENCY) return; // an explicit override wins
|
|
31
|
+
MAX_CONCURRENT = CONCURRENCY_BY_PLAN[plan] ?? CONCURRENCY_BY_PLAN.free;
|
|
32
|
+
pump();
|
|
33
|
+
}
|
|
34
|
+
export const concurrency = () => MAX_CONCURRENT;
|
|
20
35
|
let running = 0;
|
|
21
36
|
const queue = [];
|
|
22
37
|
|
|
@@ -16,7 +16,7 @@ import { spawn } from 'node:child_process';
|
|
|
16
16
|
import { load, save, uid, ROOT, DATA_DIR } from './lib/store.mjs';
|
|
17
17
|
import { runClaude, extractJson } from './lib/agent.mjs';
|
|
18
18
|
import { searchPrompt, coverLetterPrompt, autoApplyPrompt, importResumePrompt, leadsSearchPrompt, pitchPrompt, buildFromStoryPrompt } from './lib/prompts.mjs';
|
|
19
|
-
import { createJob, jobLog, listJobs, getJob, cancelJob, cancelActiveByLabel, jobEvents, setLimitHandler } from './lib/jobs.mjs';
|
|
19
|
+
import { createJob, jobLog, listJobs, getJob, cancelJob, cancelActiveByLabel, jobEvents, setLimitHandler, setConcurrencyForPlan, concurrency } from './lib/jobs.mjs';
|
|
20
20
|
import { ensureBrowser } from './scripts/launch-browser.mjs';
|
|
21
21
|
import { PLANS, SELF_SETTABLE_PLANS } from './lib/plans.mjs';
|
|
22
22
|
import { verifyLicense, licensePlanId, RECHECK_MS } from './lib/license.mjs';
|
|
@@ -147,6 +147,7 @@ const resumeQueue = createResumeQueue({
|
|
|
147
147
|
},
|
|
148
148
|
});
|
|
149
149
|
resumeQueue.start();
|
|
150
|
+
setConcurrencyForPlan(effectivePlanId());
|
|
150
151
|
setLimitHandler((descriptor, info) => resumeQueue.defer(descriptor, info));
|
|
151
152
|
|
|
152
153
|
function startSearch(position, run = null) {
|
|
@@ -658,7 +659,7 @@ export const requestHandler = async (req, res) => {
|
|
|
658
659
|
|
|
659
660
|
if (req.method === 'GET' && path === '/api/state') {
|
|
660
661
|
return json(res, 200, {
|
|
661
|
-
...db, browserAlive, jobs: listJobs(), billing: billingInfo(), deferred: resumeQueue.list(),
|
|
662
|
+
...db, browserAlive, jobs: listJobs(), billing: billingInfo(), deferred: resumeQueue.list(), concurrency: concurrency(),
|
|
662
663
|
catalog: ROLE_CATALOG.map(({ id, group, title, keywords }) => ({ id, group, title, keywords })),
|
|
663
664
|
seniority: SENIORITY, locations: LOCATIONS,
|
|
664
665
|
});
|
|
@@ -726,6 +727,7 @@ export const requestHandler = async (req, res) => {
|
|
|
726
727
|
delete body.licenseInfo;
|
|
727
728
|
Object.assign(db.settings, body);
|
|
728
729
|
save(db);
|
|
730
|
+
setConcurrencyForPlan(effectivePlanId());
|
|
729
731
|
return json(res, 200, db.settings);
|
|
730
732
|
}
|
|
731
733
|
|