Compare commits
2 Commits
8ee080b744
...
a194cf6b8b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a194cf6b8b | ||
|
|
59395f95d8 |
26
API.md
26
API.md
@@ -8,6 +8,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
|
||||
> **Convention**: `PK` primary key · `?` nullable · `->tbl` foreign key · `UQ` unique · `s(N)` string · `txt` text · `i` int · `bi` bigint · `f` float · `d` decimal · `b` bit · `dt` datetime · `bin` binary · `=val` default
|
||||
|
||||
- **account**: `id PK, company_id->company, type:s(100), name:s(255)`
|
||||
- **article**: `id PK, sku?:s(100) UQ, unit_id->unit, unitamount:f, weight:f, track_stock:b=1, brand_id?->brand, articlelist_id?->articlelist, optionslist_id?->optionslist, is_parent:b=0, parentarticle_id?->article, parentattributename_id?->attributename, parentsort:i=0, created_at:dt, updated_at:dt, UQ(parentarticle_id, parentsort)`
|
||||
- **articleattributevalue**: `id PK, article_id->article, attributevalue_id->attributevalue, sort:i=0, UQ(article_id, attributevalue_id)`
|
||||
- **articleean**: `id PK, article_id->article, ean:s(50) UQ`
|
||||
@@ -18,6 +19,7 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- **articleproperty**: `id PK, article_id->article, property_id->propertyname, value:txt, sort:i=0`
|
||||
- **articlesupplier**: `id PK, article_id->article, entity_id->entity, net?:f, tax_id?->tax, sku?:s(100), stock?:f, pu:f=1, sort:i=0, UQ(article_id, entity_id), UQ(article_id, sort)`
|
||||
- **articletext**: `id PK, article_id->article, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(article_id, language_id)`
|
||||
- **articletombstone**: `id PK, company_id->company, article_id?:i, sku?:s(100), name?:s(255), taxrate?:d(18,4), net?:f, deleted_at:dt`
|
||||
- **attributename**: `id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)`
|
||||
- **attributevalue**: `id PK, attribute_id->attributename, value:s(255), sort:i=0, UQ(attribute_id, value)`
|
||||
- **attributevaluepic**: `id PK, attributevalue_id->attributevalue, pic_id->pic, sort:i=0, UQ(attributevalue_id, pic_id), UQ(attributevalue_id, sort)`
|
||||
@@ -25,13 +27,16 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- **brandpic**: `id PK, brand_id->brand, pic_id->pic, sort:i=0, UQ(brand_id, pic_id), UQ(brand_id, sort)`
|
||||
- **category**: `id PK, parent_id?->category, sort:i=0`
|
||||
- **categoryarticle**: `id PK, category_id->category, article_id->article, UQ(category_id, article_id)`
|
||||
- **categoryattributevalue**: `id PK, category_id->category, attributevalue_id->attributevalue, sort:i=0, UQ(category_id, attributevalue_id)`
|
||||
- **categorypic**: `id PK, category_id->category, pic_id->pic, sort:i=0, UQ(category_id, pic_id), UQ(category_id, sort)`
|
||||
- **categoryproperty**: `id PK, category_id->category, property_id->propertyname, value:txt, sort:i=0`
|
||||
- **categorytext**: `id PK, category_id->category, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(category_id, language_id)`
|
||||
- **company**: `id PK, name:s(255) UQ, root_category_id?->category, default_language_id?->language, taxrate?->tax, reducedtaxrate?->tax, taxfreerate?->tax, poscustomergroup_id->customergroup`
|
||||
- **companyhistory**: `id PK, company_id->company, companyuser_id?->companyuser, date:dt, text:txt`
|
||||
- **companysetting**: `id PK, company_id->company, key:s(255), value:txt, frontend:b=0, writeonly:b=0, UQ(company_id, key)`
|
||||
- **companyuser**: `id PK, google_auth_id->google_auth, company_id->company, UQ(google_auth_id, company_id)`
|
||||
- **customergroup**: `id PK, name:s(255), sort:i=0, company_id?->company`
|
||||
- **deliverysummary**: `id PK, company_id->company, manifest_id->manifest UQ, delivery_number:s(100), external_number?:s(100), delivery_date:dt, created_at:dt, supplier_id?->entity, supplier_name?:s(255), positions:i=0, gross_total:f=0, net_total:f=0, total_quantity:f=0, stock_positions:i=0, booked_positions:i=0, unbooked_positions:i=0, stock_quantity:f=0, booked_quantity:f=0, unbooked_quantity:f=0, is_booked:b=0`
|
||||
- **entity**: `id PK, customergroup_id->customergroup, first_name?:s(255), name:s(255), number?:s(100), created_at:dt, updated_at:dt, UQ(company_id, number)`
|
||||
- **entityaddress**: `id PK, entity_id->entity, first_name?:s(255), name?:s(255), street?:s(255), streetnumber?:s(50), zip?:s(50), city?:s(255), county?:s(255), addresstype:s(50), sort:i=0`
|
||||
- **entitycontact**: `id PK, entity_id->entity, contacttype:s(50), contact:s(255), sort:i=0`
|
||||
@@ -44,16 +49,29 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- **manifest**: `id PK, company_id->company, number:s(100), type:s(100), entity_id?->entity, external_number?:s(100), date:dt, created_at:dt`
|
||||
- **manifestarticle**: `id PK, manifest_id->manifest, sku?:s(100), name?:s(255), pu:f=1, amount:f, net:f, comment?:txt, rebate:f=0, taxrate:f=0`
|
||||
- **manifestattachment**: `id PK, manifest_id->manifest, file_id->file, sort:i=0, UQ(manifest_id, sort)`
|
||||
- **mcp_auth_code**: `code:s(128) PK, client_id:s(100), google_auth_id->google_auth, redirect_uri:s(2048), code_challenge:s(255), code_challenge_method:s(32), scope?:s(255), expires_at:dt, used:b=0, created_at:dt`
|
||||
- **mcp_client**: `id PK, client_id:s(100) UQ, client_secret?:s(255), client_name:s(255), redirect_uris:txt, grant_types?:s(255), response_types?:s(255), scope?:s(255), created_at:dt`
|
||||
- **mcp_token**: `id PK, refresh_token:s(128) UQ, client_id:s(100), google_auth_id->google_auth, scope?:s(255), expires_at:dt, revoked:b=0, created_at:dt`
|
||||
- **meta**: `key:s(255) PK, value:txt`
|
||||
- **modelconversation**: `id PK, companyuser_id->companyuser, provider:s(100), model:s(255), created_at:dt, updated_at:dt, state:s(20)='idle', started_by?:i`
|
||||
- **modelmessage**: `id PK, conversation_id->modelconversation, created_at:dt, role:s(20), message:txt`
|
||||
- **modelvfs**: `id PK, company_id->company, name:s(255), mime:s(100), content:bin, meta?:txt, created_at:dt, updated_at:dt, UQ(company_id, name)`
|
||||
- **numbers**: `id PK, company_id->company, type:s(100), prefix:s(50)='', padding:i=0, next:i=1, UQ(company_id, type)`
|
||||
- **optionslist**: `id PK`
|
||||
- **optionslistarticle**: `id PK, optionslist_id->optionslist, article_id->article, count:i=1, sort:i=0, mandated:b=0, UQ(optionslist_id, article_id), UQ(optionslist_id, sort)`
|
||||
- **ordersummary**: `id PK, company_id->company, manifest_id->manifest UQ, order_number:s(100), order_date:dt, created_at:dt, entity_id?->entity, customer_name?:s(255), positions:i=0, gross_total:f=0, paid_total:f=0, ordered_quantity:f=0, shipped_quantity:f=0, is_paid:b=0, is_shipped:b=0`
|
||||
- **package**: `id PK, shipping_id->shipping, entityaddress_id->entityaddress, number:s(100), type:s(100), state:s(100), details?:txt`
|
||||
- **payment**: `id PK, account_id->account, manifest_id->manifest, type:s(100), amount:f`
|
||||
- **pic**: `id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), source:txt, fullbinary:bin, thumbbinary:bin`
|
||||
- **posterminal**: `id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin?:s(50), root_category_id?->category, sync_root_articles:b=1, lastseen_at?:dt, updated_at:dt`
|
||||
- **posterminalcategory**: `id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)`
|
||||
- **price**: `id PK, tax_id->tax, article_id->article, minmultiplier:d(18,4)=1, customergroup_id->customergroup, net:f`
|
||||
- **printer**: `id PK, company_id->company, address:s(255), type:s(50), model?:s(255), serial?:s(255), sort:i=0, desc?:txt`
|
||||
- **propertyname**: `id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)`
|
||||
- **quotesummary**: `id PK, company_id->company, manifest_id->manifest UQ, quote_number:s(100), quote_date:dt, created_at:dt, entity_id?->entity, customer_name?:s(255), positions:i=0, gross_total:f=0, total_quantity:f=0`
|
||||
- **shipmentsummary**: `id PK, company_id->company, manifest_id->manifest UQ, shipment_number:s(100), shipment_date:dt, created_at:dt, order_id?:i, order_number?:s(100), entity_id?->entity, customer_name?:s(255), positions:i=0, total_quantity:f=0, package_count:i=0, carrier?:s(100), tracking_code?:s(255), state:s(50)='new'`
|
||||
- **shipping**: `id PK, manifest_id->manifest, number:s(100), tracking_code?:s(255), carrier?:s(100), type:s(100), state:s(100)`
|
||||
- **subagentschedule**: `id PK, company_id->company, desc?:txt, prompt:txt, skills?:txt, lastagentnote?:txt, max_itr:i=15, cron:s(100), created_at:dt, lastrun_at?:dt`
|
||||
- **tax**: `id PK, rate:d(18,4) UQ`
|
||||
- **unit**: `id PK, company_id->company, si:s(50), basesi:s(50), basesifactor:f, UQ(company_id, si)`
|
||||
- **unittext**: `id PK, unit_id->unit, language_id->language, symbol:s(50), name:s(255), UQ(unit_id, language_id)`
|
||||
@@ -363,5 +381,9 @@ The store is exposed as a `LibDashStore` instance (see `src/index.js`). Each met
|
||||
- `create(googleAuthId: number, input: {accountId: number, manifestId: number, type: string, amount: number}, tx?: object): Promise<{id: number, accountId: number, manifestId: number, type: string, amount: number}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `listByManifestId(googleAuthId: number, manifestId: number, tx?: object): Promise<Array<{id: number, accountId: number, manifestId: number, type: string, amount: number}>>`
|
||||
- [runner.js](API.runner.js.md)
|
||||
- `runner(provider: import('../providers/provider.js').DatabaseProvider, tx?: object): ((sql: string, params?: object) => Promise<import('../providers/provider.js').QueryResult>) & { query: Function, dialect?: string }`
|
||||
- [subagentschedule](API.subagentschedule.md)
|
||||
- `create(googleAuthId: number, input: {prompt: string, cron: string, desc?: string, skills?: string|Array<string>, lastagentnote?: string, max_itr?: number}, tx?: object): Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>`
|
||||
- `delete(googleAuthId: number, id: number, tx?: object): Promise<void>`
|
||||
- `getById(googleAuthId: number, id: number, tx?: object): Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}|null>`
|
||||
- `list(googleAuthId: number, tx?: object): Promise<Array<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>>`
|
||||
- `update(googleAuthId: number, id: number, input: {prompt?: string, cron?: string, desc?: string|null, skills?: string|Array<string>|null, lastagentnote?: string|null, max_itr?: number, lastrun_at?: string|null}, tx?: object): Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>`
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# runner.js
|
||||
|
||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||
|
||||
## `runner.js.runner`
|
||||
|
||||
*Source: `src/store/runner.js`*
|
||||
|
||||
Resolves the object a store method should query through. Converted store modules call `runner(sql, params)` or `runner.query(sql, params)`. During the incremental migration they may still be constructed with a raw `mssql` pool, or called with a raw `mssql` Transaction as `tx`. This adapter exposes the unified runner contract over both so mixed call sites keep working until every module is converted.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `provider` | `import('../providers/provider.js').DatabaseProvider` | |
|
||||
| `[tx]` | `object` | Optional provider runner or legacy mssql Transaction. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`((sql: string, params?: object) => Promise<import('../providers/provider.js').QueryResult>) & { query: Function, dialect?: string }`
|
||||
130
API.subagentschedule.md
Normal file
130
API.subagentschedule.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# subagentschedule
|
||||
|
||||
This document is auto-generated from the JSDoc comments in `src/store` by running `npm run api-doc`.
|
||||
|
||||
## `subagentschedule.create`
|
||||
|
||||
*Source: `src/store/subagentschedule/create.js`*
|
||||
|
||||
Creates a new subagent schedule for the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `input` | `object` | Schedule input. |
|
||||
| `input.prompt` | `string` | The task prompt for the subagent. |
|
||||
| `input.cron` | `string` | Standard 5-field cron timetable string. |
|
||||
| `[input.desc]` | `string` | Optional schedule description. |
|
||||
| `[input.skills]` | `string|Array<string>` | Optional skill names or JSON string. |
|
||||
| `[input.lastagentnote]` | `string` | Optional initial agent note. |
|
||||
| `[input.max_itr=15]` | `number` | Optional max iterations per run. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>`
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — If prompt is missing, cron is invalid, or max_itr is invalid.
|
||||
|
||||
**Example**
|
||||
|
||||
```js
|
||||
const schedule = await store.subagentschedule.create(googleAuthId, {
|
||||
desc: 'Nightly inventory audit',
|
||||
prompt: 'Check stock discrepancies across all warehouses and summarize.',
|
||||
cron: '0 2 * * *',
|
||||
skills: ['stock', 'reporting'],
|
||||
max_itr: 20,
|
||||
});
|
||||
```
|
||||
|
||||
## `subagentschedule.delete`
|
||||
|
||||
*Source: `src/store/subagentschedule/delete.js`*
|
||||
|
||||
Deletes a subagent schedule by id for the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The schedule ID to delete. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<void>`
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — If schedule not found.
|
||||
|
||||
## `subagentschedule.getById`
|
||||
|
||||
*Source: `src/store/subagentschedule/getById.js`*
|
||||
|
||||
Retrieves a single subagent schedule by id for the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The schedule ID. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}|null>`
|
||||
|
||||
## `subagentschedule.list`
|
||||
|
||||
*Source: `src/store/subagentschedule/list.js`*
|
||||
|
||||
Lists all subagent schedules for the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<Array<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>>`
|
||||
|
||||
## `subagentschedule.update`
|
||||
|
||||
*Source: `src/store/subagentschedule/update.js`*
|
||||
|
||||
Updates an existing subagent schedule for the user's selected company.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `googleAuthId` | `number` | The id of the Google auth user. |
|
||||
| `id` | `number` | The schedule ID to update. |
|
||||
| `input` | `object` | Update payload. |
|
||||
| `[input.prompt]` | `string` | Optional updated task prompt. |
|
||||
| `[input.cron]` | `string` | Optional updated cron expression. |
|
||||
| `[input.desc]` | `string|null` | Optional updated description. |
|
||||
| `[input.skills]` | `string|Array<string>|null` | Optional updated skills. |
|
||||
| `[input.lastagentnote]` | `string|null` | Optional updated agent note. |
|
||||
| `[input.max_itr]` | `number` | Optional updated max iterations. |
|
||||
| `[input.lastrun_at]` | `string|null` | Optional updated last run timestamp. |
|
||||
| `[tx]` | `object` | Optional transaction to run within. |
|
||||
|
||||
**Returns**
|
||||
|
||||
`Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>`
|
||||
|
||||
**Throws**
|
||||
|
||||
`Error` — If schedule not found, prompt is empty, cron is invalid, or max_itr is invalid.
|
||||
@@ -75,6 +75,8 @@ const INTERNAL_FILES = new Set([
|
||||
'shape.js',
|
||||
'createStore.js',
|
||||
'manifestLines.js',
|
||||
'runner.js',
|
||||
'cron.js',
|
||||
'index.js',
|
||||
]);
|
||||
|
||||
@@ -321,6 +323,7 @@ function render() {
|
||||
}
|
||||
|
||||
const SCHEMA_TABLES = [
|
||||
['account', 'id PK, company_id->company, type:s(100), name:s(255)'],
|
||||
['article', 'id PK, sku?:s(100) UQ, unit_id->unit, unitamount:f, weight:f, track_stock:b=1, brand_id?->brand, articlelist_id?->articlelist, optionslist_id?->optionslist, is_parent:b=0, parentarticle_id?->article, parentattributename_id?->attributename, parentsort:i=0, created_at:dt, updated_at:dt, UQ(parentarticle_id, parentsort)'],
|
||||
['articleattributevalue', 'id PK, article_id->article, attributevalue_id->attributevalue, sort:i=0, UQ(article_id, attributevalue_id)'],
|
||||
['articleean', 'id PK, article_id->article, ean:s(50) UQ'],
|
||||
@@ -331,6 +334,7 @@ const SCHEMA_TABLES = [
|
||||
['articleproperty', 'id PK, article_id->article, property_id->propertyname, value:txt, sort:i=0'],
|
||||
['articlesupplier', 'id PK, article_id->article, entity_id->entity, net?:f, tax_id?->tax, sku?:s(100), stock?:f, pu:f=1, sort:i=0, UQ(article_id, entity_id), UQ(article_id, sort)'],
|
||||
['articletext', 'id PK, article_id->article, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(article_id, language_id)'],
|
||||
['articletombstone', 'id PK, company_id->company, article_id?:i, sku?:s(100), name?:s(255), taxrate?:d(18,4), net?:f, deleted_at:dt'],
|
||||
['attributename', 'id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)'],
|
||||
['attributevalue', 'id PK, attribute_id->attributename, value:s(255), sort:i=0, UQ(attribute_id, value)'],
|
||||
['attributevaluepic', 'id PK, attributevalue_id->attributevalue, pic_id->pic, sort:i=0, UQ(attributevalue_id, pic_id), UQ(attributevalue_id, sort)'],
|
||||
@@ -338,13 +342,16 @@ const SCHEMA_TABLES = [
|
||||
['brandpic', 'id PK, brand_id->brand, pic_id->pic, sort:i=0, UQ(brand_id, pic_id), UQ(brand_id, sort)'],
|
||||
['category', 'id PK, parent_id?->category, sort:i=0'],
|
||||
['categoryarticle', 'id PK, category_id->category, article_id->article, UQ(category_id, article_id)'],
|
||||
['categoryattributevalue', 'id PK, category_id->category, attributevalue_id->attributevalue, sort:i=0, UQ(category_id, attributevalue_id)'],
|
||||
['categorypic', 'id PK, category_id->category, pic_id->pic, sort:i=0, UQ(category_id, pic_id), UQ(category_id, sort)'],
|
||||
['categoryproperty', 'id PK, category_id->category, property_id->propertyname, value:txt, sort:i=0'],
|
||||
['categorytext', 'id PK, category_id->category, language_id->language, name:s(255), slug:s(255), seo?:txt, desc?:txt, UQ(category_id, language_id)'],
|
||||
['company', 'id PK, name:s(255) UQ, root_category_id?->category, default_language_id?->language, taxrate?->tax, reducedtaxrate?->tax, taxfreerate?->tax, poscustomergroup_id->customergroup'],
|
||||
['companyhistory', 'id PK, company_id->company, companyuser_id?->companyuser, date:dt, text:txt'],
|
||||
['companysetting', 'id PK, company_id->company, key:s(255), value:txt, frontend:b=0, writeonly:b=0, UQ(company_id, key)'],
|
||||
['companyuser', 'id PK, google_auth_id->google_auth, company_id->company, UQ(google_auth_id, company_id)'],
|
||||
['customergroup', 'id PK, name:s(255), sort:i=0, company_id?->company'],
|
||||
['deliverysummary', 'id PK, company_id->company, manifest_id->manifest UQ, delivery_number:s(100), external_number?:s(100), delivery_date:dt, created_at:dt, supplier_id?->entity, supplier_name?:s(255), positions:i=0, gross_total:f=0, net_total:f=0, total_quantity:f=0, stock_positions:i=0, booked_positions:i=0, unbooked_positions:i=0, stock_quantity:f=0, booked_quantity:f=0, unbooked_quantity:f=0, is_booked:b=0'],
|
||||
['entity', 'id PK, customergroup_id->customergroup, first_name?:s(255), name:s(255), number?:s(100), created_at:dt, updated_at:dt, UQ(company_id, number)'],
|
||||
['entityaddress', 'id PK, entity_id->entity, first_name?:s(255), name?:s(255), street?:s(255), streetnumber?:s(50), zip?:s(50), city?:s(255), county?:s(255), addresstype:s(50), sort:i=0'],
|
||||
['entitycontact', 'id PK, entity_id->entity, contacttype:s(50), contact:s(255), sort:i=0'],
|
||||
@@ -357,16 +364,29 @@ const SCHEMA_TABLES = [
|
||||
['manifest', 'id PK, company_id->company, number:s(100), type:s(100), entity_id?->entity, external_number?:s(100), date:dt, created_at:dt'],
|
||||
['manifestarticle', 'id PK, manifest_id->manifest, sku?:s(100), name?:s(255), pu:f=1, amount:f, net:f, comment?:txt, rebate:f=0, taxrate:f=0'],
|
||||
['manifestattachment', 'id PK, manifest_id->manifest, file_id->file, sort:i=0, UQ(manifest_id, sort)'],
|
||||
['mcp_auth_code', 'code:s(128) PK, client_id:s(100), google_auth_id->google_auth, redirect_uri:s(2048), code_challenge:s(255), code_challenge_method:s(32), scope?:s(255), expires_at:dt, used:b=0, created_at:dt'],
|
||||
['mcp_client', 'id PK, client_id:s(100) UQ, client_secret?:s(255), client_name:s(255), redirect_uris:txt, grant_types?:s(255), response_types?:s(255), scope?:s(255), created_at:dt'],
|
||||
['mcp_token', 'id PK, refresh_token:s(128) UQ, client_id:s(100), google_auth_id->google_auth, scope?:s(255), expires_at:dt, revoked:b=0, created_at:dt'],
|
||||
['meta', 'key:s(255) PK, value:txt'],
|
||||
['modelconversation', 'id PK, companyuser_id->companyuser, provider:s(100), model:s(255), created_at:dt, updated_at:dt, state:s(20)=\'idle\', started_by?:i'],
|
||||
['modelmessage', 'id PK, conversation_id->modelconversation, created_at:dt, role:s(20), message:txt'],
|
||||
['modelvfs', 'id PK, company_id->company, name:s(255), mime:s(100), content:bin, meta?:txt, created_at:dt, updated_at:dt, UQ(company_id, name)'],
|
||||
['numbers', 'id PK, company_id->company, type:s(100), prefix:s(50)=\'\', padding:i=0, next:i=1, UQ(company_id, type)'],
|
||||
['optionslist', 'id PK'],
|
||||
['optionslistarticle', 'id PK, optionslist_id->optionslist, article_id->article, count:i=1, sort:i=0, mandated:b=0, UQ(optionslist_id, article_id), UQ(optionslist_id, sort)'],
|
||||
['ordersummary', 'id PK, company_id->company, manifest_id->manifest UQ, order_number:s(100), order_date:dt, created_at:dt, entity_id?->entity, customer_name?:s(255), positions:i=0, gross_total:f=0, paid_total:f=0, ordered_quantity:f=0, shipped_quantity:f=0, is_paid:b=0, is_shipped:b=0'],
|
||||
['package', 'id PK, shipping_id->shipping, entityaddress_id->entityaddress, number:s(100), type:s(100), state:s(100), details?:txt'],
|
||||
['payment', 'id PK, account_id->account, manifest_id->manifest, type:s(100), amount:f'],
|
||||
['pic', 'id PK, w:i, h:i, size:bi, hash:s(64) UQ, thumbw:i, thumbh:i, thumbhash:s(64), source:txt, fullbinary:bin, thumbbinary:bin'],
|
||||
['posterminal', 'id PK, company_id->company, name:s(255), token?:s(255) UQ, pairingpin?:s(50), root_category_id?->category, sync_root_articles:b=1, lastseen_at?:dt, updated_at:dt'],
|
||||
['posterminalcategory', 'id PK, posterminal_id->posterminal, category_id->category, UQ(posterminal_id, category_id)'],
|
||||
['price', 'id PK, tax_id->tax, article_id->article, minmultiplier:d(18,4)=1, customergroup_id->customergroup, net:f'],
|
||||
['printer', 'id PK, company_id->company, address:s(255), type:s(50), model?:s(255), serial?:s(255), sort:i=0, desc?:txt'],
|
||||
['propertyname', 'id PK, company_id->company, name:s(255), sort:i=0, exclusive:b=0, UQ(company_id, name)'],
|
||||
['quotesummary', 'id PK, company_id->company, manifest_id->manifest UQ, quote_number:s(100), quote_date:dt, created_at:dt, entity_id?->entity, customer_name?:s(255), positions:i=0, gross_total:f=0, total_quantity:f=0'],
|
||||
['shipmentsummary', 'id PK, company_id->company, manifest_id->manifest UQ, shipment_number:s(100), shipment_date:dt, created_at:dt, order_id?:i, order_number?:s(100), entity_id?->entity, customer_name?:s(255), positions:i=0, total_quantity:f=0, package_count:i=0, carrier?:s(100), tracking_code?:s(255), state:s(50)=\'new\''],
|
||||
['shipping', 'id PK, manifest_id->manifest, number:s(100), tracking_code?:s(255), carrier?:s(100), type:s(100), state:s(100)'],
|
||||
['subagentschedule', 'id PK, company_id->company, desc?:txt, prompt:txt, skills?:txt, lastagentnote?:txt, max_itr:i=15, cron:s(100), created_at:dt, lastrun_at?:dt'],
|
||||
['tax', 'id PK, rate:d(18,4) UQ'],
|
||||
['unit', 'id PK, company_id->company, si:s(50), basesi:s(50), basesifactor:f, UQ(company_id, si)'],
|
||||
['unittext', 'id PK, unit_id->unit, language_id->language, symbol:s(50), name:s(255), UQ(unit_id, language_id)'],
|
||||
|
||||
@@ -394,7 +394,7 @@ const CLUSTERS = [
|
||||
{
|
||||
id: 'ai',
|
||||
label: 'AI & Models',
|
||||
tables: ['modelconversation', 'modelmessage', 'modelvfs'],
|
||||
tables: ['modelconversation', 'modelmessage', 'modelvfs', 'subagentschedule'],
|
||||
},
|
||||
{
|
||||
id: 'attributes',
|
||||
|
||||
280
schema-graph.dot
280
schema-graph.dot
File diff suppressed because one or more lines are too long
6165
schema-graph.svg
6165
schema-graph.svg
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 386 KiB After Width: | Height: | Size: 396 KiB |
13
src/index.js
13
src/index.js
@@ -183,6 +183,11 @@ import modelvfsRead from './store/modelvfs/read.js';
|
||||
import modelvfsList from './store/modelvfs/list.js';
|
||||
import modelvfsDelete from './store/modelvfs/delete.js';
|
||||
import modelvfsReset from './store/modelvfs/reset.js';
|
||||
import subagentscheduleCreate from './store/subagentschedule/create.js';
|
||||
import subagentscheduleGetById from './store/subagentschedule/getById.js';
|
||||
import subagentscheduleList from './store/subagentschedule/list.js';
|
||||
import subagentscheduleUpdate from './store/subagentschedule/update.js';
|
||||
import subagentscheduleDelete from './store/subagentschedule/delete.js';
|
||||
import accountCreate from './store/account/create.js';
|
||||
import accountList from './store/account/list.js';
|
||||
import accountDelete from './store/account/delete.js';
|
||||
@@ -584,6 +589,14 @@ export default class LibDashStore {
|
||||
add: modelmessageAdd(provider),
|
||||
};
|
||||
|
||||
this.subagentschedule = {
|
||||
create: subagentscheduleCreate(provider),
|
||||
getById: subagentscheduleGetById(provider),
|
||||
list: subagentscheduleList(provider),
|
||||
update: subagentscheduleUpdate(provider),
|
||||
delete: subagentscheduleDelete(provider),
|
||||
};
|
||||
|
||||
this.labeltemplate = {
|
||||
create: labeltemplateCreate(provider),
|
||||
getById: labeltemplateGetById(provider),
|
||||
|
||||
88
src/providers/mssql/queries/subagentschedule.js
Normal file
88
src/providers/mssql/queries/subagentschedule.js
Normal file
@@ -0,0 +1,88 @@
|
||||
export const SUBAGENTSCHEDULE_COLUMNS = `
|
||||
id,
|
||||
company_id,
|
||||
[desc],
|
||||
prompt,
|
||||
skills,
|
||||
lastagentnote,
|
||||
max_itr,
|
||||
cron,
|
||||
created_at,
|
||||
lastrun_at
|
||||
`;
|
||||
|
||||
export const LIST_BY_COMPANY = `
|
||||
SELECT
|
||||
${SUBAGENTSCHEDULE_COLUMNS}
|
||||
FROM subagentschedule
|
||||
WHERE company_id = @companyId
|
||||
ORDER BY id ASC
|
||||
`;
|
||||
|
||||
export const GET_BY_COMPANY_AND_ID = `
|
||||
SELECT
|
||||
${SUBAGENTSCHEDULE_COLUMNS}
|
||||
FROM subagentschedule
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const CREATE = `
|
||||
INSERT INTO subagentschedule (
|
||||
company_id,
|
||||
[desc],
|
||||
prompt,
|
||||
skills,
|
||||
lastagentnote,
|
||||
max_itr,
|
||||
cron
|
||||
)
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.company_id,
|
||||
INSERTED.[desc],
|
||||
INSERTED.prompt,
|
||||
INSERTED.skills,
|
||||
INSERTED.lastagentnote,
|
||||
INSERTED.max_itr,
|
||||
INSERTED.cron,
|
||||
INSERTED.created_at,
|
||||
INSERTED.lastrun_at
|
||||
VALUES (
|
||||
@companyId,
|
||||
@desc,
|
||||
@prompt,
|
||||
@skills,
|
||||
@lastagentnote,
|
||||
@maxItr,
|
||||
@cron
|
||||
)
|
||||
`;
|
||||
|
||||
export const UPDATE = `
|
||||
UPDATE subagentschedule
|
||||
SET
|
||||
[desc] = @desc,
|
||||
prompt = @prompt,
|
||||
skills = @skills,
|
||||
lastagentnote = @lastagentnote,
|
||||
max_itr = @maxItr,
|
||||
cron = @cron,
|
||||
lastrun_at = @lastrunAt
|
||||
OUTPUT
|
||||
INSERTED.id,
|
||||
INSERTED.company_id,
|
||||
INSERTED.[desc],
|
||||
INSERTED.prompt,
|
||||
INSERTED.skills,
|
||||
INSERTED.lastagentnote,
|
||||
INSERTED.max_itr,
|
||||
INSERTED.cron,
|
||||
INSERTED.created_at,
|
||||
INSERTED.lastrun_at
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
|
||||
export const DELETE_BY_COMPANY_AND_ID = `
|
||||
DELETE FROM subagentschedule
|
||||
WHERE id = @id AND company_id = @companyId
|
||||
`;
|
||||
@@ -1376,6 +1376,26 @@ BEGIN
|
||||
END
|
||||
`;
|
||||
|
||||
export const INIT_SUBAGENTSCHEDULE = `
|
||||
IF NOT EXISTS (SELECT * FROM sys.tables WHERE name = 'subagentschedule')
|
||||
BEGIN
|
||||
CREATE TABLE subagentschedule (
|
||||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||||
company_id INT NOT NULL,
|
||||
[desc] NVARCHAR(MAX) NULL,
|
||||
prompt NVARCHAR(MAX) NOT NULL,
|
||||
skills NVARCHAR(MAX) NULL,
|
||||
lastagentnote NVARCHAR(MAX) NULL,
|
||||
max_itr INT NOT NULL CONSTRAINT DF_subagentschedule_max_itr DEFAULT 15,
|
||||
cron NVARCHAR(100) NOT NULL,
|
||||
created_at DATETIME2 NOT NULL CONSTRAINT DF_subagentschedule_created_at DEFAULT SYSUTCDATETIME(),
|
||||
lastrun_at DATETIME2 NULL,
|
||||
CONSTRAINT FK_subagentschedule_company FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IX_subagentschedule_company ON subagentschedule (company_id);
|
||||
END
|
||||
`;
|
||||
|
||||
/** DDL scripts in dependency order for schema init. */
|
||||
export const SCHEMA_SCRIPTS = [
|
||||
INIT_META,
|
||||
@@ -1450,4 +1470,5 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_MCP_CLIENT,
|
||||
INIT_MCP_AUTH_CODE,
|
||||
INIT_MCP_TOKEN,
|
||||
INIT_SUBAGENTSCHEDULE,
|
||||
];
|
||||
|
||||
12
src/providers/sqlite3/queries/subagentschedule.js
Normal file
12
src/providers/sqlite3/queries/subagentschedule.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import * as source from '../../mssql/queries/subagentschedule.js';
|
||||
import { translateQueries } from './translate.js';
|
||||
|
||||
const queries = translateQueries(source, 'subagentschedule');
|
||||
export const {
|
||||
CREATE,
|
||||
DELETE_BY_COMPANY_AND_ID,
|
||||
GET_BY_COMPANY_AND_ID,
|
||||
LIST_BY_COMPANY,
|
||||
SUBAGENTSCHEDULE_COLUMNS,
|
||||
UPDATE,
|
||||
} = queries;
|
||||
@@ -1011,6 +1011,23 @@ CREATE TABLE IF NOT EXISTS mcp_token (
|
||||
);
|
||||
`;
|
||||
|
||||
export const INIT_SUBAGENTSCHEDULE = `
|
||||
CREATE TABLE IF NOT EXISTS subagentschedule (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
company_id INTEGER NOT NULL,
|
||||
"desc" TEXT,
|
||||
prompt TEXT NOT NULL,
|
||||
skills TEXT,
|
||||
lastagentnote TEXT,
|
||||
max_itr INTEGER NOT NULL DEFAULT 15,
|
||||
cron TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (${UTC_NOW}),
|
||||
lastrun_at TEXT,
|
||||
FOREIGN KEY (company_id) REFERENCES company(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS IX_subagentschedule_company ON subagentschedule (company_id);
|
||||
`;
|
||||
|
||||
/** DDL scripts in dependency order for SQLite schema initialization. */
|
||||
export const SCHEMA_SCRIPTS = [
|
||||
INIT_META,
|
||||
@@ -1082,4 +1099,5 @@ export const SCHEMA_SCRIPTS = [
|
||||
INIT_MCP_CLIENT,
|
||||
INIT_MCP_AUTH_CODE,
|
||||
INIT_MCP_TOKEN,
|
||||
INIT_SUBAGENTSCHEDULE,
|
||||
];
|
||||
|
||||
118
src/store/subagentschedule/create.js
Normal file
118
src/store/subagentschedule/create.js
Normal file
@@ -0,0 +1,118 @@
|
||||
import { CREATE as MSSQL_CREATE } from '../../providers/mssql/queries/subagentschedule.js';
|
||||
import { CREATE as SQLITE_CREATE } from '../../providers/sqlite3/queries/subagentschedule.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import { isValidCron } from './cron.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
company_id: row.company_id ?? row.companyId,
|
||||
desc: row.desc ?? null,
|
||||
prompt: row.prompt,
|
||||
skills: row.skills ?? null,
|
||||
lastagentnote: row.lastagentnote ?? null,
|
||||
max_itr: row.max_itr,
|
||||
cron: row.cron,
|
||||
created_at: row.created_at,
|
||||
lastrun_at: row.lastrun_at ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function optionalString(value) {
|
||||
if (value == null || value === '') return null;
|
||||
const s = String(value).trim();
|
||||
return s || null;
|
||||
}
|
||||
|
||||
export default function create(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const createSql = isSqlite ? SQLITE_CREATE : MSSQL_CREATE;
|
||||
|
||||
/**
|
||||
* Creates a new subagent schedule for the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} input - Schedule input.
|
||||
* @param {string} input.prompt - The task prompt for the subagent.
|
||||
* @param {string} input.cron - Standard 5-field cron timetable string.
|
||||
* @param {string} [input.desc] - Optional schedule description.
|
||||
* @param {string|Array<string>} [input.skills] - Optional skill names or JSON string.
|
||||
* @param {string} [input.lastagentnote] - Optional initial agent note.
|
||||
* @param {number} [input.max_itr=15] - Optional max iterations per run.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>}
|
||||
* @throws {Error} If prompt is missing, cron is invalid, or max_itr is invalid.
|
||||
*
|
||||
* @example
|
||||
* const schedule = await store.subagentschedule.create(googleAuthId, {
|
||||
* desc: 'Nightly inventory audit',
|
||||
* prompt: 'Check stock discrepancies across all warehouses and summarize.',
|
||||
* cron: '0 2 * * *',
|
||||
* skills: ['stock', 'reporting'],
|
||||
* max_itr: 20,
|
||||
* });
|
||||
*/
|
||||
return async (googleAuthId, input = {}, tx) => {
|
||||
const prompt = typeof input.prompt === 'string' ? input.prompt.trim() : '';
|
||||
if (!prompt) {
|
||||
throw new Error('Prompt required');
|
||||
}
|
||||
|
||||
const cron = typeof input.cron === 'string' ? input.cron.trim() : '';
|
||||
if (!cron) {
|
||||
throw new Error('Cron expression required');
|
||||
}
|
||||
if (!isValidCron(cron)) {
|
||||
throw new Error('Invalid cron expression');
|
||||
}
|
||||
|
||||
let maxItr = 15;
|
||||
if (input.max_itr !== undefined && input.max_itr !== null) {
|
||||
const parsed = Number(input.max_itr);
|
||||
if (!Number.isInteger(parsed) || parsed <= 0) {
|
||||
throw new Error('Invalid max iterations: must be a positive integer');
|
||||
}
|
||||
maxItr = parsed;
|
||||
}
|
||||
|
||||
let skills = null;
|
||||
if (Array.isArray(input.skills)) {
|
||||
skills = JSON.stringify(input.skills);
|
||||
} else if (input.skills != null) {
|
||||
skills = optionalString(input.skills);
|
||||
}
|
||||
|
||||
const desc = optionalString(input.desc);
|
||||
const lastagentnote = optionalString(input.lastagentnote);
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(createSql, {
|
||||
companyId,
|
||||
desc,
|
||||
prompt,
|
||||
skills,
|
||||
lastagentnote,
|
||||
maxItr,
|
||||
cron,
|
||||
});
|
||||
|
||||
const row = result.rows[0] ?? {
|
||||
id: result.insertId,
|
||||
company_id: companyId,
|
||||
desc,
|
||||
prompt,
|
||||
skills,
|
||||
lastagentnote,
|
||||
max_itr: maxItr,
|
||||
cron,
|
||||
created_at: new Date().toISOString(),
|
||||
lastrun_at: null,
|
||||
};
|
||||
|
||||
return shape(row);
|
||||
};
|
||||
}
|
||||
99
src/store/subagentschedule/cron.js
Normal file
99
src/store/subagentschedule/cron.js
Normal file
@@ -0,0 +1,99 @@
|
||||
const CRON_NICKNAMES = new Set([
|
||||
'@yearly',
|
||||
'@annually',
|
||||
'@monthly',
|
||||
'@weekly',
|
||||
'@daily',
|
||||
'@midnight',
|
||||
'@hourly',
|
||||
]);
|
||||
|
||||
const MONTH_NAMES = {
|
||||
jan: 1, feb: 2, mar: 3, apr: 4, may: 5, jun: 6,
|
||||
jul: 7, aug: 8, sep: 9, oct: 10, nov: 11, dec: 12,
|
||||
};
|
||||
|
||||
const DAY_NAMES = {
|
||||
sun: 0, mon: 1, tue: 2, wed: 3, thu: 4, fri: 5, sat: 6,
|
||||
};
|
||||
|
||||
const FIELD_RANGES = [
|
||||
{ name: 'minute', min: 0, max: 59 },
|
||||
{ name: 'hour', min: 0, max: 23 },
|
||||
{ name: 'day of month', min: 1, max: 31 },
|
||||
{ name: 'month', min: 1, max: 12, names: MONTH_NAMES },
|
||||
{ name: 'day of week', min: 0, max: 7, names: DAY_NAMES },
|
||||
];
|
||||
|
||||
function parseCronValue(val, min, max, names) {
|
||||
if (names && typeof val === 'string') {
|
||||
const lower = val.toLowerCase();
|
||||
if (lower in names) return names[lower];
|
||||
}
|
||||
if (!/^\d+$/.test(val)) return null;
|
||||
const num = parseInt(val, 10);
|
||||
if (num < min || num > max) return null;
|
||||
return num;
|
||||
}
|
||||
|
||||
function validateField(part, min, max, names) {
|
||||
if (!part) return false;
|
||||
const items = part.split(',');
|
||||
if (items.length === 0) return false;
|
||||
|
||||
for (const item of items) {
|
||||
if (!item) return false;
|
||||
const slashParts = item.split('/');
|
||||
if (slashParts.length > 2) return false;
|
||||
if (slashParts.length === 2) {
|
||||
const step = slashParts[1];
|
||||
if (!/^\d+$/.test(step)) return false;
|
||||
const stepNum = parseInt(step, 10);
|
||||
if (stepNum <= 0 || stepNum > max) return false;
|
||||
}
|
||||
|
||||
const base = slashParts[0];
|
||||
if (base === '*' || base === '?') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const dashParts = base.split('-');
|
||||
if (dashParts.length > 2) return false;
|
||||
if (dashParts.length === 2) {
|
||||
const start = parseCronValue(dashParts[0], min, max, names);
|
||||
const end = parseCronValue(dashParts[1], min, max, names);
|
||||
if (start === null || end === null || start > end) return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
const val = parseCronValue(base, min, max, names);
|
||||
if (val === null) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a standard 5-part cron timetable string or standard nickname.
|
||||
*
|
||||
* @param {string} cron - The cron expression to validate.
|
||||
* @returns {boolean} True if the cron expression is syntactically valid.
|
||||
*/
|
||||
export function isValidCron(cron) {
|
||||
if (typeof cron !== 'string') return false;
|
||||
const trimmed = cron.trim();
|
||||
if (!trimmed) return false;
|
||||
if (CRON_NICKNAMES.has(trimmed.toLowerCase())) return true;
|
||||
|
||||
const parts = trimmed.split(/\s+/);
|
||||
if (parts.length !== 5) return false;
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const part = parts[i];
|
||||
const { min, max, names } = FIELD_RANGES[i];
|
||||
if (!validateField(part, min, max, names)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
34
src/store/subagentschedule/delete.js
Normal file
34
src/store/subagentschedule/delete.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { DELETE_BY_COMPANY_AND_ID as MSSQL_DELETE } from '../../providers/mssql/queries/subagentschedule.js';
|
||||
import { DELETE_BY_COMPANY_AND_ID as SQLITE_DELETE } from '../../providers/sqlite3/queries/subagentschedule.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
export default function remove(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const deleteSql = isSqlite ? SQLITE_DELETE : MSSQL_DELETE;
|
||||
|
||||
/**
|
||||
* Deletes a subagent schedule by id for the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} id - The schedule ID to delete.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<void>}
|
||||
* @throws {Error} If schedule not found.
|
||||
*/
|
||||
return async (googleAuthId, id, tx) => {
|
||||
const numericId = Number(id);
|
||||
if (!Number.isInteger(numericId) || numericId <= 0) {
|
||||
throw new Error('Subagent schedule not found');
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(deleteSql, { id: numericId, companyId });
|
||||
if (result.rowCount === 0) {
|
||||
throw new Error('Subagent schedule not found');
|
||||
}
|
||||
};
|
||||
}
|
||||
47
src/store/subagentschedule/getById.js
Normal file
47
src/store/subagentschedule/getById.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { GET_BY_COMPANY_AND_ID as MSSQL_GET_BY_ID } from '../../providers/mssql/queries/subagentschedule.js';
|
||||
import { GET_BY_COMPANY_AND_ID as SQLITE_GET_BY_ID } from '../../providers/sqlite3/queries/subagentschedule.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
company_id: row.company_id ?? row.companyId,
|
||||
desc: row.desc ?? null,
|
||||
prompt: row.prompt,
|
||||
skills: row.skills ?? null,
|
||||
lastagentnote: row.lastagentnote ?? null,
|
||||
max_itr: row.max_itr,
|
||||
cron: row.cron,
|
||||
created_at: row.created_at,
|
||||
lastrun_at: row.lastrun_at ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export default function getById(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const getSql = isSqlite ? SQLITE_GET_BY_ID : MSSQL_GET_BY_ID;
|
||||
|
||||
/**
|
||||
* Retrieves a single subagent schedule by id for the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} id - The schedule ID.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}|null>}
|
||||
*/
|
||||
return async (googleAuthId, id, tx) => {
|
||||
const numericId = Number(id);
|
||||
if (!Number.isInteger(numericId) || numericId <= 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(getSql, { id: numericId, companyId });
|
||||
const row = result.rows[0];
|
||||
return row ? shape(row) : null;
|
||||
};
|
||||
}
|
||||
40
src/store/subagentschedule/list.js
Normal file
40
src/store/subagentschedule/list.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { LIST_BY_COMPANY as MSSQL_LIST } from '../../providers/mssql/queries/subagentschedule.js';
|
||||
import { LIST_BY_COMPANY as SQLITE_LIST } from '../../providers/sqlite3/queries/subagentschedule.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
company_id: row.company_id ?? row.companyId,
|
||||
desc: row.desc ?? null,
|
||||
prompt: row.prompt,
|
||||
skills: row.skills ?? null,
|
||||
lastagentnote: row.lastagentnote ?? null,
|
||||
max_itr: row.max_itr,
|
||||
cron: row.cron,
|
||||
created_at: row.created_at,
|
||||
lastrun_at: row.lastrun_at ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export default function list(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const listSql = isSqlite ? SQLITE_LIST : MSSQL_LIST;
|
||||
|
||||
/**
|
||||
* Lists all subagent schedules for the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<Array<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>>}
|
||||
*/
|
||||
return async (googleAuthId, tx) => {
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const result = await runner.query(listSql, { companyId });
|
||||
return result.rows.map(shape);
|
||||
};
|
||||
}
|
||||
142
src/store/subagentschedule/update.js
Normal file
142
src/store/subagentschedule/update.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
GET_BY_COMPANY_AND_ID as MSSQL_GET_BY_ID,
|
||||
UPDATE as MSSQL_UPDATE,
|
||||
} from '../../providers/mssql/queries/subagentschedule.js';
|
||||
import {
|
||||
GET_BY_COMPANY_AND_ID as SQLITE_GET_BY_ID,
|
||||
UPDATE as SQLITE_UPDATE,
|
||||
} from '../../providers/sqlite3/queries/subagentschedule.js';
|
||||
import resolveSelectedCompanyAccess from '../companyuser/resolveSelectedCompanyAccess.js';
|
||||
import makeRunner from '../runner.js';
|
||||
import { isValidCron } from './cron.js';
|
||||
|
||||
function shape(row) {
|
||||
return {
|
||||
id: row.id,
|
||||
company_id: row.company_id ?? row.companyId,
|
||||
desc: row.desc ?? null,
|
||||
prompt: row.prompt,
|
||||
skills: row.skills ?? null,
|
||||
lastagentnote: row.lastagentnote ?? null,
|
||||
max_itr: row.max_itr,
|
||||
cron: row.cron,
|
||||
created_at: row.created_at,
|
||||
lastrun_at: row.lastrun_at ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function optionalString(value) {
|
||||
if (value == null || value === '') return null;
|
||||
const s = String(value).trim();
|
||||
return s || null;
|
||||
}
|
||||
|
||||
export default function update(provider) {
|
||||
const access = resolveSelectedCompanyAccess(provider);
|
||||
const isSqlite = provider?.dialect === 'sqlite3';
|
||||
const getSql = isSqlite ? SQLITE_GET_BY_ID : MSSQL_GET_BY_ID;
|
||||
const updateSql = isSqlite ? SQLITE_UPDATE : MSSQL_UPDATE;
|
||||
|
||||
/**
|
||||
* Updates an existing subagent schedule for the user's selected company.
|
||||
*
|
||||
* @param {number} googleAuthId - The id of the Google auth user.
|
||||
* @param {number} id - The schedule ID to update.
|
||||
* @param {object} input - Update payload.
|
||||
* @param {string} [input.prompt] - Optional updated task prompt.
|
||||
* @param {string} [input.cron] - Optional updated cron expression.
|
||||
* @param {string|null} [input.desc] - Optional updated description.
|
||||
* @param {string|Array<string>|null} [input.skills] - Optional updated skills.
|
||||
* @param {string|null} [input.lastagentnote] - Optional updated agent note.
|
||||
* @param {number} [input.max_itr] - Optional updated max iterations.
|
||||
* @param {string|null} [input.lastrun_at] - Optional updated last run timestamp.
|
||||
* @param {object} [tx] - Optional transaction to run within.
|
||||
* @returns {Promise<{id: number, company_id: number, desc: string|null, prompt: string, skills: string|null, lastagentnote: string|null, max_itr: number, cron: string, created_at: string, lastrun_at: string|null}>}
|
||||
* @throws {Error} If schedule not found, prompt is empty, cron is invalid, or max_itr is invalid.
|
||||
*/
|
||||
return async (googleAuthId, id, input = {}, tx) => {
|
||||
const numericId = Number(id);
|
||||
if (!Number.isInteger(numericId) || numericId <= 0) {
|
||||
throw new Error('Subagent schedule not found');
|
||||
}
|
||||
|
||||
const runner = makeRunner(provider, tx);
|
||||
const { companyId } = await access(googleAuthId, runner);
|
||||
|
||||
const existing = await runner.query(getSql, { id: numericId, companyId });
|
||||
const current = existing.rows[0];
|
||||
if (!current) {
|
||||
throw new Error('Subagent schedule not found');
|
||||
}
|
||||
|
||||
let prompt = current.prompt;
|
||||
if (input.prompt !== undefined) {
|
||||
const cleaned = typeof input.prompt === 'string' ? input.prompt.trim() : '';
|
||||
if (!cleaned) {
|
||||
throw new Error('Prompt required');
|
||||
}
|
||||
prompt = cleaned;
|
||||
}
|
||||
|
||||
let cron = current.cron;
|
||||
if (input.cron !== undefined) {
|
||||
const cleaned = typeof input.cron === 'string' ? input.cron.trim() : '';
|
||||
if (!cleaned) {
|
||||
throw new Error('Cron expression required');
|
||||
}
|
||||
if (!isValidCron(cleaned)) {
|
||||
throw new Error('Invalid cron expression');
|
||||
}
|
||||
cron = cleaned;
|
||||
}
|
||||
|
||||
let maxItr = current.max_itr;
|
||||
if (input.max_itr !== undefined && input.max_itr !== null) {
|
||||
const parsed = Number(input.max_itr);
|
||||
if (!Number.isInteger(parsed) || parsed <= 0) {
|
||||
throw new Error('Invalid max iterations: must be a positive integer');
|
||||
}
|
||||
maxItr = parsed;
|
||||
}
|
||||
|
||||
let skills = current.skills;
|
||||
if (input.skills !== undefined) {
|
||||
if (Array.isArray(input.skills)) {
|
||||
skills = JSON.stringify(input.skills);
|
||||
} else {
|
||||
skills = optionalString(input.skills);
|
||||
}
|
||||
}
|
||||
|
||||
const desc = input.desc !== undefined ? optionalString(input.desc) : (current.desc ?? null);
|
||||
const lastagentnote = input.lastagentnote !== undefined ? optionalString(input.lastagentnote) : (current.lastagentnote ?? null);
|
||||
const lastrunAt = input.lastrun_at !== undefined ? optionalString(input.lastrun_at) : (current.lastrun_at ?? null);
|
||||
|
||||
const result = await runner.query(updateSql, {
|
||||
id: numericId,
|
||||
companyId,
|
||||
desc,
|
||||
prompt,
|
||||
skills,
|
||||
lastagentnote,
|
||||
maxItr,
|
||||
cron,
|
||||
lastrunAt,
|
||||
});
|
||||
|
||||
const row = result.rows[0] ?? {
|
||||
id: numericId,
|
||||
company_id: companyId,
|
||||
desc,
|
||||
prompt,
|
||||
skills,
|
||||
lastagentnote,
|
||||
max_itr: maxItr,
|
||||
cron,
|
||||
created_at: current.created_at,
|
||||
lastrun_at: lastrunAt,
|
||||
};
|
||||
|
||||
return shape(row);
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user