@jini-ai/server 0.0.0-stage → 0.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/CHANGELOG.md +118 -0
- package/LICENSE +201 -0
- package/README.md +56 -2
- package/dist/builtin-features.d.ts +138 -0
- package/dist/builtin-features.js +631 -0
- package/dist/compose-jini-kernel.d.ts +162 -0
- package/dist/compose-jini-kernel.js +208 -0
- package/dist/create-local-node-daemon.d.ts +209 -0
- package/dist/create-local-node-daemon.js +282 -0
- package/dist/feature-activation.d.ts +86 -0
- package/dist/feature-activation.js +112 -0
- package/dist/feature.d.ts +153 -0
- package/dist/feature.js +94 -0
- package/dist/frontend-control.d.ts +76 -0
- package/dist/frontend-control.js +77 -0
- package/dist/host-bootstrap.d.ts +103 -0
- package/dist/host-bootstrap.js +146 -0
- package/dist/index.d.ts +156 -0
- package/dist/index.js +11 -0
- package/dist/kernel-base.d.ts +83 -0
- package/dist/kernel-base.js +125 -0
- package/dist/lifecycle/boot-lifecycle.d.ts +61 -0
- package/dist/lifecycle/boot-lifecycle.js +74 -0
- package/dist/lifecycle/index.d.ts +3 -0
- package/dist/lifecycle/index.js +3 -0
- package/dist/lifecycle/readiness-state.d.ts +6 -0
- package/dist/lifecycle/readiness-state.js +15 -0
- package/dist/storage/backend-config.d.ts +48 -0
- package/dist/storage/backend-config.js +61 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.js +3 -0
- package/dist/storage/legacy/sqlite.d.ts +26 -0
- package/dist/storage/legacy/sqlite.js +63 -0
- package/dist/store/projects/sqlite.d.ts +161 -0
- package/dist/store/projects/sqlite.js +328 -0
- package/package.json +86 -3
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/** @module db/projects
|
|
2
|
+
* CRUD, run-status queries, and awaiting-input detection for the `projects` SQLite table — a
|
|
3
|
+
* generic workspace/container row-store, not an OD-specific concept. Also queries `messages` and
|
|
4
|
+
* `conversations` for run-status and input-gate state. Imports only the core/ kernel (SqliteDb,
|
|
5
|
+
* DbRow); reaches no sibling concern.
|
|
6
|
+
*
|
|
7
|
+
* NOTE (neutral port): drops three OD-product columns with no generic engine equivalent —
|
|
8
|
+
* `skill_id` (OD's agent-skill marketplace selection), `design_system_id` (OD's Design System
|
|
9
|
+
* feature), and `applied_plugin_snapshot_id` (OD's plugin host). The already-ported
|
|
10
|
+
* `schema/migrate.ts` projects table was pre-trimmed to match; see that file and `source-map.md`'s
|
|
11
|
+
* Design Decisions for the same drop list. The kernel-noun exclusion in `extraction-plan.md` §2.1
|
|
12
|
+
* ("projects... conversations" are not kernel nouns) does not block this module for the same reason
|
|
13
|
+
* it didn't block `conversations`: `concern-owned storage` is a generic *optional* storage adapter any
|
|
14
|
+
* consumer may use, not the kernel itself (`@jini-ai/core`/`@jini-ai/daemon` know nothing about this
|
|
15
|
+
* table) — so "project" here is a de-branded generic workspace/container concept, not OD's model.
|
|
16
|
+
*/
|
|
17
|
+
import type { SqliteDb } from '@jini-ai/db/sqlite';
|
|
18
|
+
import type { DbRow } from '@jini-ai/db/core';
|
|
19
|
+
/**
|
|
20
|
+
* Returns all projects ordered by most-recently updated first.
|
|
21
|
+
* @param db - Open SQLite connection.
|
|
22
|
+
*/
|
|
23
|
+
export declare function listProjects({ db }: {
|
|
24
|
+
db: SqliteDb;
|
|
25
|
+
}): {
|
|
26
|
+
id: any;
|
|
27
|
+
name: any;
|
|
28
|
+
pendingPrompt: any;
|
|
29
|
+
metadata: any;
|
|
30
|
+
customInstructions: any;
|
|
31
|
+
createdAt: number;
|
|
32
|
+
updatedAt: number;
|
|
33
|
+
}[];
|
|
34
|
+
/**
|
|
35
|
+
* Returns the most recent run status for each project, keyed by projectId.
|
|
36
|
+
* Scans messages with a run_status column and collapses to one entry per project,
|
|
37
|
+
* so callers can render project-level status badges without iterating conversations.
|
|
38
|
+
* @param db - Open SQLite connection.
|
|
39
|
+
*/
|
|
40
|
+
export declare function listLatestProjectRunStatuses({ db }: {
|
|
41
|
+
db: SqliteDb;
|
|
42
|
+
}): Map<string, DbRow>;
|
|
43
|
+
/**
|
|
44
|
+
* Returns the most recent run status for each conversation, keyed by conversationId.
|
|
45
|
+
* Used to display per-conversation activity state in a project sidebar.
|
|
46
|
+
* @param db - Open SQLite connection.
|
|
47
|
+
*/
|
|
48
|
+
export declare function listLatestConversationRunStatuses({ db }: {
|
|
49
|
+
db: SqliteDb;
|
|
50
|
+
}): Map<string, DbRow>;
|
|
51
|
+
/**
|
|
52
|
+
* Returns the earliest run status for each conversation, keyed by conversationId.
|
|
53
|
+
* Useful for determining when a conversation's first agent turn began.
|
|
54
|
+
* @param db - Open SQLite connection.
|
|
55
|
+
*/
|
|
56
|
+
export declare function listFirstConversationRunStatuses({ db }: {
|
|
57
|
+
db: SqliteDb;
|
|
58
|
+
}): Map<string, DbRow>;
|
|
59
|
+
/**
|
|
60
|
+
* Returns the most recent run status for each individual run, keyed by runId.
|
|
61
|
+
* Powers per-run status indicators in a conversation thread.
|
|
62
|
+
* @param db - Open SQLite connection.
|
|
63
|
+
*/
|
|
64
|
+
export declare function listLatestRunStatuses({ db }: {
|
|
65
|
+
db: SqliteDb;
|
|
66
|
+
}): Map<string, DbRow>;
|
|
67
|
+
/**
|
|
68
|
+
* Returns the set of project IDs whose latest form-bearing assistant message contains a
|
|
69
|
+
* `<question-form>` or `<ask-question>` artifact and has not yet received a user reply.
|
|
70
|
+
* Form filtering deliberately precedes ranking: a newer plain assistant turn does not consume
|
|
71
|
+
* an outstanding question. Keep timestamp/position ordering for replies sharing a timestamp.
|
|
72
|
+
* This is the data source for an "awaiting input" badge on a project list.
|
|
73
|
+
* @param db - Open SQLite connection.
|
|
74
|
+
*/
|
|
75
|
+
export declare function listProjectsAwaitingInput({ db }: {
|
|
76
|
+
db: SqliteDb;
|
|
77
|
+
}): Set<any>;
|
|
78
|
+
/**
|
|
79
|
+
* Returns the set of conversation IDs whose latest form-bearing assistant message contains a
|
|
80
|
+
* `<question-form>` or `<ask-question>` artifact and has not yet received a user reply.
|
|
81
|
+
* As in the project query, a newer plain assistant turn leaves an unanswered form outstanding.
|
|
82
|
+
* @param db - Open SQLite connection.
|
|
83
|
+
*/
|
|
84
|
+
export declare function listConversationsAwaitingInput({ db }: {
|
|
85
|
+
db: SqliteDb;
|
|
86
|
+
}): Set<any>;
|
|
87
|
+
/**
|
|
88
|
+
* Fetches a single project by id, or returns null if it does not exist.
|
|
89
|
+
* @param db - Open SQLite connection.
|
|
90
|
+
* @param id - Project id.
|
|
91
|
+
*/
|
|
92
|
+
export declare function getProject({ db, id }: {
|
|
93
|
+
db: SqliteDb;
|
|
94
|
+
id: string;
|
|
95
|
+
}): {
|
|
96
|
+
id: any;
|
|
97
|
+
name: any;
|
|
98
|
+
pendingPrompt: any;
|
|
99
|
+
metadata: any;
|
|
100
|
+
customInstructions: any;
|
|
101
|
+
createdAt: number;
|
|
102
|
+
updatedAt: number;
|
|
103
|
+
} | null;
|
|
104
|
+
/**
|
|
105
|
+
* Inserts a new project row and returns the freshly read, normalized record.
|
|
106
|
+
* Metadata is serialized to JSON; all nullable fields default to null on omission.
|
|
107
|
+
* @param db - Open SQLite connection.
|
|
108
|
+
* @param project - Project fields to insert.
|
|
109
|
+
*/
|
|
110
|
+
export declare function insertProject({ db, project: p }: {
|
|
111
|
+
db: SqliteDb;
|
|
112
|
+
project: DbRow;
|
|
113
|
+
}): {
|
|
114
|
+
id: any;
|
|
115
|
+
name: any;
|
|
116
|
+
pendingPrompt: any;
|
|
117
|
+
metadata: any;
|
|
118
|
+
customInstructions: any;
|
|
119
|
+
createdAt: number;
|
|
120
|
+
updatedAt: number;
|
|
121
|
+
} | null;
|
|
122
|
+
/**
|
|
123
|
+
* Applies a partial patch to an existing project, preserving all unpatched fields.
|
|
124
|
+
* Returns null when the project does not exist; otherwise returns the updated record.
|
|
125
|
+
* updatedAt defaults to Date.now() when the patch omits it.
|
|
126
|
+
* @param db - Open SQLite connection.
|
|
127
|
+
* @param id - Project id to update.
|
|
128
|
+
* @param patch - Fields to overwrite.
|
|
129
|
+
* @param optional.clock - Host clock port; defaults to Date.now() as before. An explicit updatedAt
|
|
130
|
+
* wins without reading the clock, so replayed/imported timestamps remain stable.
|
|
131
|
+
*/
|
|
132
|
+
export declare function updateProject({ db, id, patch }: {
|
|
133
|
+
db: SqliteDb;
|
|
134
|
+
id: string;
|
|
135
|
+
patch: DbRow;
|
|
136
|
+
}, optional?: {
|
|
137
|
+
clock?: {
|
|
138
|
+
now(): number;
|
|
139
|
+
};
|
|
140
|
+
}): {
|
|
141
|
+
id: any;
|
|
142
|
+
name: any;
|
|
143
|
+
pendingPrompt: any;
|
|
144
|
+
metadata: any;
|
|
145
|
+
customInstructions: any;
|
|
146
|
+
createdAt: number;
|
|
147
|
+
updatedAt: number;
|
|
148
|
+
} | null;
|
|
149
|
+
/**
|
|
150
|
+
* Permanently removes a project row. Cascading deletes for related rows are
|
|
151
|
+
* handled at the SQLite schema level, not here.
|
|
152
|
+
* @param db - Open SQLite connection.
|
|
153
|
+
* @param id - Project id to delete.
|
|
154
|
+
*/
|
|
155
|
+
export declare function deleteProject({ db, id }: {
|
|
156
|
+
db: SqliteDb;
|
|
157
|
+
id: string;
|
|
158
|
+
}): void;
|
|
159
|
+
/** Unchanged local host-project schema; no automatic migration at import. */
|
|
160
|
+
export declare const LEGACY_PROJECTS_DDL = " CREATE TABLE IF NOT EXISTS projects (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n pending_prompt TEXT,\n metadata_json TEXT,\n custom_instructions TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n\n";
|
|
161
|
+
//# sourceMappingURL=sqlite.d.ts.map
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/** @internal Column alias list used in every SELECT against the projects table. */
|
|
2
|
+
const PROJECT_COLS = `id, name,
|
|
3
|
+
pending_prompt AS pendingPrompt,
|
|
4
|
+
metadata_json AS metadataJson,
|
|
5
|
+
custom_instructions AS customInstructions,
|
|
6
|
+
created_at AS createdAt,
|
|
7
|
+
updated_at AS updatedAt`;
|
|
8
|
+
/**
|
|
9
|
+
* Returns all projects ordered by most-recently updated first.
|
|
10
|
+
* @param db - Open SQLite connection.
|
|
11
|
+
*/
|
|
12
|
+
export function listProjects({ db }) {
|
|
13
|
+
const rows = db
|
|
14
|
+
.prepare(`SELECT ${PROJECT_COLS}
|
|
15
|
+
FROM projects
|
|
16
|
+
ORDER BY updated_at DESC`)
|
|
17
|
+
.all();
|
|
18
|
+
return rows.map(normalizeProject);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Returns the most recent run status for each project, keyed by projectId.
|
|
22
|
+
* Scans messages with a run_status column and collapses to one entry per project,
|
|
23
|
+
* so callers can render project-level status badges without iterating conversations.
|
|
24
|
+
* @param db - Open SQLite connection.
|
|
25
|
+
*/
|
|
26
|
+
export function listLatestProjectRunStatuses({ db }) {
|
|
27
|
+
const rows = db
|
|
28
|
+
.prepare(`SELECT c.project_id AS projectId,
|
|
29
|
+
m.run_id AS runId,
|
|
30
|
+
m.run_status AS status,
|
|
31
|
+
COALESCE(m.ended_at, m.started_at, m.created_at) AS updatedAt
|
|
32
|
+
FROM messages m
|
|
33
|
+
JOIN conversations c ON c.id = m.conversation_id
|
|
34
|
+
WHERE m.run_status IS NOT NULL
|
|
35
|
+
ORDER BY updatedAt DESC`)
|
|
36
|
+
.all();
|
|
37
|
+
const latestByProject = new Map();
|
|
38
|
+
for (const row of rows) {
|
|
39
|
+
if (!latestByProject.has(row.projectId)) {
|
|
40
|
+
latestByProject.set(row.projectId, {
|
|
41
|
+
value: normalizeProjectRunStatus(row.status),
|
|
42
|
+
updatedAt: Number(row.updatedAt),
|
|
43
|
+
runId: row.runId ?? undefined,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return latestByProject;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Returns the most recent run status for each conversation, keyed by conversationId.
|
|
51
|
+
* Used to display per-conversation activity state in a project sidebar.
|
|
52
|
+
* @param db - Open SQLite connection.
|
|
53
|
+
*/
|
|
54
|
+
export function listLatestConversationRunStatuses({ db }) {
|
|
55
|
+
const rows = db
|
|
56
|
+
.prepare(`SELECT m.conversation_id AS conversationId,
|
|
57
|
+
m.run_id AS runId,
|
|
58
|
+
m.run_status AS status,
|
|
59
|
+
COALESCE(m.ended_at, m.started_at, m.created_at) AS updatedAt,
|
|
60
|
+
m.position AS position
|
|
61
|
+
FROM messages m
|
|
62
|
+
WHERE m.run_status IS NOT NULL
|
|
63
|
+
ORDER BY updatedAt DESC, m.position DESC`)
|
|
64
|
+
.all();
|
|
65
|
+
const latestByConversation = new Map();
|
|
66
|
+
for (const row of rows) {
|
|
67
|
+
if (!latestByConversation.has(row.conversationId)) {
|
|
68
|
+
latestByConversation.set(row.conversationId, {
|
|
69
|
+
value: normalizeProjectRunStatus(row.status),
|
|
70
|
+
updatedAt: Number(row.updatedAt),
|
|
71
|
+
runId: row.runId ?? undefined,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return latestByConversation;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Returns the earliest run status for each conversation, keyed by conversationId.
|
|
79
|
+
* Useful for determining when a conversation's first agent turn began.
|
|
80
|
+
* @param db - Open SQLite connection.
|
|
81
|
+
*/
|
|
82
|
+
export function listFirstConversationRunStatuses({ db }) {
|
|
83
|
+
const rows = db
|
|
84
|
+
.prepare(`SELECT m.conversation_id AS conversationId,
|
|
85
|
+
m.run_id AS runId,
|
|
86
|
+
m.run_status AS status,
|
|
87
|
+
COALESCE(m.ended_at, m.started_at, m.created_at) AS updatedAt,
|
|
88
|
+
m.position AS position
|
|
89
|
+
FROM messages m
|
|
90
|
+
WHERE m.run_status IS NOT NULL
|
|
91
|
+
AND m.run_id IS NOT NULL
|
|
92
|
+
ORDER BY m.position ASC`)
|
|
93
|
+
.all();
|
|
94
|
+
const firstByConversation = new Map();
|
|
95
|
+
for (const row of rows) {
|
|
96
|
+
if (!firstByConversation.has(row.conversationId)) {
|
|
97
|
+
firstByConversation.set(row.conversationId, {
|
|
98
|
+
value: normalizeProjectRunStatus(row.status),
|
|
99
|
+
updatedAt: Number(row.updatedAt),
|
|
100
|
+
runId: row.runId ?? undefined,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return firstByConversation;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Returns the most recent run status for each individual run, keyed by runId.
|
|
108
|
+
* Powers per-run status indicators in a conversation thread.
|
|
109
|
+
* @param db - Open SQLite connection.
|
|
110
|
+
*/
|
|
111
|
+
export function listLatestRunStatuses({ db }) {
|
|
112
|
+
const rows = db
|
|
113
|
+
.prepare(`SELECT m.run_id AS runId,
|
|
114
|
+
m.run_status AS status,
|
|
115
|
+
COALESCE(m.ended_at, m.started_at, m.created_at) AS updatedAt,
|
|
116
|
+
m.position AS position
|
|
117
|
+
FROM messages m
|
|
118
|
+
WHERE m.run_status IS NOT NULL
|
|
119
|
+
AND m.run_id IS NOT NULL
|
|
120
|
+
ORDER BY updatedAt DESC, m.position DESC`)
|
|
121
|
+
.all();
|
|
122
|
+
const latestByRun = new Map();
|
|
123
|
+
for (const row of rows) {
|
|
124
|
+
if (!latestByRun.has(row.runId)) {
|
|
125
|
+
latestByRun.set(row.runId, {
|
|
126
|
+
value: normalizeProjectRunStatus(row.status),
|
|
127
|
+
updatedAt: Number(row.updatedAt),
|
|
128
|
+
runId: row.runId ?? undefined,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return latestByRun;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Returns the set of project IDs whose latest form-bearing assistant message contains a
|
|
136
|
+
* `<question-form>` or `<ask-question>` artifact and has not yet received a user reply.
|
|
137
|
+
* Form filtering deliberately precedes ranking: a newer plain assistant turn does not consume
|
|
138
|
+
* an outstanding question. Keep timestamp/position ordering for replies sharing a timestamp.
|
|
139
|
+
* This is the data source for an "awaiting input" badge on a project list.
|
|
140
|
+
* @param db - Open SQLite connection.
|
|
141
|
+
*/
|
|
142
|
+
export function listProjectsAwaitingInput({ db }) {
|
|
143
|
+
const rows = db
|
|
144
|
+
.prepare(`SELECT latest.projectId
|
|
145
|
+
FROM (
|
|
146
|
+
SELECT c.project_id AS projectId,
|
|
147
|
+
m.conversation_id AS conversationId,
|
|
148
|
+
m.created_at AS createdAt,
|
|
149
|
+
m.position AS position,
|
|
150
|
+
ROW_NUMBER() OVER (
|
|
151
|
+
PARTITION BY c.project_id
|
|
152
|
+
ORDER BY m.created_at DESC, m.position DESC
|
|
153
|
+
) AS rowNum
|
|
154
|
+
FROM messages m
|
|
155
|
+
JOIN conversations c ON c.id = m.conversation_id
|
|
156
|
+
WHERE m.role = 'assistant'
|
|
157
|
+
-- ask-question is an accepted alias for question-form (host UI parser
|
|
158
|
+
-- + agent open-tag matcher), so an alias-form turn must also count as
|
|
159
|
+
-- awaiting input.
|
|
160
|
+
AND (
|
|
161
|
+
LOWER(m.content) LIKE '%<question-form%'
|
|
162
|
+
OR LOWER(m.content) LIKE '%<ask-question%'
|
|
163
|
+
)
|
|
164
|
+
) latest
|
|
165
|
+
WHERE latest.rowNum = 1
|
|
166
|
+
AND NOT EXISTS (
|
|
167
|
+
SELECT 1
|
|
168
|
+
FROM messages reply
|
|
169
|
+
WHERE reply.conversation_id = latest.conversationId
|
|
170
|
+
AND reply.role = 'user'
|
|
171
|
+
AND (
|
|
172
|
+
reply.created_at > latest.createdAt
|
|
173
|
+
OR (reply.created_at = latest.createdAt AND reply.position > latest.position)
|
|
174
|
+
)
|
|
175
|
+
)`)
|
|
176
|
+
.all();
|
|
177
|
+
return new Set(rows.map((row) => row.projectId));
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Returns the set of conversation IDs whose latest form-bearing assistant message contains a
|
|
181
|
+
* `<question-form>` or `<ask-question>` artifact and has not yet received a user reply.
|
|
182
|
+
* As in the project query, a newer plain assistant turn leaves an unanswered form outstanding.
|
|
183
|
+
* @param db - Open SQLite connection.
|
|
184
|
+
*/
|
|
185
|
+
export function listConversationsAwaitingInput({ db }) {
|
|
186
|
+
const rows = db
|
|
187
|
+
.prepare(`SELECT latest.conversationId
|
|
188
|
+
FROM (
|
|
189
|
+
SELECT m.conversation_id AS conversationId,
|
|
190
|
+
m.created_at AS createdAt,
|
|
191
|
+
m.position AS position,
|
|
192
|
+
ROW_NUMBER() OVER (
|
|
193
|
+
PARTITION BY m.conversation_id
|
|
194
|
+
ORDER BY m.created_at DESC, m.position DESC
|
|
195
|
+
) AS rowNum
|
|
196
|
+
FROM messages m
|
|
197
|
+
WHERE m.role = 'assistant'
|
|
198
|
+
AND (
|
|
199
|
+
LOWER(m.content) LIKE '%<question-form%'
|
|
200
|
+
OR LOWER(m.content) LIKE '%<ask-question%'
|
|
201
|
+
)
|
|
202
|
+
) latest
|
|
203
|
+
WHERE latest.rowNum = 1
|
|
204
|
+
AND NOT EXISTS (
|
|
205
|
+
SELECT 1
|
|
206
|
+
FROM messages reply
|
|
207
|
+
WHERE reply.conversation_id = latest.conversationId
|
|
208
|
+
AND reply.role = 'user'
|
|
209
|
+
AND (
|
|
210
|
+
reply.created_at > latest.createdAt
|
|
211
|
+
OR (reply.created_at = latest.createdAt AND reply.position > latest.position)
|
|
212
|
+
)
|
|
213
|
+
)`)
|
|
214
|
+
.all();
|
|
215
|
+
return new Set(rows.map((row) => row.conversationId));
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Fetches a single project by id, or returns null if it does not exist.
|
|
219
|
+
* @param db - Open SQLite connection.
|
|
220
|
+
* @param id - Project id.
|
|
221
|
+
*/
|
|
222
|
+
export function getProject({ db, id }) {
|
|
223
|
+
const row = db
|
|
224
|
+
.prepare(`SELECT ${PROJECT_COLS} FROM projects WHERE id = ?`)
|
|
225
|
+
.get(id);
|
|
226
|
+
return row ? normalizeProject(row) : null;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Inserts a new project row and returns the freshly read, normalized record.
|
|
230
|
+
* Metadata is serialized to JSON; all nullable fields default to null on omission.
|
|
231
|
+
* @param db - Open SQLite connection.
|
|
232
|
+
* @param project - Project fields to insert.
|
|
233
|
+
*/
|
|
234
|
+
export function insertProject({ db, project: p }) {
|
|
235
|
+
db.prepare(`INSERT INTO projects
|
|
236
|
+
(id, name, pending_prompt, metadata_json, custom_instructions, created_at, updated_at)
|
|
237
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(p.id, p.name, p.pendingPrompt ?? null, p.metadata ? JSON.stringify(p.metadata) : null, p.customInstructions ?? null, p.createdAt, p.updatedAt);
|
|
238
|
+
return getProject({ db, id: p.id });
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Applies a partial patch to an existing project, preserving all unpatched fields.
|
|
242
|
+
* Returns null when the project does not exist; otherwise returns the updated record.
|
|
243
|
+
* updatedAt defaults to Date.now() when the patch omits it.
|
|
244
|
+
* @param db - Open SQLite connection.
|
|
245
|
+
* @param id - Project id to update.
|
|
246
|
+
* @param patch - Fields to overwrite.
|
|
247
|
+
* @param optional.clock - Host clock port; defaults to Date.now() as before. An explicit updatedAt
|
|
248
|
+
* wins without reading the clock, so replayed/imported timestamps remain stable.
|
|
249
|
+
*/
|
|
250
|
+
export function updateProject({ db, id, patch }, optional = {}) {
|
|
251
|
+
const existing = getProject({ db, id });
|
|
252
|
+
if (!existing)
|
|
253
|
+
return null;
|
|
254
|
+
const merged = {
|
|
255
|
+
...existing,
|
|
256
|
+
...patch,
|
|
257
|
+
updatedAt: typeof patch.updatedAt === 'number' ? patch.updatedAt : (optional.clock ?? Date).now(),
|
|
258
|
+
};
|
|
259
|
+
db.prepare(`UPDATE projects
|
|
260
|
+
SET name = ?,
|
|
261
|
+
pending_prompt = ?,
|
|
262
|
+
metadata_json = ?,
|
|
263
|
+
custom_instructions = ?,
|
|
264
|
+
updated_at = ?
|
|
265
|
+
WHERE id = ?`).run(merged.name, merged.pendingPrompt ?? null, merged.metadata ? JSON.stringify(merged.metadata) : null, merged.customInstructions ?? null, merged.updatedAt, id);
|
|
266
|
+
return getProject({ db, id });
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Permanently removes a project row. Cascading deletes for related rows are
|
|
270
|
+
* handled at the SQLite schema level, not here.
|
|
271
|
+
* @param db - Open SQLite connection.
|
|
272
|
+
* @param id - Project id to delete.
|
|
273
|
+
*/
|
|
274
|
+
export function deleteProject({ db, id }) {
|
|
275
|
+
db.prepare(`DELETE FROM projects WHERE id = ?`).run(id);
|
|
276
|
+
}
|
|
277
|
+
/** @internal Converts a raw SQLite row into the typed project shape, parsing
|
|
278
|
+
* metadataJson and casting numeric timestamps. */
|
|
279
|
+
function normalizeProject(row) {
|
|
280
|
+
let metadata;
|
|
281
|
+
if (row.metadataJson) {
|
|
282
|
+
try {
|
|
283
|
+
metadata = JSON.parse(row.metadataJson);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
metadata = undefined;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
id: row.id,
|
|
291
|
+
name: row.name,
|
|
292
|
+
pendingPrompt: row.pendingPrompt ?? undefined,
|
|
293
|
+
metadata,
|
|
294
|
+
customInstructions: row.customInstructions ?? undefined,
|
|
295
|
+
createdAt: Number(row.createdAt),
|
|
296
|
+
updatedAt: Number(row.updatedAt),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/** @internal Maps raw DB run_status strings to the canonical client-facing enum,
|
|
300
|
+
* collapsing 'starting' → 'running' and 'cancelled' → 'canceled', and defaulting
|
|
301
|
+
* unknown values to 'not_started'. */
|
|
302
|
+
function normalizeProjectRunStatus(status) {
|
|
303
|
+
if (status === 'starting')
|
|
304
|
+
return 'running';
|
|
305
|
+
if (status === 'cancelled')
|
|
306
|
+
return 'canceled';
|
|
307
|
+
if (status === 'queued' ||
|
|
308
|
+
status === 'running' ||
|
|
309
|
+
status === 'succeeded' ||
|
|
310
|
+
status === 'failed' ||
|
|
311
|
+
status === 'canceled') {
|
|
312
|
+
return status;
|
|
313
|
+
}
|
|
314
|
+
return 'not_started';
|
|
315
|
+
}
|
|
316
|
+
/** Unchanged local host-project schema; no automatic migration at import. */
|
|
317
|
+
export const LEGACY_PROJECTS_DDL = ` CREATE TABLE IF NOT EXISTS projects (
|
|
318
|
+
id TEXT PRIMARY KEY,
|
|
319
|
+
name TEXT NOT NULL,
|
|
320
|
+
pending_prompt TEXT,
|
|
321
|
+
metadata_json TEXT,
|
|
322
|
+
custom_instructions TEXT,
|
|
323
|
+
created_at INTEGER NOT NULL,
|
|
324
|
+
updated_at INTEGER NOT NULL
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
`;
|
|
328
|
+
//# sourceMappingURL=sqlite.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,89 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jini-ai/server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/AINSEP/Jini.git",
|
|
8
|
+
"directory": "packages/node-host"
|
|
9
|
+
},
|
|
10
|
+
"jini": {
|
|
11
|
+
"domain": "server",
|
|
12
|
+
"kind": "host",
|
|
13
|
+
"runtime": "node",
|
|
14
|
+
"admission": "locked"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./storage": {
|
|
26
|
+
"types": "./dist/storage/index.d.ts",
|
|
27
|
+
"import": "./dist/storage/index.js",
|
|
28
|
+
"default": "./dist/storage/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./storage/legacy/sqlite": {
|
|
31
|
+
"types": "./dist/storage/legacy/sqlite.d.ts",
|
|
32
|
+
"import": "./dist/storage/legacy/sqlite.js",
|
|
33
|
+
"default": "./dist/storage/legacy/sqlite.js"
|
|
34
|
+
},
|
|
35
|
+
"./store/projects/sqlite": {
|
|
36
|
+
"types": "./dist/store/projects/sqlite.d.ts",
|
|
37
|
+
"import": "./dist/store/projects/sqlite.js",
|
|
38
|
+
"default": "./dist/store/projects/sqlite.js"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"CHANGELOG.md",
|
|
45
|
+
"LICENSE",
|
|
46
|
+
"!dist/**/*.test.js",
|
|
47
|
+
"!dist/**/*.test.d.ts",
|
|
48
|
+
"!dist/**/*.test.js.map",
|
|
49
|
+
"!dist/**/*.test.d.ts.map",
|
|
50
|
+
"!dist/**/__tests__/**",
|
|
51
|
+
"!dist/**/*.typecheck.*"
|
|
52
|
+
],
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public",
|
|
55
|
+
"registry": "https://registry.npmjs.org"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"express": "^4.21.0",
|
|
59
|
+
"@jini-ai/agent-runtime": "0.4.0",
|
|
60
|
+
"@jini-ai/core": "0.4.0",
|
|
61
|
+
"@jini-ai/http-kit": "0.4.0",
|
|
62
|
+
"@jini-ai/daemon": "0.5.0",
|
|
63
|
+
"@jini-ai/sidecar": "0.4.0",
|
|
64
|
+
"@jini-ai/db": "0.2.0",
|
|
65
|
+
"@jini-ai/registry": "0.5.0",
|
|
66
|
+
"@jini-ai/chat": "0.5.0"
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/better-sqlite3": "^7.6.11",
|
|
70
|
+
"@types/express": "^4.17.21",
|
|
71
|
+
"@vitest/coverage-v8": "^2.1.9",
|
|
72
|
+
"better-sqlite3": "^13.0.0"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"better-sqlite3": "^13.0.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"better-sqlite3": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"sideEffects": false,
|
|
83
|
+
"scripts": {
|
|
84
|
+
"build": "tsc -p tsconfig.json",
|
|
85
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
86
|
+
"test": "vitest run",
|
|
87
|
+
"test:coverage": "vitest run --coverage"
|
|
88
|
+
}
|
|
6
89
|
}
|