@taskforcehq/taskforce 0.3.301 → 0.3.303
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/dist/Taskforce.module.css +37 -2
- package/dist/TaskforceCore.filter-persistence.test.js +1 -1
- package/dist/TaskforceCore.js +72 -33
- package/dist/TaskforceCore.test.js +848 -130
- package/dist/components/features/TaskSettings.js +14 -82
- package/dist/components/features/TaskSettings.test.js +33 -70
- package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
- package/dist/components/task/TaskActionHeader.js +6 -3
- package/dist/components/task/TaskCard.js +6 -3
- package/dist/components/task/TaskForm.js +2 -2
- package/dist/components/task/TaskForm.test.js +41 -2
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/ui/ReferenceBadgeButton.js +1 -1
- package/dist/components/views/PlansPage.d.ts +3 -1
- package/dist/components/views/PlansPage.js +109 -49
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +100 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +473 -256
- package/dist/components/views/panels/PlanningDrawer.js +18 -1
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +48 -0
- package/dist/components/views/standalone/modals/AccountHubModal.js +36 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
- package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
- package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/HelpModal.js +8 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
- package/dist/components/views/standalone/modals/types.d.ts +2 -0
- package/dist/components/views/standalone/modals/types.js +1 -0
- package/dist/components/views/teamManagementAccess.d.ts +1 -1
- package/dist/components/views/teamManagementAccess.js +2 -2
- package/dist/components/views/teamManagementAccess.test.js +3 -3
- package/dist/config/deployment.js +2 -2
- package/dist/config/deployment.test.js +5 -3
- package/dist/core/AiProfileService.js +7 -2
- package/dist/core/AiProfiles.test.js +19 -0
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +29 -5
- package/dist/core/PlanEntitlementService.js +297 -219
- package/dist/core/SignupMonetizationSettings.test.js +173 -49
- package/dist/core/SyncReconciliationService.d.ts +2 -0
- package/dist/core/SyncReconciliationService.js +3 -0
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskAuditTimeline.test.js +40 -0
- package/dist/core/Taskforce.d.ts +100 -10
- package/dist/core/Taskforce.ids.test.js +133 -0
- package/dist/core/Taskforce.js +459 -127
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +111 -0
- package/dist/core/taskReferencePolicy.d.ts +28 -0
- package/dist/core/taskReferencePolicy.js +189 -0
- package/dist/core/types.d.ts +13 -12
- package/dist/hooks/tasks/useTaskFormActions.js +3 -0
- package/dist/hooks/useSyncOrchestrator.js +17 -3
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +97 -5
- package/dist/hooks/useTaskData.js +3 -2
- package/dist/hooks/useTaskData.test.js +66 -0
- package/dist/hooks/useTaskforce.d.ts +8 -0
- package/dist/hooks/useTaskforce.js +72 -19
- package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
- package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
- package/dist/mcp/aiProfileBootstrap.js +80 -0
- package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
- package/dist/mcp/aiProfileBootstrap.test.js +68 -0
- package/dist/mcp/clientIntegrations.d.ts +2 -8
- package/dist/mcp/clientIntegrations.js +21 -40
- package/dist/mcp/clientIntegrations.test.js +28 -2
- package/dist/mcp/clientProfileDefaults.d.ts +12 -0
- package/dist/mcp/clientProfileDefaults.js +37 -0
- package/dist/mcp/runtime.d.ts +10 -7
- package/dist/mcp/runtime.js +131 -48
- package/dist/mcp/runtime.test.js +321 -83
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +68 -1
- package/dist/qaOpen.test.d.ts +1 -0
- package/dist/qaOpen.test.js +56 -0
- package/dist/server/index.cookieProxy.test.js +2 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +17 -1
- package/dist/server/index.test.js +15 -1
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +105 -45
- package/dist/server/routes/auth.d.ts +6 -0
- package/dist/server/routes/auth.js +505 -30
- package/dist/server/routes/billing.js +517 -24
- package/dist/server/routes/billing.test.js +967 -45
- package/dist/server/routes/documents.js +6 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/shared.js +16 -6
- package/dist/server/routes/sync.integration.test.js +290 -0
- package/dist/server/routes/sync.js +234 -1922
- package/dist/server/routes/tasks.js +18 -4
- package/dist/server/routes.js +29 -22
- package/dist/server/routes.test.js +639 -82
- package/dist/shared/runtimeContract.d.ts +1 -1
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.d.ts +17 -0
- package/dist/sync/collaborationSyncPayload.js +131 -0
- package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.test.js +104 -0
- package/dist/sync/collaborationSyncState.d.ts +15 -0
- package/dist/sync/collaborationSyncState.js +60 -0
- package/dist/sync/contentSyncPayload.d.ts +45 -0
- package/dist/sync/contentSyncPayload.js +104 -0
- package/dist/sync/contentSyncPayload.test.d.ts +1 -0
- package/dist/sync/contentSyncPayload.test.js +104 -0
- package/dist/sync/contentSyncState.d.ts +98 -0
- package/dist/sync/contentSyncState.js +1004 -0
- package/dist/sync/planningSyncPayload.d.ts +9 -0
- package/dist/sync/planningSyncPayload.js +82 -0
- package/dist/sync/planningSyncPayload.test.d.ts +1 -0
- package/dist/sync/planningSyncPayload.test.js +68 -0
- package/dist/sync/syncApplyHandlers.d.ts +83 -0
- package/dist/sync/syncApplyHandlers.js +445 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +21 -8
- package/dist/sync/taskSyncPayload.d.ts +33 -0
- package/dist/sync/taskSyncPayload.js +126 -0
- package/dist/sync/taskSyncPayload.test.d.ts +1 -0
- package/dist/sync/taskSyncPayload.test.js +54 -0
- package/dist/sync/workspacePullFeed.d.ts +38 -0
- package/dist/sync/workspacePullFeed.js +349 -0
- package/dist/sync/workspacePullFeed.test.d.ts +1 -0
- package/dist/sync/workspacePullFeed.test.js +278 -0
- package/dist/sync/workspaceSyncModel.d.ts +4 -1
- package/dist/sync/workspaceSyncModel.js +9 -2
- package/dist/sync/workspaceSyncModel.test.js +29 -0
- package/dist/test/setup.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-CpsTmrIW.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-C_TmXZwA.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-C3GyzrWm.js} +2 -2
- package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-4-Rh2K2n.js} +1 -1
- package/dist/ui/assets/PlansPage-BP7AYOqr.js +1 -0
- package/dist/ui/assets/TaskSettings-BOC5F6Ag.js +8 -0
- package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
- package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-CGk9s3NJ.js} +1 -1
- package/dist/ui/assets/index-CLIMgR6g.js +6 -0
- package/dist/ui/assets/index-DneETxcu.css +1 -0
- package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-pWocEipT.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +17 -4
- package/dist/utils/taskActivity.test.js +27 -0
- package/dist/utils/taskNormalization.js +10 -2
- package/dist/utils/taskReferences.d.ts +11 -1
- package/dist/utils/taskReferences.js +33 -1
- package/package.json +4 -3
- package/scripts/qa-open-lib.mjs +175 -0
- package/scripts/qa-open-lib.mjs.d.ts +49 -0
- package/scripts/qa-open.mjs +352 -0
- package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
- package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
- package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
- package/dist/ui/assets/index-xBWLPOEb.js +0 -6
package/dist/mcp/runtime.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import path from 'path';
|
|
@@ -7,61 +7,8 @@ import { MCP_TOOL_REGISTRY } from './toolRegistry';
|
|
|
7
7
|
import { TaskforceCore, TaskforceRuleError } from '../core/Taskforce';
|
|
8
8
|
import { WorkspaceAssetStore } from '../storage/workspaceAssetStore';
|
|
9
9
|
import { ANNOTATED_ATTACHMENTS_WORKSPACE_FEATURE_KEY, APP_GATE_DEFINITIONS, } from '../runtime/appGates';
|
|
10
|
-
function
|
|
11
|
-
|
|
12
|
-
const core = new TaskforceCore({
|
|
13
|
-
projectRoot,
|
|
14
|
-
storagePath: path.join(projectRoot, '.taskforce')
|
|
15
|
-
});
|
|
16
|
-
try {
|
|
17
|
-
Object.defineProperty(core, 'getAccountAccessStatus', {
|
|
18
|
-
configurable: true,
|
|
19
|
-
value: () => ({
|
|
20
|
-
gate: 'ok',
|
|
21
|
-
hasEntitlement: true,
|
|
22
|
-
canAccessApp: true,
|
|
23
|
-
canAccessPlans: true,
|
|
24
|
-
signupMonetizationStrategy: 'none',
|
|
25
|
-
planSelectionRequired: false,
|
|
26
|
-
message: null,
|
|
27
|
-
entitlement: null
|
|
28
|
-
})
|
|
29
|
-
});
|
|
30
|
-
core.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
|
|
31
|
-
core.ensureBootstrapUserAccess({
|
|
32
|
-
userId: 'owner-1',
|
|
33
|
-
workspaceId: 'workspace-1',
|
|
34
|
-
role: 'owner',
|
|
35
|
-
email: 'owner-1@example.com'
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
finally {
|
|
39
|
-
core.close();
|
|
40
|
-
}
|
|
41
|
-
return projectRoot;
|
|
42
|
-
}
|
|
43
|
-
async function listTools(projectRoot, runtimeMode) {
|
|
44
|
-
const runtime = createTaskforceMcpServer({
|
|
45
|
-
projectRoot,
|
|
46
|
-
runtimeMode,
|
|
47
|
-
logStartup: false,
|
|
48
|
-
auth: runtimeMode === 'cloud'
|
|
49
|
-
? { scopes: ['tasks:read', 'tasks:write', 'workspace:read'] }
|
|
50
|
-
: undefined
|
|
51
|
-
});
|
|
52
|
-
const handler = runtime.server._requestHandlers.get('tools/list');
|
|
53
|
-
if (!handler) {
|
|
54
|
-
throw new Error('tools/list handler not registered');
|
|
55
|
-
}
|
|
56
|
-
return await handler({
|
|
57
|
-
jsonrpc: '2.0',
|
|
58
|
-
id: 'test-tools-list',
|
|
59
|
-
method: 'tools/list',
|
|
60
|
-
params: {}
|
|
61
|
-
}, {});
|
|
62
|
-
}
|
|
63
|
-
async function callTool(projectRoot, runtimeMode, name, args, options) {
|
|
64
|
-
const runtime = createTaskforceMcpServer({
|
|
10
|
+
function createRuntime(projectRoot, runtimeMode, options) {
|
|
11
|
+
return createTaskforceMcpServer({
|
|
65
12
|
projectRoot,
|
|
66
13
|
runtimeMode,
|
|
67
14
|
workspaceId: options?.workspaceId,
|
|
@@ -73,6 +20,8 @@ async function callTool(projectRoot, runtimeMode, name, args, options) {
|
|
|
73
20
|
actorType: 'oauth',
|
|
74
21
|
aiProfileToken: options?.authAiProfileToken || null,
|
|
75
22
|
aiProfileName: options?.authAiProfileName || null,
|
|
23
|
+
aiProfileIcon: options?.authAiProfileIcon || null,
|
|
24
|
+
aiProfileColor: options?.authAiProfileColor || null,
|
|
76
25
|
aiProfileDescription: options?.authAiProfileDescription || null,
|
|
77
26
|
aiProfileRole: options?.authAiProfileRole || null,
|
|
78
27
|
aiProfileProvider: options?.authAiProfileProvider || null,
|
|
@@ -81,6 +30,8 @@ async function callTool(projectRoot, runtimeMode, name, args, options) {
|
|
|
81
30
|
}
|
|
82
31
|
: undefined
|
|
83
32
|
});
|
|
33
|
+
}
|
|
34
|
+
async function callToolWithRuntime(runtime, name, args, options) {
|
|
84
35
|
const handler = runtime.server._requestHandlers.get('tools/call');
|
|
85
36
|
if (!handler) {
|
|
86
37
|
throw new Error('tools/call handler not registered');
|
|
@@ -110,20 +61,71 @@ async function callTool(projectRoot, runtimeMode, name, args, options) {
|
|
|
110
61
|
}
|
|
111
62
|
}, {});
|
|
112
63
|
}
|
|
113
|
-
async function
|
|
114
|
-
const
|
|
64
|
+
async function subscribeResource(runtime, uri) {
|
|
65
|
+
const handler = runtime.server._requestHandlers.get('resources/subscribe');
|
|
66
|
+
if (!handler) {
|
|
67
|
+
throw new Error('resources/subscribe handler not registered');
|
|
68
|
+
}
|
|
69
|
+
await handler({
|
|
70
|
+
jsonrpc: '2.0',
|
|
71
|
+
id: `test-resources-subscribe-${uri}`,
|
|
72
|
+
method: 'resources/subscribe',
|
|
73
|
+
params: { uri }
|
|
74
|
+
}, {});
|
|
75
|
+
}
|
|
76
|
+
function createProjectRoot() {
|
|
77
|
+
const projectRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'taskforce-mcp-runtime-'));
|
|
78
|
+
const core = new TaskforceCore({
|
|
115
79
|
projectRoot,
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
80
|
+
storagePath: path.join(projectRoot, '.taskforce')
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
Object.defineProperty(core, 'getAccountAccessStatus', {
|
|
84
|
+
configurable: true,
|
|
85
|
+
value: () => ({
|
|
86
|
+
gate: 'ok',
|
|
87
|
+
hasEntitlement: true,
|
|
88
|
+
canAccessApp: true,
|
|
89
|
+
canAccessPlans: true,
|
|
90
|
+
planSelectionRequired: false,
|
|
91
|
+
message: null,
|
|
92
|
+
entitlement: null
|
|
93
|
+
})
|
|
94
|
+
});
|
|
95
|
+
core.createWorkspace({ workspaceId: 'workspace-1', name: 'Workspace 1' });
|
|
96
|
+
core.ensureBootstrapUserAccess({
|
|
97
|
+
userId: 'owner-1',
|
|
98
|
+
workspaceId: 'workspace-1',
|
|
99
|
+
role: 'owner',
|
|
100
|
+
email: 'owner-1@example.com'
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
core.close();
|
|
105
|
+
}
|
|
106
|
+
return projectRoot;
|
|
107
|
+
}
|
|
108
|
+
async function listTools(projectRoot, runtimeMode) {
|
|
109
|
+
const runtime = createRuntime(projectRoot, runtimeMode);
|
|
110
|
+
const handler = runtime.server._requestHandlers.get('tools/list');
|
|
111
|
+
if (!handler) {
|
|
112
|
+
throw new Error('tools/list handler not registered');
|
|
113
|
+
}
|
|
114
|
+
return await handler({
|
|
115
|
+
jsonrpc: '2.0',
|
|
116
|
+
id: 'test-tools-list',
|
|
117
|
+
method: 'tools/list',
|
|
118
|
+
params: {}
|
|
119
|
+
}, {});
|
|
120
|
+
}
|
|
121
|
+
async function callTool(projectRoot, runtimeMode, name, args, options) {
|
|
122
|
+
const runtime = createRuntime(projectRoot, runtimeMode, options);
|
|
123
|
+
return await callToolWithRuntime(runtime, name, args, {
|
|
124
|
+
resolveProfile: options?.resolveProfile,
|
|
126
125
|
});
|
|
126
|
+
}
|
|
127
|
+
async function readResource(projectRoot, runtimeMode, uri, options) {
|
|
128
|
+
const runtime = createRuntime(projectRoot, runtimeMode, options);
|
|
127
129
|
const handler = runtime.server._requestHandlers.get('resources/read');
|
|
128
130
|
if (!handler) {
|
|
129
131
|
throw new Error('resources/read handler not registered');
|
|
@@ -136,19 +138,7 @@ async function readResource(projectRoot, runtimeMode, uri, options) {
|
|
|
136
138
|
}, {});
|
|
137
139
|
}
|
|
138
140
|
async function listResources(projectRoot, runtimeMode, options) {
|
|
139
|
-
const runtime =
|
|
140
|
-
projectRoot,
|
|
141
|
-
runtimeMode,
|
|
142
|
-
workspaceId: options?.workspaceId,
|
|
143
|
-
logStartup: false,
|
|
144
|
-
auth: runtimeMode === 'cloud'
|
|
145
|
-
? {
|
|
146
|
-
scopes: ['tasks:read', 'tasks:write', 'workspace:read'],
|
|
147
|
-
userId: options?.authUserId || 'owner-1',
|
|
148
|
-
actorType: 'oauth'
|
|
149
|
-
}
|
|
150
|
-
: undefined
|
|
151
|
-
});
|
|
141
|
+
const runtime = createRuntime(projectRoot, runtimeMode, options);
|
|
152
142
|
const handler = runtime.server._requestHandlers.get('resources/list');
|
|
153
143
|
if (!handler) {
|
|
154
144
|
throw new Error('resources/list handler not registered');
|
|
@@ -302,6 +292,29 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
302
292
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
303
293
|
}
|
|
304
294
|
});
|
|
295
|
+
it('does not let TASKFORCE_TOOLS prune the runtime tool catalog', async () => {
|
|
296
|
+
const projectRoot = createProjectRoot();
|
|
297
|
+
const previousValue = process.env.TASKFORCE_TOOLS;
|
|
298
|
+
process.env.TASKFORCE_TOOLS = 'get_task';
|
|
299
|
+
try {
|
|
300
|
+
const result = await listTools(projectRoot, 'cloud');
|
|
301
|
+
const runtimeToolNames = result.tools.map((tool) => tool.name).sort();
|
|
302
|
+
const registryToolNames = Object.entries(MCP_TOOL_REGISTRY)
|
|
303
|
+
.filter(([, entry]) => entry.permission.kind !== 'local-only' && entry.permission.kind !== 'session-only')
|
|
304
|
+
.map(([name]) => name)
|
|
305
|
+
.sort();
|
|
306
|
+
expect(runtimeToolNames).toEqual(registryToolNames);
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
if (previousValue === undefined) {
|
|
310
|
+
delete process.env.TASKFORCE_TOOLS;
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
process.env.TASKFORCE_TOOLS = previousValue;
|
|
314
|
+
}
|
|
315
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
316
|
+
}
|
|
317
|
+
});
|
|
305
318
|
it('includes local-only guidance in local runtime tool descriptions', async () => {
|
|
306
319
|
const projectRoot = createProjectRoot();
|
|
307
320
|
try {
|
|
@@ -713,6 +726,8 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
713
726
|
model: 'Codex',
|
|
714
727
|
surfaceType: 'coding_tool'
|
|
715
728
|
}));
|
|
729
|
+
expect(currentProfile.bindingSource).toBe('connection_bootstrap');
|
|
730
|
+
expect(currentProfile.writeReady).toBe(true);
|
|
716
731
|
}
|
|
717
732
|
finally {
|
|
718
733
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
@@ -788,6 +803,8 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
788
803
|
authAiProfileName: 'Automation Codex'
|
|
789
804
|
}));
|
|
790
805
|
expect(currentProfile.identityBound).toBe(true);
|
|
806
|
+
expect(currentProfile.bindingSource).toBe('connection_bootstrap');
|
|
807
|
+
expect(currentProfile.writeReady).toBe(true);
|
|
791
808
|
expect(currentProfile.profileToken).toMatch(/^tfp_/);
|
|
792
809
|
expect(currentProfile.profile).toEqual(expect.objectContaining({
|
|
793
810
|
name: 'Automation Codex',
|
|
@@ -817,6 +834,27 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
817
834
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
818
835
|
}
|
|
819
836
|
});
|
|
837
|
+
it('reports token_lookup binding when get_current_profile is inspected with an explicit profileToken', async () => {
|
|
838
|
+
const projectRoot = createProjectRoot();
|
|
839
|
+
try {
|
|
840
|
+
const resolved = parseToolJson(await callTool(projectRoot, 'local', 'resolve_profile', {
|
|
841
|
+
name: 'Codex'
|
|
842
|
+
}, { resolveProfile: false }));
|
|
843
|
+
const payload = parseToolJson(await callTool(projectRoot, 'local', 'get_current_profile', {
|
|
844
|
+
profileToken: resolved.profile.profileToken,
|
|
845
|
+
}, { resolveProfile: false }));
|
|
846
|
+
expect(payload.identityBound).toBe(false);
|
|
847
|
+
expect(payload.bindingSource).toBe('token_lookup');
|
|
848
|
+
expect(payload.writeReady).toBe(true);
|
|
849
|
+
expect(payload.profile).toEqual(expect.objectContaining({
|
|
850
|
+
id: resolved.profile.id,
|
|
851
|
+
profileToken: resolved.profile.profileToken,
|
|
852
|
+
}));
|
|
853
|
+
}
|
|
854
|
+
finally {
|
|
855
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
856
|
+
}
|
|
857
|
+
});
|
|
820
858
|
it('recovers from an invalid per-call profileToken when the connection has a valid AI name', async () => {
|
|
821
859
|
const projectRoot = createProjectRoot();
|
|
822
860
|
try {
|
|
@@ -1214,6 +1252,8 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
1214
1252
|
}
|
|
1215
1253
|
}, {}));
|
|
1216
1254
|
expect(payload.identityBound).toBe(true);
|
|
1255
|
+
expect(payload.bindingSource).toBe('session');
|
|
1256
|
+
expect(payload.writeReady).toBe(true);
|
|
1217
1257
|
expect(payload.profile).toEqual(expect.objectContaining({
|
|
1218
1258
|
id: resolved.profile.id,
|
|
1219
1259
|
username: resolved.profile.username,
|
|
@@ -1235,6 +1275,8 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
1235
1275
|
resolveProfile: false
|
|
1236
1276
|
}));
|
|
1237
1277
|
expect(payload.identityBound).toBe(false);
|
|
1278
|
+
expect(payload.bindingSource).toBe('none');
|
|
1279
|
+
expect(payload.writeReady).toBe(false);
|
|
1238
1280
|
expect(payload.profile).toBeNull();
|
|
1239
1281
|
expect(payload.taskFilter).toBeNull();
|
|
1240
1282
|
expect(payload.nextTool).toBe('resolve_profile');
|
|
@@ -1251,13 +1293,29 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
1251
1293
|
const payload = parseToolJson(await callTool(projectRoot, 'cloud', 'get_current_profile', {}, {
|
|
1252
1294
|
workspaceId: 'workspace-1',
|
|
1253
1295
|
resolveProfile: false,
|
|
1254
|
-
authAiProfileName: 'Automation Codex'
|
|
1296
|
+
authAiProfileName: 'Automation Codex',
|
|
1297
|
+
authAiProfileIcon: 'Code2',
|
|
1298
|
+
authAiProfileColor: '#10a37f',
|
|
1299
|
+
authAiProfileDescription: 'OpenAI Codex coding agent connected through MCP.',
|
|
1300
|
+
authAiProfileRole: 'Coding Agent',
|
|
1301
|
+
authAiProfileProvider: 'OpenAI',
|
|
1302
|
+
authAiProfileModel: 'Codex',
|
|
1303
|
+
authAiProfileSurfaceType: 'coding_tool'
|
|
1255
1304
|
}));
|
|
1256
1305
|
expect(payload.identityBound).toBe(true);
|
|
1306
|
+
expect(payload.bindingSource).toBe('connection_bootstrap');
|
|
1307
|
+
expect(payload.writeReady).toBe(true);
|
|
1257
1308
|
expect(payload.profile).toEqual(expect.objectContaining({
|
|
1258
1309
|
name: 'Automation Codex',
|
|
1259
1310
|
username: 'automation-codex',
|
|
1260
|
-
profileToken: expect.stringMatching(/^tfp_/)
|
|
1311
|
+
profileToken: expect.stringMatching(/^tfp_/),
|
|
1312
|
+
icon: 'Code2',
|
|
1313
|
+
color: '#10a37f',
|
|
1314
|
+
description: 'OpenAI Codex coding agent connected through MCP.',
|
|
1315
|
+
role: 'Coding Agent',
|
|
1316
|
+
provider: 'OpenAI',
|
|
1317
|
+
model: 'Codex',
|
|
1318
|
+
surfaceType: 'coding_tool'
|
|
1261
1319
|
}));
|
|
1262
1320
|
expect(payload.taskFilter).toEqual({ assignee: payload.profile.id });
|
|
1263
1321
|
expect(payload.nextTool).toBe('list_tasks');
|
|
@@ -1453,6 +1511,150 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
1453
1511
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1454
1512
|
}
|
|
1455
1513
|
});
|
|
1514
|
+
it('builds workstream listings from one historical task snapshot and returns task summaries', async () => {
|
|
1515
|
+
const projectRoot = createProjectRoot();
|
|
1516
|
+
const core = new TaskforceCore({
|
|
1517
|
+
projectRoot,
|
|
1518
|
+
storagePath: path.join(projectRoot, '.taskforce')
|
|
1519
|
+
});
|
|
1520
|
+
try {
|
|
1521
|
+
const category = core.getCategories()[0]?.value;
|
|
1522
|
+
const type = core.getTypes()[0]?.value;
|
|
1523
|
+
const firstWorkstream = core.createWorkstream({ title: 'Workstream one' }, 'workspace-1');
|
|
1524
|
+
const secondWorkstream = core.createWorkstream({ title: 'Workstream two' }, 'workspace-1');
|
|
1525
|
+
core.createTask({
|
|
1526
|
+
title: 'Linked task one',
|
|
1527
|
+
category,
|
|
1528
|
+
type,
|
|
1529
|
+
workstreamId: firstWorkstream.id,
|
|
1530
|
+
}, { workspaceId: 'workspace-1', actorRef: 'owner-1' });
|
|
1531
|
+
core.createTask({
|
|
1532
|
+
title: 'Linked task two',
|
|
1533
|
+
category,
|
|
1534
|
+
type,
|
|
1535
|
+
workstreamId: secondWorkstream.id,
|
|
1536
|
+
}, { workspaceId: 'workspace-1', actorRef: 'owner-1' });
|
|
1537
|
+
const archivedTask = core.createTask({
|
|
1538
|
+
title: 'Archived linked task',
|
|
1539
|
+
category,
|
|
1540
|
+
type,
|
|
1541
|
+
workstreamId: firstWorkstream.id,
|
|
1542
|
+
status: 'done',
|
|
1543
|
+
}, { workspaceId: 'workspace-1', actorRef: 'owner-1' });
|
|
1544
|
+
core.archiveTask(archivedTask.id, false, undefined, 'workspace-1', { actorRef: 'owner-1' });
|
|
1545
|
+
}
|
|
1546
|
+
finally {
|
|
1547
|
+
core.close();
|
|
1548
|
+
}
|
|
1549
|
+
const listTasksSpy = vi.spyOn(TaskforceCore.prototype, 'listTasks');
|
|
1550
|
+
const listArchiveSpy = vi.spyOn(TaskforceCore.prototype, 'listArchive');
|
|
1551
|
+
try {
|
|
1552
|
+
const beforeListTasksCalls = listTasksSpy.mock.calls.length;
|
|
1553
|
+
const beforeListArchiveCalls = listArchiveSpy.mock.calls.length;
|
|
1554
|
+
const payload = parseToolJson(await callTool(projectRoot, 'local', 'list_workstreams', {}));
|
|
1555
|
+
const firstWorkstreamPayload = payload.workstreams.find((entry) => entry.workstream.title === 'Workstream one');
|
|
1556
|
+
const linkedTask = firstWorkstreamPayload?.tasks.find((task) => task.title === 'Archived linked task');
|
|
1557
|
+
expect(listTasksSpy.mock.calls.length - beforeListTasksCalls).toBe(1);
|
|
1558
|
+
expect(listArchiveSpy.mock.calls.length - beforeListArchiveCalls).toBe(1);
|
|
1559
|
+
expect(linkedTask).toMatchObject({
|
|
1560
|
+
title: 'Archived linked task',
|
|
1561
|
+
workstreamId: firstWorkstreamPayload?.workstream.id,
|
|
1562
|
+
status: 'done',
|
|
1563
|
+
});
|
|
1564
|
+
expect(linkedTask.description).toBeUndefined();
|
|
1565
|
+
expect(linkedTask.comments).toBeUndefined();
|
|
1566
|
+
expect(linkedTask.activity).toBeUndefined();
|
|
1567
|
+
expect(linkedTask.attachments).toBeUndefined();
|
|
1568
|
+
expect(linkedTask.checklistItems).toBeUndefined();
|
|
1569
|
+
}
|
|
1570
|
+
finally {
|
|
1571
|
+
listTasksSpy.mockRestore();
|
|
1572
|
+
listArchiveSpy.mockRestore();
|
|
1573
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1574
|
+
}
|
|
1575
|
+
});
|
|
1576
|
+
it('queues bulk resource invalidations instead of awaiting them in the write path', async () => {
|
|
1577
|
+
const projectRoot = createProjectRoot();
|
|
1578
|
+
const runtime = createRuntime(projectRoot, 'local');
|
|
1579
|
+
const resourceUri = `taskforce://${path.basename(projectRoot)}/tasks`;
|
|
1580
|
+
const sendResourceUpdatedSpy = vi
|
|
1581
|
+
.spyOn(runtime.server, 'sendResourceUpdated')
|
|
1582
|
+
.mockImplementation(async () => {
|
|
1583
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
1584
|
+
});
|
|
1585
|
+
try {
|
|
1586
|
+
await subscribeResource(runtime, resourceUri);
|
|
1587
|
+
const created = parseToolJson(await callToolWithRuntime(runtime, 'create_task', {
|
|
1588
|
+
title: 'Queued invalidation task'
|
|
1589
|
+
}));
|
|
1590
|
+
expect(created.title).toBe('Queued invalidation task');
|
|
1591
|
+
expect(sendResourceUpdatedSpy).not.toHaveBeenCalled();
|
|
1592
|
+
await new Promise((resolve) => setTimeout(resolve, 40));
|
|
1593
|
+
expect(sendResourceUpdatedSpy).toHaveBeenCalledTimes(1);
|
|
1594
|
+
expect(sendResourceUpdatedSpy).toHaveBeenCalledWith({ uri: resourceUri });
|
|
1595
|
+
}
|
|
1596
|
+
finally {
|
|
1597
|
+
sendResourceUpdatedSpy.mockRestore();
|
|
1598
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1599
|
+
}
|
|
1600
|
+
});
|
|
1601
|
+
it('reads the bulk tasks resource as summary-only rows', async () => {
|
|
1602
|
+
const projectRoot = createProjectRoot();
|
|
1603
|
+
try {
|
|
1604
|
+
const created = parseToolJson(await callTool(projectRoot, 'local', 'create_task', {
|
|
1605
|
+
title: 'Verbose task',
|
|
1606
|
+
description: 'This should stay off the bulk tasks resource'
|
|
1607
|
+
}));
|
|
1608
|
+
await callTool(projectRoot, 'local', 'add_comment', {
|
|
1609
|
+
id: created.referenceLabel,
|
|
1610
|
+
text: 'This comment should stay off the bulk tasks resource',
|
|
1611
|
+
author: 'ai-profile-019d15c5-6ad3-7bf0-9b9e-fa9c366c3ff4'
|
|
1612
|
+
});
|
|
1613
|
+
const taskResource = await readResource(projectRoot, 'cloud', `taskforce://${path.basename(projectRoot)}/tasks`, {
|
|
1614
|
+
workspaceId: 'workspace-1',
|
|
1615
|
+
authUserId: 'owner-1'
|
|
1616
|
+
});
|
|
1617
|
+
const payload = JSON.parse(String(taskResource.contents[0]?.text || '{}'));
|
|
1618
|
+
const resourceTask = payload.tasks.find((task) => task.referenceLabel === created.referenceLabel);
|
|
1619
|
+
expect(resourceTask).toMatchObject({
|
|
1620
|
+
referenceLabel: created.referenceLabel,
|
|
1621
|
+
title: 'Verbose task',
|
|
1622
|
+
});
|
|
1623
|
+
expect(resourceTask.description).toBeUndefined();
|
|
1624
|
+
expect(resourceTask.comments).toBeUndefined();
|
|
1625
|
+
expect(resourceTask.activity).toBeUndefined();
|
|
1626
|
+
expect(resourceTask.attachments).toBeUndefined();
|
|
1627
|
+
expect(resourceTask.checklistItems).toBeUndefined();
|
|
1628
|
+
}
|
|
1629
|
+
finally {
|
|
1630
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
it('reads initiative listings with summary initiative and workstream records only', async () => {
|
|
1634
|
+
const projectRoot = createProjectRoot();
|
|
1635
|
+
try {
|
|
1636
|
+
const initiative = parseToolJson(await callTool(projectRoot, 'local', 'create_initiative', {
|
|
1637
|
+
title: 'Summary initiative',
|
|
1638
|
+
description: 'Verbose initiative details should stay off the bulk listing'
|
|
1639
|
+
}));
|
|
1640
|
+
await callTool(projectRoot, 'local', 'create_workstream', {
|
|
1641
|
+
title: 'Summary workstream',
|
|
1642
|
+
description: 'Verbose workstream details should stay off the bulk listing',
|
|
1643
|
+
initiativeId: initiative.initiative.referenceLabel
|
|
1644
|
+
});
|
|
1645
|
+
const initiatives = parseToolJson(await callTool(projectRoot, 'local', 'list_initiatives', {}));
|
|
1646
|
+
const listedInitiative = initiatives.initiatives.find((entry) => entry.initiative.referenceLabel === initiative.initiative.referenceLabel);
|
|
1647
|
+
const listedWorkstream = listedInitiative?.workstreams.find((entry) => entry.title === 'Summary workstream');
|
|
1648
|
+
expect(listedInitiative).toBeTruthy();
|
|
1649
|
+
expect(listedInitiative.initiative.description).toBeUndefined();
|
|
1650
|
+
expect(listedWorkstream).toBeTruthy();
|
|
1651
|
+
expect(listedWorkstream.description).toBeUndefined();
|
|
1652
|
+
expect(listedWorkstream.taskIds).toBeUndefined();
|
|
1653
|
+
}
|
|
1654
|
+
finally {
|
|
1655
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1456
1658
|
it('creates MCP tasks as unassigned when no explicit assignee is provided', async () => {
|
|
1457
1659
|
const projectRoot = createProjectRoot();
|
|
1458
1660
|
try {
|
|
@@ -1494,6 +1696,42 @@ describe('Taskforce MCP tool descriptions', () => {
|
|
|
1494
1696
|
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1495
1697
|
}
|
|
1496
1698
|
});
|
|
1699
|
+
it('returns summary-shaped task entries from the shared tasks resource', async () => {
|
|
1700
|
+
const projectRoot = createProjectRoot();
|
|
1701
|
+
try {
|
|
1702
|
+
const core = new TaskforceCore({
|
|
1703
|
+
projectRoot,
|
|
1704
|
+
storagePath: path.join(projectRoot, '.taskforce')
|
|
1705
|
+
});
|
|
1706
|
+
core.createTask({
|
|
1707
|
+
title: 'Resource summary task',
|
|
1708
|
+
description: 'Verbose task description',
|
|
1709
|
+
comments: [{ id: 'comment-1', author: 'user', text: 'Hello', timestamp: '2026-04-13T01:00:00.000Z' }],
|
|
1710
|
+
attachments: [{ name: 'note.md', path: 'notes/note.md' }],
|
|
1711
|
+
checklistItems: [{ id: 'item-1', taskId: 'temp', title: 'Check me', isCompleted: false, order: 0, createdAt: '2026-04-13T01:00:00.000Z' }],
|
|
1712
|
+
}, {
|
|
1713
|
+
workspaceId: 'workspace-1',
|
|
1714
|
+
actorRef: 'owner-1'
|
|
1715
|
+
});
|
|
1716
|
+
core.close();
|
|
1717
|
+
const tasksResource = await readResource(projectRoot, 'cloud', `taskforce://${path.basename(projectRoot)}/tasks`, {
|
|
1718
|
+
workspaceId: 'workspace-1',
|
|
1719
|
+
authUserId: 'owner-1'
|
|
1720
|
+
});
|
|
1721
|
+
const payload = JSON.parse(String(tasksResource.contents[0]?.text || '{}'));
|
|
1722
|
+
const task = payload.tasks.find((entry) => entry.title === 'Resource summary task');
|
|
1723
|
+
expect(task).toBeDefined();
|
|
1724
|
+
expect(task.description).toBeUndefined();
|
|
1725
|
+
expect(task.comments).toBeUndefined();
|
|
1726
|
+
expect(task.taskEvents).toBeUndefined();
|
|
1727
|
+
expect(task.activity).toBeUndefined();
|
|
1728
|
+
expect(task.attachments).toBeUndefined();
|
|
1729
|
+
expect(task.checklistItems).toBeUndefined();
|
|
1730
|
+
}
|
|
1731
|
+
finally {
|
|
1732
|
+
fs.rmSync(projectRoot, { recursive: true, force: true });
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1497
1735
|
it('persists save_task_attachment through canonical task attachments', async () => {
|
|
1498
1736
|
const projectRoot = createProjectRoot();
|
|
1499
1737
|
try {
|
|
@@ -25,6 +25,7 @@ describe('billing schema parity', () => {
|
|
|
25
25
|
expect(names.has('user_billing')).toBe(true);
|
|
26
26
|
expect(names.has('stripe_webhook_events')).toBe(true);
|
|
27
27
|
expect(names.has('stripe_webhook_failures')).toBe(true);
|
|
28
|
+
expect(names.has('billing_checkout_attempts')).toBe(true);
|
|
28
29
|
expect(names.has('billing_config')).toBe(true);
|
|
29
30
|
expect(names.has('billing_price_mappings')).toBe(true);
|
|
30
31
|
}
|
|
@@ -101,9 +102,11 @@ describe('billing schema parity', () => {
|
|
|
101
102
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS user_billing');
|
|
102
103
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS stripe_webhook_events');
|
|
103
104
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS stripe_webhook_failures');
|
|
105
|
+
expect(sql).toContain('CREATE TABLE IF NOT EXISTS billing_checkout_attempts');
|
|
104
106
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS billing_config');
|
|
105
107
|
expect(sql).toContain('CREATE TABLE IF NOT EXISTS billing_price_mappings');
|
|
106
108
|
expect(sql).toContain('CREATE INDEX IF NOT EXISTS idx_stripe_webhook_failures_purge_after');
|
|
109
|
+
expect(sql).toContain('CREATE INDEX IF NOT EXISTS idx_billing_checkout_attempts_user_status');
|
|
107
110
|
expect(sql).toContain('CREATE INDEX IF NOT EXISTS idx_billing_price_mappings_plan');
|
|
108
111
|
expect(sql).toContain('CREATE UNIQUE INDEX IF NOT EXISTS idx_billing_price_mappings_active_interval');
|
|
109
112
|
});
|
|
@@ -122,9 +122,13 @@ export function ensureAssigneeTaskSchema(db) {
|
|
|
122
122
|
export function ensureTaskReferenceSchema(db) {
|
|
123
123
|
const columns = db.prepare(`PRAGMA table_info(tasks)`).all();
|
|
124
124
|
const hasReferenceNumberColumn = columns.some((c) => c.name === 'reference_number');
|
|
125
|
+
const hasLocalReferenceNumberColumn = columns.some((c) => c.name === 'local_reference_number');
|
|
125
126
|
if (!hasReferenceNumberColumn) {
|
|
126
127
|
db.exec(`ALTER TABLE tasks ADD COLUMN reference_number INTEGER`);
|
|
127
128
|
}
|
|
129
|
+
if (!hasLocalReferenceNumberColumn) {
|
|
130
|
+
db.exec(`ALTER TABLE tasks ADD COLUMN local_reference_number INTEGER`);
|
|
131
|
+
}
|
|
128
132
|
db.exec(`
|
|
129
133
|
CREATE TABLE IF NOT EXISTS workspace_task_reference_sequences (
|
|
130
134
|
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
@@ -132,6 +136,12 @@ export function ensureTaskReferenceSchema(db) {
|
|
|
132
136
|
next_reference_number INTEGER NOT NULL DEFAULT 0,
|
|
133
137
|
PRIMARY KEY (tenant_id, workspace_id)
|
|
134
138
|
);
|
|
139
|
+
CREATE TABLE IF NOT EXISTS workspace_local_task_reference_sequences (
|
|
140
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
141
|
+
workspace_id TEXT NOT NULL DEFAULT 'default',
|
|
142
|
+
next_reference_number INTEGER NOT NULL DEFAULT 0,
|
|
143
|
+
PRIMARY KEY (tenant_id, workspace_id)
|
|
144
|
+
);
|
|
135
145
|
`);
|
|
136
146
|
const workspaceRows = db.prepare(`
|
|
137
147
|
SELECT DISTINCT
|
|
@@ -141,7 +151,7 @@ export function ensureTaskReferenceSchema(db) {
|
|
|
141
151
|
ORDER BY tenant_id ASC, workspace_id ASC
|
|
142
152
|
`).all();
|
|
143
153
|
const selectRows = db.prepare(`
|
|
144
|
-
SELECT id, reference_number, created_at
|
|
154
|
+
SELECT id, reference_number, local_reference_number, created_at
|
|
145
155
|
FROM tasks
|
|
146
156
|
WHERE tenant_id = ? AND workspace_id = ?
|
|
147
157
|
ORDER BY created_at ASC, id ASC
|
|
@@ -171,11 +181,20 @@ export function ensureTaskReferenceSchema(db) {
|
|
|
171
181
|
const currentReferenceNumber = Number.isFinite(rawReferenceNumber) && rawReferenceNumber > 0
|
|
172
182
|
? Math.floor(rawReferenceNumber)
|
|
173
183
|
: null;
|
|
184
|
+
const rawLocalReferenceNumber = Number(row.local_reference_number);
|
|
185
|
+
const currentLocalReferenceNumber = Number.isFinite(rawLocalReferenceNumber) && rawLocalReferenceNumber > 0
|
|
186
|
+
? Math.floor(rawLocalReferenceNumber)
|
|
187
|
+
: null;
|
|
174
188
|
if (currentReferenceNumber && !seen.has(currentReferenceNumber)) {
|
|
175
189
|
seen.add(currentReferenceNumber);
|
|
176
190
|
nextReferenceNumber = Math.max(nextReferenceNumber, currentReferenceNumber);
|
|
177
191
|
continue;
|
|
178
192
|
}
|
|
193
|
+
// Provisional local references are intentionally non-canonical and should
|
|
194
|
+
// not be promoted to T-### during schema repair or restart migrations.
|
|
195
|
+
if (!currentReferenceNumber && currentLocalReferenceNumber) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
179
198
|
nextReferenceNumber += 1;
|
|
180
199
|
seen.add(nextReferenceNumber);
|
|
181
200
|
updateReference.run(nextReferenceNumber, tenantId, workspaceId, taskId);
|
|
@@ -185,6 +204,8 @@ export function ensureTaskReferenceSchema(db) {
|
|
|
185
204
|
db.exec(`
|
|
186
205
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_workspace_reference
|
|
187
206
|
ON tasks(tenant_id, workspace_id, reference_number);
|
|
207
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_tasks_workspace_local_reference
|
|
208
|
+
ON tasks(tenant_id, workspace_id, local_reference_number);
|
|
188
209
|
`);
|
|
189
210
|
}
|
|
190
211
|
export function ensureWorkspaceSchema(db) {
|
|
@@ -215,6 +236,23 @@ export function ensureWorkspaceSchema(db) {
|
|
|
215
236
|
updated_at TEXT
|
|
216
237
|
);
|
|
217
238
|
|
|
239
|
+
CREATE TABLE IF NOT EXISTS user_avatar_upload_drafts (
|
|
240
|
+
draft_id TEXT PRIMARY KEY,
|
|
241
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
242
|
+
user_id TEXT NOT NULL,
|
|
243
|
+
storage_key TEXT NOT NULL,
|
|
244
|
+
mime_type TEXT NOT NULL,
|
|
245
|
+
original_filename TEXT NOT NULL,
|
|
246
|
+
size_bytes INTEGER NOT NULL DEFAULT 0,
|
|
247
|
+
storage_provider TEXT NOT NULL DEFAULT 'local',
|
|
248
|
+
finalized_at TEXT,
|
|
249
|
+
consumed_at TEXT,
|
|
250
|
+
discarded_at TEXT,
|
|
251
|
+
created_at TEXT NOT NULL,
|
|
252
|
+
updated_at TEXT NOT NULL,
|
|
253
|
+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
|
254
|
+
);
|
|
255
|
+
|
|
218
256
|
CREATE TABLE IF NOT EXISTS auth_tokens (
|
|
219
257
|
id TEXT PRIMARY KEY,
|
|
220
258
|
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
@@ -356,6 +394,20 @@ export function ensureWorkspaceSchema(db) {
|
|
|
356
394
|
if (!hasWorkspaceIdColumn) {
|
|
357
395
|
db.exec(`ALTER TABLE auth_tokens ADD COLUMN workspace_id TEXT`);
|
|
358
396
|
}
|
|
397
|
+
if (db.provider === 'postgres') {
|
|
398
|
+
try {
|
|
399
|
+
db.exec(`ALTER TABLE user_avatar_upload_drafts ADD COLUMN IF NOT EXISTS consumed_at TEXT`);
|
|
400
|
+
}
|
|
401
|
+
catch {
|
|
402
|
+
// Best-effort repair for older dev databases.
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
const avatarDraftColumns = db.prepare(`PRAGMA table_info(user_avatar_upload_drafts)`).all();
|
|
407
|
+
if (avatarDraftColumns.length > 0 && !avatarDraftColumns.some((c) => c.name === 'consumed_at')) {
|
|
408
|
+
db.exec(`ALTER TABLE user_avatar_upload_drafts ADD COLUMN consumed_at TEXT`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
359
411
|
const mcpTokenColumns = db.prepare(`PRAGMA table_info(mcp_access_tokens)`).all();
|
|
360
412
|
if (mcpTokenColumns.length > 0) {
|
|
361
413
|
const requiredColumns = [
|
|
@@ -441,6 +493,18 @@ export function ensureBillingSchema(db) {
|
|
|
441
493
|
created_at TEXT NOT NULL
|
|
442
494
|
);
|
|
443
495
|
|
|
496
|
+
CREATE TABLE IF NOT EXISTS billing_checkout_attempts (
|
|
497
|
+
session_id TEXT PRIMARY KEY,
|
|
498
|
+
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
499
|
+
user_id TEXT NOT NULL,
|
|
500
|
+
plan_version_id TEXT NOT NULL,
|
|
501
|
+
billing_interval TEXT,
|
|
502
|
+
status TEXT NOT NULL DEFAULT 'open',
|
|
503
|
+
expires_at TEXT,
|
|
504
|
+
created_at TEXT NOT NULL,
|
|
505
|
+
updated_at TEXT NOT NULL
|
|
506
|
+
);
|
|
507
|
+
|
|
444
508
|
CREATE TABLE IF NOT EXISTS billing_config (
|
|
445
509
|
tenant_id TEXT PRIMARY KEY,
|
|
446
510
|
config_json TEXT NOT NULL DEFAULT '{}',
|
|
@@ -468,6 +532,8 @@ export function ensureBillingSchema(db) {
|
|
|
468
532
|
CREATE INDEX IF NOT EXISTS idx_stripe_webhook_failures_tenant ON stripe_webhook_failures(tenant_id);
|
|
469
533
|
CREATE INDEX IF NOT EXISTS idx_stripe_webhook_failures_event ON stripe_webhook_failures(event_id);
|
|
470
534
|
CREATE INDEX IF NOT EXISTS idx_stripe_webhook_failures_purge_after ON stripe_webhook_failures(purge_after);
|
|
535
|
+
CREATE INDEX IF NOT EXISTS idx_billing_checkout_attempts_user_status ON billing_checkout_attempts(tenant_id, user_id, status, created_at);
|
|
536
|
+
CREATE INDEX IF NOT EXISTS idx_billing_checkout_attempts_plan_status ON billing_checkout_attempts(tenant_id, plan_version_id, status, created_at);
|
|
471
537
|
CREATE INDEX IF NOT EXISTS idx_billing_price_mappings_plan ON billing_price_mappings(tenant_id, plan_version_id);
|
|
472
538
|
CREATE INDEX IF NOT EXISTS idx_billing_price_mappings_active ON billing_price_mappings(tenant_id, active);
|
|
473
539
|
`);
|
|
@@ -671,6 +737,7 @@ export function ensureEntitlementsSchema(db) {
|
|
|
671
737
|
updated_by TEXT,
|
|
672
738
|
created_at TEXT NOT NULL,
|
|
673
739
|
updated_at TEXT NOT NULL,
|
|
740
|
+
FOREIGN KEY (account_id) REFERENCES users(id) ON DELETE CASCADE,
|
|
674
741
|
FOREIGN KEY (plan_version_id) REFERENCES plan_versions(plan_version_id)
|
|
675
742
|
);
|
|
676
743
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|